📄 mysqlnd.c
字号:
mysqlnd_read_result_metadata(MYSQLND *conn, MYSQLND_RES *result TSRMLS_DC){ int i = 0; php_mysql_packet_res_field field_packet; /* Make it safe to call it repeatedly for PS - better free and allocate a new because the number of field might change (select *) with altered table. Also for statements which skip the PS infrastructure. */ if (result->meta) { mysqlnd_internal_free_result_metadata(result->meta, FALSE TSRMLS_CC); result->meta = NULL; } /* +1 is to have empty marker at the end */ result->meta = ecalloc(1, sizeof(MYSQLND_RES_METADATA)); result->meta->field_count = result->field_count; result->meta->fields = ecalloc(result->field_count + 1, sizeof(MYSQLND_FIELD)); result->meta->zend_hash_keys = ecalloc(result->field_count, sizeof(struct mysqlnd_field_hash_key)); /* 1. Read all fields metadata */ /* It's safe to reread without freeing */ PACKET_INIT_ALLOCA(field_packet, PROT_RSET_FLD_PACKET); for (;i < result->field_count; i++) { long idx; if (result->meta->fields[i].root) { /* We re-read metadata for PS */ efree(result->meta->fields[i].root); result->meta->fields[i].root = NULL; } field_packet.metadata = &(result->meta->fields[i]); if (FAIL == PACKET_READ_ALLOCA(field_packet, conn)) { PACKET_FREE_ALLOCA(field_packet); goto error; } if (mysqlnd_ps_fetch_functions[result->meta->fields[i].type].func == NULL) { SET_CLIENT_ERROR(result->conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Uknown field type sent by the server"); php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown type %d sent by the server. " "Please send a report to the developers", result->meta->fields[i].type); goto error; }#if PHP_MAJOR_VERSION >= 6 if (UG(unicode)) { UChar *ustr; int ulen; zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, result->meta->fields[i].name, result->meta->fields[i].name_length TSRMLS_CC); if ((result->meta->zend_hash_keys[i].is_numeric = mysqlnd_unicode_is_key_numeric(ustr, ulen + 1, &idx))) { result->meta->zend_hash_keys[i].key = idx; efree(ustr); } else { result->meta->zend_hash_keys[i].ustr.u = ustr; result->meta->zend_hash_keys[i].ulen = ulen; result->meta->zend_hash_keys[i].key = zend_u_get_hash_value(IS_UNICODE, ZSTR(ustr), ulen + 1); } } else #endif { /* For BC we have to check whether the key is numeric and use it like this */ if ((result->meta->zend_hash_keys[i].is_numeric = mysqlnd_is_key_numeric(field_packet.metadata->name, field_packet.metadata->name_length + 1, &idx))) { result->meta->zend_hash_keys[i].key = idx; } else { result->meta->zend_hash_keys[i].key = zend_get_hash_value(field_packet.metadata->name, field_packet.metadata->name_length + 1); } } } PACKET_FREE_ALLOCA(field_packet); /* 2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata() should consume. 3. If there is a result set, it follows. The last packet will have 'eof' set If PS, then no result set follows. */ return PASS;error: mysqlnd_internal_free_result_contents(result TSRMLS_CC); return FAIL;}/* }}} */enum_func_statusmysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC){ enum_func_status ret; php_mysql_packet_rset_header rset_header; ret = FAIL; PACKET_INIT_ALLOCA(rset_header, PROT_RSET_HEADER_PACKET); do { if (FAIL == (ret = PACKET_READ_ALLOCA(rset_header, conn))) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading result set's header"); break; } if (rset_header.error_info.error_no) { /* Cover a protocol design error: error packet does not contain the server status. Therefore, the client has no way to find out whether there are more result sets of a multiple-result-set statement pending. Luckily, in 5.0 an error always aborts execution of a statement, wherever it is a multi-statement or a stored procedure, so it should be safe to unconditionally turn off the flag here. */ conn->upsert_status.server_status &= ~SERVER_MORE_RESULTS_EXISTS; conn->upsert_status.affected_rows = -1; /* This will copy the error code and the messages, as they are buffers in the struct */ conn->error_info = rset_header.error_info; ret = FAIL; break; } conn->error_info.error_no = 0; switch (rset_header.field_count) { case MYSQLND_NULL_LENGTH: { /* LOAD DATA LOCAL INFILE */ zend_bool is_warning; conn->last_query_type = QUERY_LOAD_LOCAL; ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file, &is_warning TSRMLS_CC); conn->state = (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT; MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_NON_RSET_QUERY); break; } case 0: /* UPSERT */ conn->last_query_type = QUERY_UPSERT; conn->field_count = rset_header.field_count; conn->upsert_status.warning_count = rset_header.warning_count; conn->upsert_status.server_status = rset_header.server_status; conn->upsert_status.affected_rows = rset_header.affected_rows; conn->upsert_status.last_insert_id = rset_header.last_insert_id; SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, rset_header.info_or_local_file, rset_header.info_or_local_file_len); /* Result set can follow UPSERT statement, check server_status */ if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) { conn->state = CONN_NEXT_RESULT_PENDING; } else { conn->state = CONN_READY; } ret = PASS; MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_NON_RSET_QUERY); break; default:{ /* Result set */ php_mysql_packet_eof fields_eof; MYSQLND_RES *result; uint stat = -1; MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_RSET_QUERY); memset(&conn->upsert_status, 0, sizeof(conn->upsert_status)); conn->last_query_type = QUERY_SELECT; conn->state = CONN_FETCHING_DATA; /* PS has already allocated it */ if (!stmt) { conn->field_count = rset_header.field_count; result = conn->current_result= mysqlnd_result_init(rset_header.field_count, mysqlnd_palloc_get_cache_reference(conn->zval_cache)); } else { if (!stmt->result) { /* This is 'SHOW'/'EXPLAIN'-like query. Current implementation of prepared statements can't send result set metadata for these queries on prepare stage. Read it now. */ conn->field_count = rset_header.field_count; result = stmt->result = mysqlnd_result_init(rset_header.field_count, mysqlnd_palloc_get_cache_reference(conn->zval_cache)); } else { /* Update result set metadata if it for some reason changed between prepare and execute, i.e.: - in case of 'SELECT ?' we don't know column type unless data was supplied to mysql_stmt_execute, so updated column type is sent now. - if data dictionary changed between prepare and execute, for example a table used in the query was altered. Note, that now (4.1.3) we always send metadata in reply to COM_STMT_EXECUTE (even if it is not necessary), so either this or previous branch always works. */ } result = stmt->result; } if (FAIL == (ret = mysqlnd_read_result_metadata(conn, result TSRMLS_CC))) { /* For PS, we leave them in Prepared state */ if (!stmt) { efree(conn->current_result); conn->current_result = NULL; } break; } /* Check for SERVER_STATUS_MORE_RESULTS if needed */ PACKET_INIT_ALLOCA(fields_eof, PROT_EOF_PACKET); if (FAIL == (ret = PACKET_READ_ALLOCA(fields_eof, conn))) { mysqlnd_internal_free_result_contents(result TSRMLS_CC); efree(result); if (!stmt) { conn->current_result = NULL; } else { stmt->result = NULL; memset(stmt, 0, sizeof(MYSQLND_STMT)); stmt->state = MYSQLND_STMT_INITTED; } } else { conn->upsert_status.warning_count = fields_eof.warning_count; conn->upsert_status.server_status = fields_eof.server_status; } if (fields_eof.server_status & MYSQLND_SERVER_QUERY_NO_GOOD_INDEX_USED) { stat = STAT_BAD_INDEX_USED; } else if (fields_eof.server_status & MYSQLND_SERVER_QUERY_NO_INDEX_USED) { stat = STAT_NO_INDEX_USED; } if (stat != -1) { MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat); } PACKET_FREE_ALLOCA(fields_eof); break; } } } while (0); PACKET_FREE_ALLOCA(rset_header); return ret;}/* {{{ mysqlnd_query *//* If conn->error_info.error_no is not zero, then we had an error. Still the result from the query is PASS*/static enum_func_status_mysqlnd_query(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC){ if (PASS != mysqlnd_simple_command(conn, COM_QUERY, query, query_len, PROT_LAST /* we will handle the OK packet*/, FALSE TSRMLS_CC)) { return FAIL; } /* Here read the result set. We don't do it in simple_command because it need information from the ok packet. We will fetch it ourselves. */ return mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC);}/* }}} *//* {{{ mysqlnd_fetch_lengths_unbuffered */staticunsigned long * mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result){ return result->lengths;}/* }}} *//* {{{ mysqlnd_fetch_lengths_buffered *//* Do lazy initialization for buffered results. As PHP strings have length inside, this function makes not much sense in the context of PHP, to be called as separate function. But let's have it for completeness.*/static unsigned long * mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result){ int i; zval **previous_row; /* If: - unbuffered result - first row hs not been read - last_row has been read */ if (result->data->data_cursor == NULL || result->data->data_cursor == result->data->data || ((result->data->data_cursor - result->data->data) > result->data->row_count)) { return NULL;/* No rows or no more rows */ } previous_row = *(result->data->data_cursor - 1); for (i = 0; i < result->field_count; i++) { result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]); } return result->lengths;}/* }}} *//* {{{ _mysqlnd_fetch_lengths */PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result){ return result->m.fetch_lengths? result->m.fetch_lengths(result):NULL;}/* }}} *//* {{{ mysqlnd_fetch_row_unbuffered */static enum_func_statusmysqlnd_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC){ enum_func_status ret; zval *row = (zval *) param; unsigned int i, field_count = result->field_count; php_mysql_packet_row *row_packet = result->row_packet; unsigned long *lengths = result->lengths; if (result->unbuf->eof_reached) { /* No more rows obviously */ *fetched_anything = FALSE; return PASS; } if (result->conn->state != CONN_FETCHING_DATA) { SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); return FAIL; } /* Let the row packet fill our buffer and skip additional malloc + memcpy */ row_packet->skip_extraction = row? FALSE:TRUE; /* If we skip rows (row == NULL) we have to mysqlnd_unbuffered_free_last_data() before it. The function returns always true. */ if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { mysqlnd_unbuffered_free_last_data(result TSRMLS_CC); result->unbuf->last_row_data = row_packet->fields; result->unbuf->last_row_buffer = row_packet->row_buffer; row_packet->fields = NULL; row_packet->row_buffer = NULL; result->unbuf->row_count++; *fetched_anything = TRUE; MYSQLND_INC_CONN_STATISTIC(&result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT); if (!row_packet->skip_extraction) { HashTable *row_ht = Z_ARRVAL_P(row); for (i = 0; i < field_count; i++) { zval *data = result->unbuf->last_row_data[i]; int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); if (lengths) { lengths[i] = len; } /* Forbid ZE to free it, we will clean it */ ZVAL_ADDREF(data); if ((flags & MYSQLND_FETCH_BOTH) == MYSQLND_FETCH_BOTH) { ZVAL_ADDREF(data); } if (flags & MYSQLND_FETCH_NUM) { zend_hash_next_index_insert(row_ht, &data, sizeof(zval *), NULL); } if (flags & MYSQLND_FETCH_ASSOC) { /* zend_hash_quick_update needs length + trailing zero */ /* QQ: Error handling ? */ /* zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether the index is a numeric and convert it to it. This however means constant hashing of the column name, which is not needed as it can be precomputed. */ if (result->meta->zend_hash_keys[i].is_numeric == FALSE) {#if PHP_MAJOR_VERSION >= 6 if (UG(unicode)) { zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE, result->meta->zend_hash_keys[i].ustr, result->meta->zend_hash_keys[i].ulen + 1, result->meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); } else#endif { zend_hash_quick_update(Z_ARRVAL_P(row), result->meta->fields[i].name, result->meta->fields[i].name_length + 1, result->meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); } } else { zend_hash_index_update(Z_ARRVAL_P(row), result->meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); } } if (result->meta->fields[i].max_length < len) { result->meta->fields[i].max_length = len; } } } } else if (row_packet->eof) { /* Mark the connection as usable again */ result->unbuf->eof_reached = TRUE; result->conn->upsert_status.warning_count = row_packet->warning_count; result->conn->upsert_status.server_status = row_packet->server_status; /* result->row_packet will be cleaned when destroying the result object */ if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) { result->conn->state = CONN_NEXT_RESULT_PENDING; } else { result->conn->state = CONN_READY; } mysqlnd_unbuffered_free_last_data(result TSRMLS_CC); *fetched_anything = FALSE; } return PASS;}/* }}} *//* {{{ mysqlnd_use_result */staticMYSQLND_RES * _mysqlnd_use_result(MYSQLND * const conn TSRMLS_DC){ MYSQLND_RES *result; if (!conn->current_result) { return NULL;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -