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 mnd_efree(conn->current_result);
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 end:
739 DBG_INF_FMT("rows=%llu", (unsigned long long)result->stored_data->row_count);
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 enum_func_status
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_RETURN(PASS);
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 DBG_ENTER("mysqlnd_res::fetch_into");
976 if (FAIL == result->m.fetch_row(result, &row_data, flags, &fetched_anything)) {
977 RETVAL_FALSE;
978 DBG_VOID_RETURN;
979 } else if (fetched_anything == FALSE) {
980 RETVAL_NULL();
981 DBG_VOID_RETURN;
982 }
983
984 const MYSQLND_RES_METADATA * const meta = result->meta;
985 unsigned int array_size = meta->field_count;
986 if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
987 array_size *= 2;
988 }
989 array_init_size(return_value, array_size);
990
991 HashTable *row_ht = Z_ARRVAL_P(return_value);
992 MYSQLND_FIELD *field = meta->fields;
993 for (unsigned i = 0; i < meta->field_count; i++, field++) {
994 zval *data = &row_data[i];
995
996 if (flags & MYSQLND_FETCH_NUM) {
997 if (zend_hash_index_add(row_ht, i, data) != NULL) {
998 Z_TRY_ADDREF_P(data);
999 }
1000 }
1001 if (flags & MYSQLND_FETCH_ASSOC) {
1002 /* zend_hash_quick_update needs length + trailing zero */
1003 /* QQ: Error handling ? */
1004 /*
1005 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1006 the index is a numeric and convert it to it. This however means constant
1007 hashing of the column name, which is not needed as it can be precomputed.
1008 */
1009 Z_TRY_ADDREF_P(data);
1010 if (meta->fields[i].is_numeric == FALSE) {
1011 zend_hash_update(row_ht, meta->fields[i].sname, data);
1012 } else {
1013 zend_hash_index_update(row_ht, meta->fields[i].num_key, data);
1014 }
1015 }
1016
1017 zval_ptr_dtor_nogc(data);
1018 }
1019 DBG_VOID_RETURN;
1020 }
1021 /* }}} */
1022
1023
1024 /* {{{ mysqlnd_res::fetch_row_c */
1025 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1026 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1027 {
1028 bool fetched_anything;
1029 zval *row_data;
1030 MYSQLND_ROW_C ret = NULL;
1031 DBG_ENTER("mysqlnd_res::fetch_row_c");
1032
1033 mysqlnd_result_free_prev_data(result);
1034 if (result->m.fetch_row(result, &row_data, 0, &fetched_anything) == PASS && fetched_anything) {
1035 unsigned field_count = result->field_count;
1036 MYSQLND_FIELD *field = result->meta->fields;
1037
1038 ret = mnd_emalloc(field_count * sizeof(char *));
1039 for (unsigned i = 0; i < field_count; i++, field++) {
1040 zval *data = &row_data[i];
1041 if (Z_TYPE_P(data) != IS_NULL) {
1042 convert_to_string(data);
1043 ret[i] = Z_STRVAL_P(data);
1044 } else {
1045 ret[i] = NULL;
1046 }
1047 }
1048 result->free_row_data = 1;
1049 }
1050 DBG_RETURN(ret);
1051 }
1052 /* }}} */
1053
1054
1055 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1056 MYSQLND_METHOD(mysqlnd_res, fetch_row),
1057 MYSQLND_METHOD(mysqlnd_res, use_result),
1058 MYSQLND_METHOD(mysqlnd_res, store_result),
1059 MYSQLND_METHOD(mysqlnd_res, fetch_into),
1060 MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1061 MYSQLND_METHOD(mysqlnd_res, num_rows),
1062 MYSQLND_METHOD(mysqlnd_res, num_fields),
1063 MYSQLND_METHOD(mysqlnd_res, skip_result),
1064 MYSQLND_METHOD(mysqlnd_res, data_seek),
1065 MYSQLND_METHOD(mysqlnd_res, field_seek),
1066 MYSQLND_METHOD(mysqlnd_res, field_tell),
1067 MYSQLND_METHOD(mysqlnd_res, fetch_field),
1068 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1069 MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1070 MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1071 MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1072 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1073 MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1074 MYSQLND_METHOD(mysqlnd_res, free_result),
1075 MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1076 mysqlnd_result_meta_init,
1077 NULL, /* unused1 */
1078 NULL, /* unused2 */
1079 NULL, /* unused3 */
1080 NULL, /* unused4 */
1081 NULL /* unused5 */
1082 MYSQLND_CLASS_METHODS_END;
1083
1084
1085 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1086 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1087 NULL, /* row_decoder */
1088 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1089 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1090 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1091 MYSQLND_CLASS_METHODS_END;
1092
1093
1094 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1095 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row),
1096 NULL, /* row_decoder */
1097 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1098 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths),
1099 MYSQLND_METHOD(mysqlnd_result_buffered, data_seek),
1100 MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1101 MYSQLND_CLASS_METHODS_END;
1102
1103
1104 /* {{{ mysqlnd_result_init */
1105 PHPAPI MYSQLND_RES *
mysqlnd_result_init(const unsigned int field_count)1106 mysqlnd_result_init(const unsigned int field_count)
1107 {
1108 const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1109 MYSQLND_MEMORY_POOL * pool;
1110 MYSQLND_RES * ret;
1111
1112 DBG_ENTER("mysqlnd_result_init");
1113
1114 pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1115 if (!pool) {
1116 DBG_RETURN(NULL);
1117 }
1118
1119 ret = pool->get_chunk(pool, alloc_size);
1120 memset(ret, 0, alloc_size);
1121
1122 ret->row_data = pool->get_chunk(pool, field_count * sizeof(zval));
1123 ret->free_row_data = 0;
1124
1125 ret->memory_pool = pool;
1126 ret->field_count = field_count;
1127 ret->m = *mysqlnd_result_get_methods();
1128
1129 mysqlnd_mempool_save_state(pool);
1130
1131 DBG_RETURN(ret);
1132 }
1133 /* }}} */
1134
1135
1136 /* {{{ mysqlnd_result_unbuffered_init */
1137 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1138 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1139 {
1140 const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1141 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1142 MYSQLND_RES_UNBUFFERED * ret;
1143
1144 DBG_ENTER("mysqlnd_result_unbuffered_init");
1145
1146 ret = pool->get_chunk(pool, alloc_size);
1147 memset(ret, 0, alloc_size);
1148
1149 ret->result_set_memory_pool = pool;
1150 ret->field_count = field_count;
1151 ret->stmt = stmt;
1152
1153 ret->m = *mysqlnd_result_unbuffered_get_methods();
1154
1155 if (stmt) {
1156 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1157 ret->m.fetch_lengths = NULL; /* makes no sense */
1158 ret->lengths = NULL;
1159 } else {
1160 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1161
1162 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1163 memset(ret->lengths, 0, field_count * sizeof(size_t));
1164 }
1165
1166 DBG_RETURN(ret);
1167 }
1168 /* }}} */
1169
1170
1171 /* {{{ mysqlnd_result_buffered_init */
1172 PHPAPI MYSQLND_RES_BUFFERED *
mysqlnd_result_buffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1173 mysqlnd_result_buffered_init(MYSQLND_RES * result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1174 {
1175 const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1176 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1177 MYSQLND_RES_BUFFERED * ret;
1178
1179 DBG_ENTER("mysqlnd_result_buffered_init");
1180
1181 ret = pool->get_chunk(pool, alloc_size);
1182 memset(ret, 0, alloc_size);
1183
1184 mysqlnd_error_info_init(&ret->error_info, /* persistent */ 0);
1185
1186 ret->result_set_memory_pool = pool;
1187 ret->field_count= field_count;
1188 ret->stmt = stmt;
1189 ret->m = *mysqlnd_result_buffered_get_methods();
1190
1191 if (stmt) {
1192 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1193 ret->m.fetch_lengths = NULL; /* makes no sense */
1194 ret->lengths = NULL;
1195 } else {
1196 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1197
1198 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1199 memset(ret->lengths, 0, field_count * sizeof(size_t));
1200 }
1201
1202 DBG_RETURN(ret);
1203 }
1204 /* }}} */
1205