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 | Authors: Andrey Hristov <andrey@php.net> |
14 | Ulf Wendel <uw@php.net> |
15 +----------------------------------------------------------------------+
16 */
17
18 #include "php.h"
19 #include "mysqlnd.h"
20 #include "mysqlnd_wireprotocol.h"
21 #include "mysqlnd_block_alloc.h"
22 #include "mysqlnd_connection.h"
23 #include "mysqlnd_priv.h"
24 #include "mysqlnd_result.h"
25 #include "mysqlnd_result_meta.h"
26 #include "mysqlnd_statistics.h"
27 #include "mysqlnd_debug.h"
28 #include "mysqlnd_ext_plugin.h"
29
30 /* {{{ mysqlnd_result_unbuffered::free_result */
31 static void
MYSQLND_METHOD(mysqlnd_result_unbuffered,free_result)32 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)(MYSQLND_RES_UNBUFFERED * const result, MYSQLND_STATS * const global_stats)
33 {
34 DBG_ENTER("mysqlnd_result_unbuffered, free_result");
35
36 /* must be free before because references the memory pool */
37 if (result->row_packet) {
38 PACKET_FREE(result->row_packet);
39 mnd_efree(result->row_packet);
40 result->row_packet = NULL;
41 }
42
43 DBG_VOID_RETURN;
44 }
45 /* }}} */
46
mysqlnd_result_free_prev_data(MYSQLND_RES * result)47 static void mysqlnd_result_free_prev_data(MYSQLND_RES *result)
48 {
49 if (result->free_row_data) {
50 for (unsigned i = 0; i < result->field_count; ++i) {
51 zval_ptr_dtor_nogc(&result->row_data[i]);
52 }
53 result->free_row_data = 0;
54 }
55 }
56
57 /* {{{ mysqlnd_result_buffered::free_result */
58 static void
MYSQLND_METHOD(mysqlnd_result_buffered,free_result)59 MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * const set)
60 {
61
62 DBG_ENTER("mysqlnd_result_buffered::free_result");
63 DBG_INF_FMT("Freeing %" PRIu64 " row(s)", set->row_count);
64
65 mysqlnd_error_info_free_contents(&set->error_info);
66
67 if (set->row_buffers) {
68 mnd_efree(set->row_buffers);
69 set->row_buffers = NULL;
70 }
71
72 DBG_VOID_RETURN;
73 }
74 /* }}} */
75
76
77 /* {{{ mysqlnd_res::free_result_buffers */
78 static void
MYSQLND_METHOD(mysqlnd_res,free_result_buffers)79 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result)
80 {
81 DBG_ENTER("mysqlnd_res::free_result_buffers");
82 DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
83
84 mysqlnd_result_free_prev_data(result);
85
86 if (result->meta) {
87 ZEND_ASSERT(zend_arena_contains(result->memory_pool->arena, result->meta));
88 result->meta->m->free_metadata(result->meta);
89 result->meta = NULL;
90 }
91
92 if (result->unbuf) {
93 result->unbuf->m.free_result(result->unbuf, result->conn? result->conn->stats : NULL);
94 result->unbuf = NULL;
95 } else if (result->stored_data) {
96 result->stored_data->m.free_result(result->stored_data);
97 result->stored_data = NULL;
98 }
99
100 mysqlnd_mempool_restore_state(result->memory_pool);
101 mysqlnd_mempool_save_state(result->memory_pool);
102
103 DBG_VOID_RETURN;
104 }
105 /* }}} */
106
107
108 /* {{{ mysqlnd_res::free_result_contents_internal */
109 static
MYSQLND_METHOD(mysqlnd_res,free_result_contents_internal)110 void MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal)(MYSQLND_RES * result)
111 {
112 DBG_ENTER("mysqlnd_res::free_result_contents_internal");
113
114 result->m.free_result_buffers(result);
115
116 if (result->conn) {
117 result->conn->m->free_reference(result->conn);
118 result->conn = NULL;
119 }
120
121 mysqlnd_mempool_destroy(result->memory_pool);
122
123 DBG_VOID_RETURN;
124 }
125 /* }}} */
126
127
128 /* {{{ mysqlnd_res::read_result_metadata */
129 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,read_result_metadata)130 MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn)
131 {
132 DBG_ENTER("mysqlnd_res::read_result_metadata");
133
134 /*
135 Make it safe to call it repeatedly for PS -
136 better free and allocate a new because the number of field might change
137 (select *) with altered table. Also for statements which skip the PS
138 infrastructure!
139 */
140 if (result->meta) {
141 result->meta->m->free_metadata(result->meta);
142 result->meta = NULL;
143 }
144
145 result->meta = result->m.result_meta_init(result, result->field_count);
146
147 /* 1. Read all fields metadata */
148
149 /* It's safe to reread without freeing */
150 if (FAIL == result->meta->m->read_metadata(result->meta, conn, result)) {
151 result->meta->m->free_metadata(result->meta);
152 result->meta = NULL;
153 DBG_RETURN(FAIL);
154 }
155 /* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */
156 result->field_count = result->meta->field_count;
157
158 /*
159 2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
160 should consume.
161 3. If there is a result set, it follows. The last packet will have 'eof' set
162 If PS, then no result set follows.
163 */
164
165 DBG_RETURN(PASS);
166 }
167 /* }}} */
168
169
170 /* {{{ mysqlnd_query_read_result_set_header */
171 enum_func_status
mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn,MYSQLND_STMT * s)172 mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
173 {
174 enum_func_status ret;
175 MYSQLND_STMT_DATA * stmt = s ? s->data : NULL;
176 MYSQLND_PACKET_RSET_HEADER rset_header;
177 MYSQLND_PACKET_EOF fields_eof;
178
179 DBG_ENTER("mysqlnd_query_read_result_set_header");
180 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt? stmt->stmt_id:0);
181
182 ret = FAIL;
183 do {
184 conn->payload_decoder_factory->m.init_rset_header_packet(&rset_header);
185 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
186
187 if (FAIL == (ret = PACKET_READ(conn, &rset_header))) {
188 if (conn->error_info->error_no != CR_SERVER_GONE_ERROR) {
189 php_error_docref(NULL, E_WARNING, "Error reading result set's header");
190 }
191 break;
192 }
193
194 if (rset_header.error_info.error_no) {
195 /*
196 Cover a protocol design error: error packet does not
197 contain the server status. Therefore, the client has no way
198 to find out whether there are more result sets of
199 a multiple-result-set statement pending. Luckily, in 5.0 an
200 error always aborts execution of a statement, wherever it is
201 a multi-statement or a stored procedure, so it should be
202 safe to unconditionally turn off the flag here.
203 */
204 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
205 /*
206 This will copy the error code and the messages, as they
207 are buffers in the struct
208 */
209 COPY_CLIENT_ERROR(conn->error_info, rset_header.error_info);
210 ret = FAIL;
211 DBG_ERR_FMT("error=%s", rset_header.error_info.error);
212 /* Return back from CONN_QUERY_SENT */
213 SET_CONNECTION_STATE(&conn->state, CONN_READY);
214 break;
215 }
216 conn->error_info->error_no = 0;
217
218 switch (rset_header.field_count) {
219 case MYSQLND_NULL_LENGTH: { /* LOAD DATA LOCAL INFILE */
220 bool is_warning;
221 DBG_INF("LOAD DATA");
222 conn->last_query_type = QUERY_LOAD_LOCAL;
223 conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
224 SET_CONNECTION_STATE(&conn->state, CONN_SENDING_LOAD_DATA);
225 ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file.s, &is_warning);
226 SET_CONNECTION_STATE(&conn->state, (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
227 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
228 break;
229 }
230 case 0: /* UPSERT */
231 DBG_INF("UPSERT");
232 conn->last_query_type = QUERY_UPSERT;
233 conn->field_count = rset_header.field_count;
234 UPSERT_STATUS_RESET(conn->upsert_status);
235 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, rset_header.warning_count);
236 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, rset_header.server_status);
237 UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, rset_header.affected_rows);
238 UPSERT_STATUS_SET_LAST_INSERT_ID(conn->upsert_status, rset_header.last_insert_id);
239 mysqlnd_set_string(&conn->last_message, rset_header.info_or_local_file.s, rset_header.info_or_local_file.l);
240 /* Result set can follow UPSERT statement, check server_status */
241 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
242 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
243 } else {
244 SET_CONNECTION_STATE(&conn->state, CONN_READY);
245 }
246 ret = PASS;
247 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
248 break;
249 default: do { /* Result set */
250 MYSQLND_RES * result;
251 enum_mysqlnd_collected_stats statistic = STAT_LAST;
252
253 DBG_INF("Result set pending");
254 mysqlnd_set_string(&conn->last_message, NULL, 0);
255
256 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
257 UPSERT_STATUS_RESET(conn->upsert_status);
258 /* restore after zeroing */
259 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
260
261 conn->last_query_type = QUERY_SELECT;
262 SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
263 /* PS has already allocated it */
264 conn->field_count = rset_header.field_count;
265 if (!stmt) {
266 result = conn->current_result = conn->m->result_init(rset_header.field_count);
267 } else {
268 if (!stmt->result) {
269 DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
270 /*
271 This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
272 prepared statements can't send result set metadata for these queries
273 on prepare stage. Read it now.
274 */
275 result = stmt->result = conn->m->result_init(rset_header.field_count);
276 } else {
277 /*
278 Update result set metadata if it for some reason changed between
279 prepare and execute, i.e.:
280 - in case of 'SELECT ?' we don't know column type unless data was
281 supplied to mysql_stmt_execute, so updated column type is sent
282 now.
283 - if data dictionary changed between prepare and execute, for
284 example a table used in the query was altered.
285 Note, that now (4.1.3) we always send metadata in reply to
286 COM_STMT_EXECUTE (even if it is not necessary), so either this or
287 previous branch always works.
288 */
289 if (rset_header.field_count != stmt->result->field_count) {
290 stmt->result->m.free_result(stmt->result, TRUE);
291 stmt->result = conn->m->result_init(rset_header.field_count);
292 }
293 result = stmt->result;
294 }
295 }
296 if (!result) {
297 SET_OOM_ERROR(conn->error_info);
298 ret = FAIL;
299 break;
300 }
301
302 if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
303 /* For PS, we leave them in Prepared state */
304 if (!stmt && conn->current_result) {
305 conn->current_result->m.free_result(conn->current_result, TRUE);
306 conn->current_result = NULL;
307 }
308 DBG_ERR("Error occurred while reading metadata");
309 break;
310 }
311
312 /* Check for SERVER_STATUS_MORE_RESULTS if needed */
313 conn->payload_decoder_factory->m.init_eof_packet(&fields_eof);
314 if (FAIL == (ret = PACKET_READ(conn, &fields_eof))) {
315 DBG_ERR("Error occurred while reading the EOF packet");
316 result->m.free_result_contents(result);
317 if (!stmt) {
318 conn->current_result = NULL;
319 } else {
320 stmt->result = NULL;
321 /* XXX: This will crash, because we will null also the methods.
322 But seems it happens in extreme cases or doesn't. Should be fixed by exporting a function
323 (from mysqlnd_driver.c?) to do the reset.
324 This is done also in mysqlnd_ps.c
325 */
326 memset(stmt, 0, sizeof(*stmt));
327 stmt->state = MYSQLND_STMT_INITTED;
328 }
329 } else {
330 DBG_INF_FMT("warnings=%u server_status=%u", fields_eof.warning_count, fields_eof.server_status);
331 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, fields_eof.warning_count);
332 /*
333 If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
334 The first packet after sending the query/com_execute has the bit set only
335 in this cases. Not sure why it's a needed but it marks that the whole stream
336 will include many result sets. What actually matters are the bits set at the end
337 of every result set (the EOF packet).
338 */
339 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, fields_eof.server_status);
340 if (fields_eof.server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
341 statistic = STAT_BAD_INDEX_USED;
342 } else if (fields_eof.server_status & SERVER_QUERY_NO_INDEX_USED) {
343 statistic = STAT_NO_INDEX_USED;
344 } else if (fields_eof.server_status & SERVER_QUERY_WAS_SLOW) {
345 statistic = STAT_QUERY_WAS_SLOW;
346 }
347 MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
348 }
349 PACKET_FREE(&fields_eof);
350 } while (0);
351 break; /* switch break */
352 }
353 } while (0);
354 PACKET_FREE(&rset_header);
355
356 DBG_INF(ret == PASS? "PASS":"FAIL");
357 DBG_RETURN(ret);
358 }
359 /* }}} */
360
361
362 /* {{{ mysqlnd_result_buffered::fetch_lengths */
363 /*
364 Do lazy initialization for buffered results. As PHP strings have
365 length inside, this function makes not much sense in the context
366 of PHP, to be called as separate function. But let's have it for
367 completeness.
368 */
369 static const size_t *
MYSQLND_METHOD(mysqlnd_result_buffered,fetch_lengths)370 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths)(const MYSQLND_RES_BUFFERED * const result)
371 {
372 DBG_ENTER("mysqlnd_result_buffered::fetch_lengths");
373
374 if (result->current_row > result->row_count || result->current_row == 0) {
375 DBG_INF("EOF");
376 DBG_RETURN(NULL); /* No more rows, or no fetched row */
377 }
378 DBG_INF("non NULL");
379 DBG_RETURN(result->lengths);
380 }
381 /* }}} */
382
383
384 /* {{{ mysqlnd_result_unbuffered::fetch_lengths */
385 static const size_t *
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_lengths)386 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths)(const MYSQLND_RES_UNBUFFERED * const result)
387 {
388 /* simulate output of libmysql */
389 return (result->last_row_buffer.ptr || result->eof_reached)? result->lengths : NULL;
390 }
391 /* }}} */
392
393
394 /* {{{ mysqlnd_res::fetch_lengths */
395 static const size_t *
MYSQLND_METHOD(mysqlnd_res,fetch_lengths)396 MYSQLND_METHOD(mysqlnd_res, fetch_lengths)(const MYSQLND_RES * const result)
397 {
398 const size_t * ret;
399 DBG_ENTER("mysqlnd_res::fetch_lengths");
400 ret = result->stored_data && result->stored_data->m.fetch_lengths ?
401 result->stored_data->m.fetch_lengths(result->stored_data) :
402 (result->unbuf && result->unbuf->m.fetch_lengths ?
403 result->unbuf->m.fetch_lengths(result->unbuf) :
404 NULL
405 );
406 DBG_RETURN(ret);
407 }
408 /* }}} */
409
410
411 /* {{{ mysqlnd_result_unbuffered::fetch_row */
412 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_row)413 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, zval **row_ptr, const unsigned int flags, bool * fetched_anything)
414 {
415 enum_func_status ret;
416 MYSQLND_PACKET_ROW *row_packet = result->unbuf->row_packet;
417 const MYSQLND_RES_METADATA * const meta = result->meta;
418 MYSQLND_RES_UNBUFFERED *set = result->unbuf;
419 MYSQLND_CONN_DATA * const conn = result->conn;
420 void *checkpoint;
421
422 DBG_ENTER("mysqlnd_result_unbuffered::fetch_row");
423
424 *fetched_anything = FALSE;
425 if (set->eof_reached) {
426 /* No more rows obviously */
427 DBG_RETURN(PASS);
428 }
429 if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) {
430 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
431 DBG_RETURN(FAIL);
432 }
433 if (!row_packet) {
434 /* Not fully initialized object that is being cleaned up */
435 DBG_RETURN(FAIL);
436 }
437
438 checkpoint = result->memory_pool->checkpoint;
439 mysqlnd_mempool_save_state(result->memory_pool);
440
441 if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
442 set->last_row_buffer = row_packet->row_buffer;
443 row_packet->row_buffer.ptr = NULL;
444
445 MYSQLND_INC_CONN_STATISTIC(conn->stats, set->stmt
446 ? STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF
447 : STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
448
449 if (row_ptr) {
450 unsigned int field_count = meta->field_count;
451
452 *row_ptr = result->row_data;
453 enum_func_status rc = set->m.row_decoder(
454 &set->last_row_buffer, result->row_data, field_count,
455 row_packet->fields_metadata, conn->options->int_and_float_native, conn->stats);
456 if (PASS != rc) {
457 mysqlnd_mempool_restore_state(result->memory_pool);
458 result->memory_pool->checkpoint = checkpoint;
459 DBG_RETURN(FAIL);
460 }
461
462 size_t *lengths = set->lengths;
463 if (lengths) {
464 for (unsigned i = 0; i < field_count; i++) {
465 zval *data = &result->row_data[i];
466 lengths[i] = Z_TYPE_P(data) == IS_STRING ? Z_STRLEN_P(data) : 0;
467 }
468 }
469 }
470 set->row_count++;
471 *fetched_anything = TRUE;
472 } else if (ret == FAIL) {
473 if (row_packet->error_info.error_no) {
474 COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
475 if (set->stmt) {
476 COPY_CLIENT_ERROR(set->stmt->error_info, row_packet->error_info);
477 }
478 DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
479 }
480 if (GET_CONNECTION_STATE(&conn->state) != CONN_QUIT_SENT) {
481 SET_CONNECTION_STATE(&conn->state, CONN_READY);
482 }
483 set->eof_reached = TRUE; /* so next time we won't get an error */
484 } else if (row_packet->eof) {
485 /* Mark the connection as usable again */
486 DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
487 set->eof_reached = TRUE;
488
489 UPSERT_STATUS_RESET(conn->upsert_status);
490 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
491 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
492 /*
493 result->row_packet will be cleaned when
494 destroying the result object
495 */
496 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
497 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
498 } else {
499 SET_CONNECTION_STATE(&conn->state, CONN_READY);
500 }
501 }
502
503 mysqlnd_mempool_restore_state(result->memory_pool);
504 result->memory_pool->checkpoint = checkpoint;
505
506 DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
507 DBG_RETURN(ret);
508 }
509 /* }}} */
510
511
512 /* {{{ mysqlnd_res::use_result */
513 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)514 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, MYSQLND_STMT_DATA *stmt)
515 {
516 MYSQLND_CONN_DATA * const conn = result->conn;
517 DBG_ENTER("mysqlnd_res::use_result");
518
519 SET_EMPTY_ERROR(conn->error_info);
520
521 if (!stmt) {
522 result->type = MYSQLND_RES_NORMAL;
523 } else {
524 result->type = MYSQLND_RES_PS_UNBUF;
525 }
526
527 result->unbuf = mysqlnd_result_unbuffered_init(result, result->field_count, stmt);
528
529 /*
530 Will be freed in the mysqlnd_internal_free_result_contents() called
531 by the resource destructor. mysqlnd_result_unbuffered::fetch_row() expects
532 this to be not NULL.
533 */
534 /* FALSE = non-persistent */
535 {
536 struct st_mysqlnd_packet_row *row_packet = mnd_emalloc(sizeof(struct st_mysqlnd_packet_row));
537
538 conn->payload_decoder_factory->m.init_row_packet(row_packet);
539 row_packet->result_set_memory_pool = result->unbuf->result_set_memory_pool;
540 row_packet->field_count = result->field_count;
541 row_packet->binary_protocol = stmt != NULL;
542 row_packet->fields_metadata = result->meta->fields;
543
544 result->unbuf->row_packet = row_packet;
545 }
546
547 DBG_RETURN(result);
548 }
549 /* }}} */
550
551
552 /* {{{ mysqlnd_result_buffered::fetch_row */
553 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered,fetch_row)554 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, zval **row_ptr, const unsigned int flags, bool * fetched_anything)
555 {
556 MYSQLND_RES_BUFFERED *set = result->stored_data;
557
558 DBG_ENTER("mysqlnd_result_buffered::fetch_row");
559
560 /* If we haven't read everything */
561 if (set->current_row < set->row_count) {
562 if (row_ptr) {
563 const MYSQLND_RES_METADATA * const meta = result->meta;
564 const unsigned int field_count = meta->field_count;
565 MYSQLND_CONN_DATA * const conn = result->conn;
566 enum_func_status rc;
567 zval *current_row = result->row_data;
568 *row_ptr = result->row_data;
569 rc = result->stored_data->m.row_decoder(&set->row_buffers[set->current_row],
570 current_row,
571 field_count,
572 meta->fields,
573 conn->options->int_and_float_native,
574 conn->stats);
575 if (rc != PASS) {
576 DBG_RETURN(FAIL);
577 }
578
579 if (set->lengths) {
580 for (unsigned i = 0; i < field_count; ++i) {
581 zval *data = ¤t_row[i];
582 set->lengths[i] = Z_TYPE_P(data) == IS_STRING ? Z_STRLEN_P(data) : 0;
583 }
584 }
585 }
586
587 ++set->current_row;
588 MYSQLND_INC_GLOBAL_STATISTIC(set->stmt
589 ? STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF : STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
590 *fetched_anything = TRUE;
591 } else {
592 if (set->current_row == set->row_count) {
593 set->current_row = set->row_count + 1;
594 }
595 DBG_INF_FMT("EOF reached. current_row=%llu", (unsigned long long) set->current_row);
596 *fetched_anything = FALSE;
597 }
598
599 DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
600 DBG_RETURN(PASS);
601 }
602 /* }}} */
603
604
605 /* {{{ mysqlnd_res::fetch_row */
606 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,fetch_row)607 MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES *result, zval **row_ptr, const unsigned int flags, bool *fetched_anything)
608 {
609 const mysqlnd_fetch_row_func f =
610 result->stored_data ? result->stored_data->m.fetch_row :
611 result->unbuf ? result->unbuf->m.fetch_row : NULL;
612 if (f) {
613 return f(result, row_ptr, flags, fetched_anything);
614 }
615 *fetched_anything = FALSE;
616 return PASS;
617 }
618 /* }}} */
619
620
621 /* {{{ mysqlnd_res::store_result_fetch_data */
622 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)623 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result,
624 MYSQLND_RES_METADATA * meta,
625 MYSQLND_ROW_BUFFER **row_buffers,
626 bool binary_protocol)
627 {
628 enum_func_status ret;
629 uint64_t total_allocated_rows = 0;
630 unsigned int free_rows = 0;
631 MYSQLND_RES_BUFFERED * set = result->stored_data;
632 MYSQLND_PACKET_ROW row_packet;
633
634 DBG_ENTER("mysqlnd_res::store_result_fetch_data");
635 if (!set || !row_buffers) {
636 ret = FAIL;
637 goto end;
638 }
639
640 *row_buffers = NULL;
641
642 conn->payload_decoder_factory->m.init_row_packet(&row_packet);
643 set->references = 1;
644
645 row_packet.result_set_memory_pool = result->stored_data->result_set_memory_pool;
646 row_packet.field_count = meta->field_count;
647 row_packet.binary_protocol = binary_protocol;
648 row_packet.fields_metadata = meta->fields;
649
650 while (FAIL != (ret = PACKET_READ(conn, &row_packet)) && !row_packet.eof) {
651 if (!free_rows) {
652 MYSQLND_ROW_BUFFER * new_row_buffers;
653
654 if (total_allocated_rows < 1024) {
655 if (total_allocated_rows == 0) {
656 free_rows = 1;
657 total_allocated_rows = 1;
658 } else {
659 free_rows = total_allocated_rows;
660 total_allocated_rows *= 2;
661 }
662 } else {
663 free_rows = 1024;
664 total_allocated_rows += 1024;
665 }
666
667 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
668 if (total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
669 SET_OOM_ERROR(conn->error_info);
670 ret = FAIL;
671 goto free_end;
672 }
673 if (*row_buffers) {
674 new_row_buffers = mnd_erealloc(*row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
675 } else {
676 new_row_buffers = mnd_emalloc((size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
677 }
678 *row_buffers = new_row_buffers;
679 }
680 free_rows--;
681 (*row_buffers)[set->row_count] = row_packet.row_buffer;
682
683 set->row_count++;
684
685 /* So row_packet's destructor function won't efree() it */
686 row_packet.row_buffer.ptr = NULL;
687 }
688 /* Overflow ? */
689 MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
690 binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
691 STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
692 set->row_count);
693
694 /* Finally clean */
695 if (row_packet.eof) {
696 UPSERT_STATUS_RESET(conn->upsert_status);
697 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet.warning_count);
698 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet.server_status);
699 }
700
701 if (ret == FAIL) {
702 /* Error packets do not contain server status information. However, we know that after
703 * an error there will be no further result sets. */
704 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status,
705 UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
706 }
707
708 /* save some memory */
709 if (free_rows) {
710 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
711 if (set->row_count * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
712 SET_OOM_ERROR(conn->error_info);
713 ret = FAIL;
714 goto free_end;
715 }
716 *row_buffers = mnd_erealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_ROW_BUFFER)));
717 }
718
719 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
720 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
721 } else {
722 SET_CONNECTION_STATE(&conn->state, CONN_READY);
723 }
724
725 if (ret == FAIL) {
726 COPY_CLIENT_ERROR(&set->error_info, row_packet.error_info);
727 } else {
728 /* libmysql's documentation says it should be so for SELECT statements */
729 UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, set->row_count);
730 }
731 DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
732 ret == PASS? "PASS":"FAIL",
733 (uint32_t) set->row_count,
734 UPSERT_STATUS_GET_WARNINGS(conn->upsert_status),
735 UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status));
736 free_end:
737 PACKET_FREE(&row_packet);
738 DBG_INF_FMT("rows=%llu", (unsigned long long)set->row_count);
739 end:
740 DBG_RETURN(ret);
741 }
742 /* }}} */
743
744
745 /* {{{ mysqlnd_res::store_result */
746 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)747 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
748 MYSQLND_CONN_DATA * const conn,
749 MYSQLND_STMT_DATA *stmt)
750 {
751 enum_func_status ret;
752 MYSQLND_ROW_BUFFER **row_buffers = NULL;
753
754 DBG_ENTER("mysqlnd_res::store_result");
755
756 /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
757 /* In case of error the reference will be released in free_result() called indirectly by our caller */
758 result->conn = conn->m->get_reference(conn);
759 result->type = MYSQLND_RES_NORMAL;
760
761 SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
762
763 result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_init(result, result->field_count, stmt);
764 row_buffers = &result->stored_data->row_buffers;
765
766 ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, stmt != NULL);
767
768 if (FAIL == ret) {
769 if (result->stored_data) {
770 COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
771 } else {
772 SET_OOM_ERROR(conn->error_info);
773 }
774 DBG_RETURN(NULL);
775 } else {
776 result->stored_data->current_row = 0;
777 }
778
779 /* libmysql's documentation says it should be so for SELECT statements */
780 UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, result->stored_data->row_count);
781
782 DBG_RETURN(result);
783 }
784 /* }}} */
785
786
787 /* {{{ mysqlnd_res::skip_result */
788 static void
MYSQLND_METHOD(mysqlnd_res,skip_result)789 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result)
790 {
791 bool fetched_anything;
792
793 DBG_ENTER("mysqlnd_res::skip_result");
794 /*
795 Unbuffered sets
796 A PS could be prepared - there is metadata and thus a stmt->result but the
797 fetch_row function isn't actually set (NULL), thus we have to skip these.
798 */
799 if (result->unbuf && !result->unbuf->eof_reached) {
800 MYSQLND_CONN_DATA * const conn = result->conn;
801 DBG_INF("skipping result");
802 /* We have to fetch all data to clean the line */
803 MYSQLND_INC_CONN_STATISTIC(conn->stats,
804 result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
805 STAT_FLUSHED_PS_SETS);
806
807 while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything)) && fetched_anything == TRUE) {
808 MYSQLND_INC_CONN_STATISTIC(conn->stats,
809 result->type == MYSQLND_RES_NORMAL
810 ? STAT_ROWS_SKIPPED_NORMAL : STAT_ROWS_SKIPPED_PS);
811 }
812 }
813 DBG_VOID_RETURN;
814 }
815 /* }}} */
816
817
818 /* {{{ mysqlnd_res::free_result */
819 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)820 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, const bool implicit)
821 {
822 DBG_ENTER("mysqlnd_res::free_result");
823
824 MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
825 implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
826 STAT_FREE_RESULT_EXPLICIT);
827
828 result->m.skip_result(result);
829 result->m.free_result_contents(result);
830 DBG_RETURN(PASS);
831 }
832 /* }}} */
833
834
835 /* {{{ mysqlnd_res::data_seek */
836 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)837 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_t row)
838 {
839 DBG_ENTER("mysqlnd_res::data_seek");
840 DBG_INF_FMT("row=%" PRIu64, row);
841
842 DBG_RETURN(result->stored_data? result->stored_data->m.data_seek(result->stored_data, row) : FAIL);
843 }
844 /* }}} */
845
846
847 /* {{{ mysqlnd_result_buffered::data_seek */
848 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered,data_seek)849 MYSQLND_METHOD(mysqlnd_result_buffered, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
850 {
851 DBG_ENTER("mysqlnd_result_buffered::data_seek");
852
853 /* libmysql just moves to the end, it does traversing of a linked list */
854 if (row >= result->row_count) {
855 result->current_row = result->row_count;
856 } else {
857 result->current_row = row;
858 }
859 DBG_RETURN(PASS);
860 }
861 /* }}} */
862
863
864 /* {{{ mysqlnd_result_unbuffered::num_rows */
865 static uint64_t
MYSQLND_METHOD(mysqlnd_result_unbuffered,num_rows)866 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows)(const MYSQLND_RES_UNBUFFERED * const result)
867 {
868 /* Be compatible with libmysql. We count row_count, but will return 0 */
869 return result->eof_reached? result->row_count : 0;
870 }
871 /* }}} */
872
873
874 /* {{{ mysqlnd_result_buffered::num_rows */
875 static uint64_t
MYSQLND_METHOD(mysqlnd_result_buffered,num_rows)876 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows)(const MYSQLND_RES_BUFFERED * const result)
877 {
878 return result->row_count;
879 }
880 /* }}} */
881
882
883 /* {{{ mysqlnd_res::num_rows */
884 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)885 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result)
886 {
887 return result->stored_data?
888 result->stored_data->m.num_rows(result->stored_data) :
889 (result->unbuf? result->unbuf->m.num_rows(result->unbuf) : 0);
890 }
891 /* }}} */
892
893
894 /* {{{ mysqlnd_res::num_fields */
895 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)896 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result)
897 {
898 return result->field_count;
899 }
900 /* }}} */
901
902
903 /* {{{ mysqlnd_res::fetch_field */
904 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)905 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result)
906 {
907 DBG_ENTER("mysqlnd_res::fetch_field");
908 do {
909 if (result->meta) {
910 DBG_RETURN(result->meta->m->fetch_field(result->meta));
911 }
912 } while (0);
913 DBG_RETURN(NULL);
914 }
915 /* }}} */
916
917
918 /* {{{ mysqlnd_res::fetch_field_direct */
919 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)920 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr)
921 {
922 DBG_ENTER("mysqlnd_res::fetch_field_direct");
923 do {
924 if (result->meta) {
925 DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr));
926 }
927 } while (0);
928
929 DBG_RETURN(NULL);
930 }
931 /* }}} */
932
933
934 /* {{{ mysqlnd_res::fetch_field */
935 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)936 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result)
937 {
938 DBG_ENTER("mysqlnd_res::fetch_fields");
939 do {
940 if (result->meta) {
941 DBG_RETURN(result->meta->m->fetch_fields(result->meta));
942 }
943 } while (0);
944 DBG_RETURN(NULL);
945 }
946 /* }}} */
947
948
949 /* {{{ mysqlnd_res::field_seek */
950 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)951 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset)
952 {
953 return result->meta? result->meta->m->field_seek(result->meta, field_offset) : 0;
954 }
955 /* }}} */
956
957
958 /* {{{ mysqlnd_res::field_tell */
959 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)960 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result)
961 {
962 return result->meta? result->meta->m->field_tell(result->meta) : 0;
963 }
964 /* }}} */
965
966
967 /* {{{ mysqlnd_res::fetch_into */
968 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)969 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags,
970 zval *return_value ZEND_FILE_LINE_DC)
971 {
972 bool fetched_anything;
973 zval *row_data;
974
975 // We clean the error here because in unbuffered mode we could receive a new error
976 // and therefore consumers of this method are checking for errors
977 MYSQLND_CONN_DATA *conn = result->conn;
978 if (conn) {
979 SET_EMPTY_ERROR(conn->error_info);
980 }
981
982 DBG_ENTER("mysqlnd_res::fetch_into");
983 if (FAIL == result->m.fetch_row(result, &row_data, flags, &fetched_anything)) {
984 RETVAL_FALSE;
985 DBG_VOID_RETURN;
986 } else if (fetched_anything == FALSE) {
987 RETVAL_NULL();
988 DBG_VOID_RETURN;
989 }
990
991 const MYSQLND_RES_METADATA * const meta = result->meta;
992 unsigned int array_size = meta->field_count;
993 if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
994 array_size *= 2;
995 }
996 array_init_size(return_value, array_size);
997
998 HashTable *row_ht = Z_ARRVAL_P(return_value);
999 MYSQLND_FIELD *field = meta->fields;
1000 for (unsigned i = 0; i < meta->field_count; i++, field++) {
1001 zval *data = &row_data[i];
1002
1003 if (flags & MYSQLND_FETCH_NUM) {
1004 if (zend_hash_index_add(row_ht, i, data) != NULL) {
1005 Z_TRY_ADDREF_P(data);
1006 }
1007 }
1008 if (flags & MYSQLND_FETCH_ASSOC) {
1009 /* zend_hash_quick_update needs length + trailing zero */
1010 /* QQ: Error handling ? */
1011 /*
1012 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1013 the index is a numeric and convert it to it. This however means constant
1014 hashing of the column name, which is not needed as it can be precomputed.
1015 */
1016 Z_TRY_ADDREF_P(data);
1017 if (meta->fields[i].is_numeric == FALSE) {
1018 zend_hash_update(row_ht, meta->fields[i].sname, data);
1019 } else {
1020 zend_hash_index_update(row_ht, meta->fields[i].num_key, data);
1021 }
1022 }
1023
1024 zval_ptr_dtor_nogc(data);
1025 }
1026 DBG_VOID_RETURN;
1027 }
1028 /* }}} */
1029
1030
1031 /* {{{ mysqlnd_res::fetch_row_c */
1032 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1033 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1034 {
1035 bool fetched_anything;
1036 zval *row_data;
1037 MYSQLND_ROW_C ret = NULL;
1038 DBG_ENTER("mysqlnd_res::fetch_row_c");
1039
1040 mysqlnd_result_free_prev_data(result);
1041 if (result->m.fetch_row(result, &row_data, 0, &fetched_anything) == PASS && fetched_anything) {
1042 unsigned field_count = result->field_count;
1043 MYSQLND_FIELD *field = result->meta->fields;
1044
1045 ret = mnd_emalloc(field_count * sizeof(char *));
1046 for (unsigned i = 0; i < field_count; i++, field++) {
1047 zval *data = &row_data[i];
1048 if (Z_TYPE_P(data) != IS_NULL) {
1049 convert_to_string(data);
1050 ret[i] = Z_STRVAL_P(data);
1051 } else {
1052 ret[i] = NULL;
1053 }
1054 }
1055 result->free_row_data = 1;
1056 }
1057 DBG_RETURN(ret);
1058 }
1059 /* }}} */
1060
1061
1062 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1063 MYSQLND_METHOD(mysqlnd_res, fetch_row),
1064 MYSQLND_METHOD(mysqlnd_res, use_result),
1065 MYSQLND_METHOD(mysqlnd_res, store_result),
1066 MYSQLND_METHOD(mysqlnd_res, fetch_into),
1067 MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1068 MYSQLND_METHOD(mysqlnd_res, num_rows),
1069 MYSQLND_METHOD(mysqlnd_res, num_fields),
1070 MYSQLND_METHOD(mysqlnd_res, skip_result),
1071 MYSQLND_METHOD(mysqlnd_res, data_seek),
1072 MYSQLND_METHOD(mysqlnd_res, field_seek),
1073 MYSQLND_METHOD(mysqlnd_res, field_tell),
1074 MYSQLND_METHOD(mysqlnd_res, fetch_field),
1075 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1076 MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1077 MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1078 MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1079 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1080 MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1081 MYSQLND_METHOD(mysqlnd_res, free_result),
1082 MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1083 mysqlnd_result_meta_init,
1084 NULL, /* unused1 */
1085 NULL, /* unused2 */
1086 NULL, /* unused3 */
1087 NULL, /* unused4 */
1088 NULL /* unused5 */
1089 MYSQLND_CLASS_METHODS_END;
1090
1091
1092 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1093 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1094 NULL, /* row_decoder */
1095 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1096 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1097 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1098 MYSQLND_CLASS_METHODS_END;
1099
1100
1101 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1102 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row),
1103 NULL, /* row_decoder */
1104 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1105 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths),
1106 MYSQLND_METHOD(mysqlnd_result_buffered, data_seek),
1107 MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1108 MYSQLND_CLASS_METHODS_END;
1109
1110
1111 /* {{{ mysqlnd_result_init */
1112 PHPAPI MYSQLND_RES *
mysqlnd_result_init(const unsigned int field_count)1113 mysqlnd_result_init(const unsigned int field_count)
1114 {
1115 const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1116 MYSQLND_MEMORY_POOL * pool;
1117 MYSQLND_RES * ret;
1118
1119 DBG_ENTER("mysqlnd_result_init");
1120
1121 pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1122 if (!pool) {
1123 DBG_RETURN(NULL);
1124 }
1125
1126 ret = pool->get_chunk(pool, alloc_size);
1127 memset(ret, 0, alloc_size);
1128
1129 ret->row_data = pool->get_chunk(pool, field_count * sizeof(zval));
1130 ret->free_row_data = 0;
1131
1132 ret->memory_pool = pool;
1133 ret->field_count = field_count;
1134 ret->m = *mysqlnd_result_get_methods();
1135
1136 mysqlnd_mempool_save_state(pool);
1137
1138 DBG_RETURN(ret);
1139 }
1140 /* }}} */
1141
1142
1143 /* {{{ mysqlnd_result_unbuffered_init */
1144 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1145 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1146 {
1147 const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1148 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1149 MYSQLND_RES_UNBUFFERED * ret;
1150
1151 DBG_ENTER("mysqlnd_result_unbuffered_init");
1152
1153 ret = pool->get_chunk(pool, alloc_size);
1154 memset(ret, 0, alloc_size);
1155
1156 ret->result_set_memory_pool = pool;
1157 ret->field_count = field_count;
1158 ret->stmt = stmt;
1159
1160 ret->m = *mysqlnd_result_unbuffered_get_methods();
1161
1162 if (stmt) {
1163 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1164 ret->m.fetch_lengths = NULL; /* makes no sense */
1165 ret->lengths = NULL;
1166 } else {
1167 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1168
1169 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1170 memset(ret->lengths, 0, field_count * sizeof(size_t));
1171 }
1172
1173 DBG_RETURN(ret);
1174 }
1175 /* }}} */
1176
1177
1178 /* {{{ mysqlnd_result_buffered_init */
1179 PHPAPI MYSQLND_RES_BUFFERED *
mysqlnd_result_buffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1180 mysqlnd_result_buffered_init(MYSQLND_RES * result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1181 {
1182 const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1183 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1184 MYSQLND_RES_BUFFERED * ret;
1185
1186 DBG_ENTER("mysqlnd_result_buffered_init");
1187
1188 ret = pool->get_chunk(pool, alloc_size);
1189 memset(ret, 0, alloc_size);
1190
1191 mysqlnd_error_info_init(&ret->error_info, /* persistent */ 0);
1192
1193 ret->result_set_memory_pool = pool;
1194 ret->field_count= field_count;
1195 ret->stmt = stmt;
1196 ret->m = *mysqlnd_result_buffered_get_methods();
1197
1198 if (stmt) {
1199 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1200 ret->m.fetch_lengths = NULL; /* makes no sense */
1201 ret->lengths = NULL;
1202 } else {
1203 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1204
1205 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1206 memset(ret->lengths, 0, field_count * sizeof(size_t));
1207 }
1208
1209 DBG_RETURN(ret);
1210 }
1211 /* }}} */
1212