/* {{{ mysqlnd_stmt_execute_generate_request */ enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer) { MYSQLND_STMT_DATA * stmt = s->data; zend_uchar *p = stmt->execute_cmd_buffer.buffer, *cmd_buffer = stmt->execute_cmd_buffer.buffer; size_t cmd_buffer_length = stmt->execute_cmd_buffer.length; enum_func_status ret; DBG_ENTER("mysqlnd_stmt_execute_generate_request"); int4store(p, stmt->stmt_id); p += 4; /* flags is 4 bytes, we store just 1 */ int1store(p, (zend_uchar) stmt->flags); p++; /* Make it all zero */ int4store(p, 0); int1store(p, 1); /* and send 1 for iteration count */ p+= 4; ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length); *free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer); *request_len = (p - cmd_buffer); *request = cmd_buffer; DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"******"); DBG_RETURN(ret); }
/* {{{ mysqlnd_command::shutdown */ static enum_func_status MYSQLND_METHOD(mysqlnd_command, shutdown)(MYSQLND_CONN_DATA * const conn, const uint8_t level) { const func_mysqlnd_protocol_payload_decoder_factory__send_command send_command = conn->payload_decoder_factory->m.send_command; const func_mysqlnd_protocol_payload_decoder_factory__send_command_handle_response send_command_handle_response = conn->payload_decoder_factory->m.send_command_handle_response; zend_uchar bits[1]; enum_func_status ret = FAIL; DBG_ENTER("mysqlnd_command::shutdown"); int1store(bits, level); ret = send_command(conn->payload_decoder_factory, COM_SHUTDOWN, bits, 1, FALSE, &conn->state, conn->error_info, conn->upsert_status, conn->stats, conn->m->send_close, conn); if (PASS == ret) { ret = send_command_handle_response(conn->payload_decoder_factory, PROT_OK_PACKET, FALSE, COM_SHUTDOWN, TRUE, conn->error_info, conn->upsert_status, &conn->last_message); } DBG_RETURN(ret); }
/* {{{ mysqlnd_com_shutdown_run */ enum_func_status mysqlnd_com_shutdown_run(void *cmd) { struct st_mysqlnd_protocol_com_shutdown_command * command = (struct st_mysqlnd_protocol_com_shutdown_command *) cmd; zend_uchar bits[1]; enum_func_status ret = FAIL; MYSQLND_CONN_DATA * conn = command->context.conn; func_mysqlnd_protocol_payload_decoder_factory__send_command send_command = conn->payload_decoder_factory->m.send_command; func_mysqlnd_protocol_payload_decoder_factory__send_command_handle_response send_command_handle_response = conn->payload_decoder_factory->m.send_command_handle_response; DBG_ENTER("mysqlnd_com_shutdown_run"); int1store(bits, command->context.level); ret = send_command(conn->payload_decoder_factory, COM_SHUTDOWN, bits, 1, FALSE, &conn->state, conn->error_info, conn->upsert_status, conn->stats, conn->m->send_close, conn); if (PASS == ret) { ret = send_command_handle_response(conn->payload_decoder_factory, PROT_OK_PACKET, FALSE, COM_SHUTDOWN, TRUE, conn->error_info, conn->upsert_status, &conn->last_message, conn->persistent); } DBG_RETURN(ret); }
static void convert_froma_string(MYSQL_BIND *r_param, char *buffer, size_t len) { int error= 0; switch (r_param->buffer_type) { case MYSQL_TYPE_TINY: { longlong val= my_atoll(buffer, buffer + len, &error); *r_param->error= error ? 1 : r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX8) : NUMERIC_TRUNCATION(val, INT_MIN8, INT_MAX8) || error > 0; int1store(r_param->buffer, (uchar) val); r_param->buffer_length= sizeof(uchar); } break; case MYSQL_TYPE_YEAR: case MYSQL_TYPE_SHORT: { longlong val= my_atoll(buffer, buffer + len, &error); *r_param->error= error ? 1 : r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX16) : NUMERIC_TRUNCATION(val, INT_MIN16, INT_MAX16) || error > 0; shortstore(r_param->buffer, (short)val); r_param->buffer_length= sizeof(short); } break; case MYSQL_TYPE_LONG: { longlong val= my_atoll(buffer, buffer + len, &error); *r_param->error=error ? 1 : r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX32) : NUMERIC_TRUNCATION(val, INT_MIN32, INT_MAX32) || error > 0; longstore(r_param->buffer, (int32)val); r_param->buffer_length= sizeof(uint32); } break; case MYSQL_TYPE_LONGLONG: { longlong val= my_atoll(buffer, buffer + len, &error); *r_param->error= error > 0; /* no need to check for truncation */ longlongstore(r_param->buffer, val); r_param->buffer_length= sizeof(longlong); } break; case MYSQL_TYPE_DOUBLE: { double val= my_atod(buffer, buffer + len, &error); *r_param->error= error > 0; /* no need to check for truncation */ float8store(r_param->buffer, val); r_param->buffer_length= sizeof(double); } break; case MYSQL_TYPE_FLOAT: { float val= (float)my_atod(buffer, buffer + len, &error); *r_param->error= error > 0; /* no need to check for truncation */ float4store(r_param->buffer, val); r_param->buffer_length= sizeof(float); } break; case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATE: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_TIMESTAMP: { MYSQL_TIME *tm= (MYSQL_TIME *)r_param->buffer; str_to_TIME(buffer, len, tm); break; } break; case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_NEWDECIMAL: default: { char *start= buffer + r_param->offset; /* stmt_fetch_column sets offset */ char *end= buffer + len; size_t copylen= 0; if (start < end) { copylen= end - start; if (r_param->buffer_length) memcpy(r_param->buffer, start, MIN(copylen, r_param->buffer_length)); } if (copylen < r_param->buffer_length) ((char *)r_param->buffer)[copylen]= '\0'; *r_param->error= (copylen > r_param->buffer_length); *r_param->length= (ulong)len; } break; } }
/* {{{ mysqlnd_stmt_execute_store_params */ static enum_func_status mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len ) { MYSQLND_STMT_DATA * stmt = s->data; zend_uchar * provided_buffer = *buf; size_t data_size = 0; zval *copies = NULL;/* if there are different types */ enum_func_status ret = FAIL; int resend_types_next_time = 0; size_t null_byte_offset; DBG_ENTER("mysqlnd_stmt_execute_store_params"); { unsigned int null_count = (stmt->param_count + 7) / 8; if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, null_count)) { SET_OOM_ERROR(*stmt->error_info); goto end; } /* put `null` bytes */ null_byte_offset = *p - *buf; memset(*p, 0, null_count); *p += null_count; } /* 1. Store type information */ /* check if need to send the types even if stmt->send_types_to_server is 0. This is because if we send "i" (42) then the type will be int and the server will expect int. However, if next time we try to send > LONG_MAX, the conversion to string will send a string and the server won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values occur, and force resend for the next execution. */ if (FAIL == mysqlnd_stmt_execute_prepare_param_types(stmt, &copies, &resend_types_next_time)) { goto end; } int1store(*p, stmt->send_types_to_server); (*p)++; if (stmt->send_types_to_server) { if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, stmt->param_count * 2)) { SET_OOM_ERROR(*stmt->error_info); goto end; } mysqlnd_stmt_execute_store_types(stmt, copies, p); } stmt->send_types_to_server = resend_types_next_time; /* 2. Store data */ /* 2.1 Calculate how much space we need */ if (FAIL == mysqlnd_stmt_execute_calculate_param_values_size(stmt, &copies, &data_size)) { goto end; } /* 2.2 Enlarge the buffer, if needed */ if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, data_size)) { SET_OOM_ERROR(*stmt->error_info); goto end; } /* 2.3 Store the actual data */ mysqlnd_stmt_execute_store_param_values(stmt, copies, *buf, p, null_byte_offset); ret = PASS; end: mysqlnd_stmt_free_copies(stmt, copies); DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"******"); DBG_RETURN(ret); }
/* {{{ mysqlnd_stmt_execute_store_params */ static enum_func_status mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len, unsigned int null_byte_offset TSRMLS_DC) { MYSQLND_STMT_DATA * stmt = s->data; unsigned int i = 0; zend_uchar * provided_buffer = *buf; size_t left = (*buf_len - (*p - *buf)); size_t data_size = 0; zval **copies = NULL;/* if there are different types */ enum_func_status ret = FAIL; int resend_types_next_time = 0; DBG_ENTER("mysqlnd_stmt_execute_store_params"); /* 1. Store type information */ /* check if need to send the types even if stmt->send_types_to_server is 0. This is because if we send "i" (42) then the type will be int and the server will expect int. However, if next time we try to send > LONG_MAX, the conversion to string will send a string and the server won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values occur, and force resend for the next execution. */ for (i = 0; i < stmt->param_count; i++) { if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (stmt->param_bind[i].type == MYSQL_TYPE_LONG || stmt->param_bind[i].type == MYSQL_TYPE_LONGLONG)) { /* always copy the var, because we do many conversions */ if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG && PASS != mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(stmt->error_info); goto end; } /* if it doesn't fit in a long send it as a string. Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX */ if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) { zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; convert_to_double_ex(&tmp_data); if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { stmt->send_types_to_server = resend_types_next_time = 1; } } } } int1store(*p, stmt->send_types_to_server); (*p)++; if (stmt->send_types_to_server) { /* 2 bytes per type, and leave 20 bytes for future use */ if (left < ((stmt->param_count * 2) + 20)) { unsigned int offset = *p - *buf; zend_uchar *tmp_buf; *buf_len = offset + stmt->param_count * 2 + 20; tmp_buf = mnd_emalloc(*buf_len); if (!tmp_buf) { SET_OOM_ERROR(stmt->error_info); goto end; } memcpy(tmp_buf, *buf, offset); *buf = tmp_buf; /* Update our pos pointer */ *p = *buf + offset; } for (i = 0; i < stmt->param_count; i++) { short current_type = stmt->param_bind[i].type; /* our types are not unsigned */ #if SIZEOF_LONG==8 if (current_type == MYSQL_TYPE_LONG) { current_type = MYSQL_TYPE_LONGLONG; } #endif if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) { /* if it doesn't fit in a long send it as a string. Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX */ if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) { zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; convert_to_double_ex(&tmp_data); if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { convert_to_string_ex(&tmp_data); current_type = MYSQL_TYPE_VAR_STRING; /* don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING we force convert_to_long_ex in all cases, thus the type will be right in the next switch. if the type is however not long, then we will do a goto in the next switch. We want to preserve the original bind type given by the user. Thus, we do these hacks. */ } else { convert_to_long_ex(&tmp_data); } } } int2store(*p, current_type); *p+= 2; } } stmt->send_types_to_server = resend_types_next_time; /* 2. Store data */ /* 2.1 Calculate how much space we need */ for (i = 0; i < stmt->param_count; i++) { unsigned int j; zval *the_var = stmt->param_bind[i].zv; if (!the_var || (stmt->param_bind[i].type != MYSQL_TYPE_LONG_BLOB && Z_TYPE_P(the_var) == IS_NULL)) { continue; } for (j = i + 1; j < stmt->param_count; j++) { if (stmt->param_bind[j].zv == the_var) { /* Double binding of the same zval, make a copy */ if (!copies || !copies[i]) { if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(stmt->error_info); goto end; } } break; } } switch (stmt->param_bind[i].type) { case MYSQL_TYPE_DOUBLE: data_size += 8; if (Z_TYPE_P(the_var) != IS_DOUBLE) { if (!copies || !copies[i]) { if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(stmt->error_info); goto end; } } } break; case MYSQL_TYPE_LONGLONG: { zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; if (Z_TYPE_P(tmp_data) == IS_STRING) { goto use_string; } convert_to_long_ex(&tmp_data); } data_size += 8; break; case MYSQL_TYPE_LONG: { zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; if (Z_TYPE_P(tmp_data) == IS_STRING) { goto use_string; } convert_to_long_ex(&tmp_data); } data_size += 4; break; case MYSQL_TYPE_LONG_BLOB: if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) { /* User hasn't sent anything, we will send empty string. Empty string has length of 0, encoded in 1 byte. No real data will follows after it. */ data_size++; } break; case MYSQL_TYPE_VAR_STRING: use_string: data_size += 8; /* max 8 bytes for size */ #if MYSQLND_UNICODE if (Z_TYPE_P(the_var) != IS_STRING || Z_TYPE_P(the_var) == IS_UNICODE) #else if (Z_TYPE_P(the_var) != IS_STRING) #endif { if (!copies || !copies[i]) { if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(stmt->error_info); goto end; } } the_var = copies[i]; #if MYSQLND_UNICODE if (Z_TYPE_P(the_var) == IS_UNICODE) { zval_unicode_to_string_ex(the_var, UG(utf8_conv) TSRMLS_CC); } #endif } convert_to_string_ex(&the_var); data_size += Z_STRLEN_P(the_var); break; } } /* 2.2 Enlarge the buffer, if needed */ left = (*buf_len - (*p - *buf)); if (left < data_size) { unsigned int offset = *p - *buf; zend_uchar *tmp_buf; *buf_len = offset + data_size + 10; /* Allocate + 10 for safety */ tmp_buf = mnd_emalloc(*buf_len); if (!tmp_buf) { SET_OOM_ERROR(stmt->error_info); goto end; } memcpy(tmp_buf, *buf, offset); /* When too many columns the buffer provided to the function might not be sufficient. In this case new buffer has been allocated above. When we allocate a buffer and then allocate a bigger one here, we should free the first one. */ if (*buf != provided_buffer) { mnd_efree(*buf); } *buf = tmp_buf; /* Update our pos pointer */ *p = *buf + offset; } /* 2.3 Store the actual data */ for (i = 0; i < stmt->param_count; i++) { zval *data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; /* Handle long data */ if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) { (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); } else { switch (stmt->param_bind[i].type) { case MYSQL_TYPE_DOUBLE: convert_to_double_ex(&data); float8store(*p, Z_DVAL_P(data)); (*p) += 8; break; case MYSQL_TYPE_LONGLONG: if (Z_TYPE_P(data) == IS_STRING) { goto send_string; } /* data has alreade been converted to long */ int8store(*p, Z_LVAL_P(data)); (*p) += 8; break; case MYSQL_TYPE_LONG: if (Z_TYPE_P(data) == IS_STRING) { goto send_string; } /* data has alreade been converted to long */ int4store(*p, Z_LVAL_P(data)); (*p) += 4; break; case MYSQL_TYPE_LONG_BLOB: if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; } else { /* send_long_data() not called, send empty string */ *p = php_mysqlnd_net_store_length(*p, 0); } break; case MYSQL_TYPE_VAR_STRING: send_string: { unsigned int len = Z_STRLEN_P(data); /* to is after p. The latter hasn't been moved */ *p = php_mysqlnd_net_store_length(*p, len); memcpy(*p, Z_STRVAL_P(data), len); (*p) += len; } break; default: /* Won't happen, but set to NULL */ (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); break; } } } ret = PASS; end: if (copies) { for (i = 0; i < stmt->param_count; i++) { if (copies[i]) { zval_ptr_dtor(&copies[i]); } } mnd_efree(copies); } DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"******"); DBG_RETURN(ret); }
/* IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!! This is done for performance reasons in the caller of this function. Otherwise we will have to do send two TCP packets, or do new alloc and memcpy. Neither are quick, thus the clients of this function are obligated to do what they are asked for. `count` is actually the length of the payload data. Thus : count + MYSQLND_HEADER_SIZE = sizeof(buffer) (not the pointer but the actual buffer) */ static size_t MYSQLND_METHOD(mysqlnd_net, send_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info) { zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))]; zend_uchar * safe_storage = safe_buf; size_t bytes_sent, packets_sent = 1; size_t left = count; zend_uchar * p = (zend_uchar *) buffer; zend_uchar * compress_buf = NULL; size_t to_be_sent; DBG_ENTER("mysqlnd_net::send_ex"); DBG_INF_FMT("count=" MYSQLND_SZ_T_SPEC " compression=%u", count, net->data->compressed); if (net->data->compressed == TRUE) { size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE); DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size); compress_buf = mnd_emalloc(comp_buf_size); } do { to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE); DBG_INF_FMT("to_be_sent=%u", to_be_sent); DBG_INF_FMT("packets_sent=%u", packets_sent); DBG_INF_FMT("compressed_envelope_packet_no=%u", net->compressed_envelope_packet_no); DBG_INF_FMT("packet_no=%u", net->packet_no); #ifdef MYSQLND_COMPRESSION_ENABLED if (net->data->compressed == TRUE) { /* here we need to compress the data and then write it, first comes the compressed header */ size_t tmp_complen = to_be_sent; size_t payload_size; zend_uchar * uncompressed_payload = p; /* should include the header */ STORE_HEADER_SIZE(safe_storage, uncompressed_payload); int3store(uncompressed_payload, to_be_sent); int1store(uncompressed_payload + 3, net->packet_no); if (PASS == net->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE)) { int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE); payload_size = tmp_complen; } else { int3store(compress_buf + MYSQLND_HEADER_SIZE, 0); memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE); payload_size = to_be_sent + MYSQLND_HEADER_SIZE; } RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage); int3store(compress_buf, payload_size); int1store(compress_buf + 3, net->packet_no); DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE); bytes_sent = net->data->m.network_write_ex(net, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info); net->compressed_envelope_packet_no++; #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY if (res == Z_OK) { size_t decompressed_size = left + MYSQLND_HEADER_SIZE; zend_uchar * decompressed_data = mnd_malloc(decompressed_size); int error = net->data->m.decode(decompressed_data, decompressed_size, compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size); if (error == Z_OK) { int i; DBG_INF("success decompressing"); for (i = 0 ; i < decompressed_size; i++) { if (i && (i % 30 == 0)) { printf("\n\t\t"); } printf("%.2X ", (int)*((char*)&(decompressed_data[i]))); DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i]))); } } else { DBG_INF("error decompressing"); } mnd_free(decompressed_data); } #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */ } else #endif /* MYSQLND_COMPRESSION_ENABLED */ { DBG_INF("no compression"); STORE_HEADER_SIZE(safe_storage, p); int3store(p, to_be_sent); int1store(p + 3, net->packet_no); bytes_sent = net->data->m.network_write_ex(net, p, to_be_sent + MYSQLND_HEADER_SIZE, conn_stats, error_info); RESTORE_HEADER_SIZE(p, safe_storage); net->compressed_envelope_packet_no++; } net->packet_no++; p += to_be_sent; left -= to_be_sent; packets_sent++; /* if left is 0 then there is nothing more to send, but if the last packet was exactly with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty packet will be sent and this loop will end. */ } while (bytes_sent && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE)); DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no); MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats, STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE, STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE, STAT_PACKETS_SENT, packets_sent); if (compress_buf) { mnd_efree(compress_buf); } /* Even for zero size payload we have to send a packet */ if (!bytes_sent) { DBG_ERR_FMT("Can't %u send bytes", count); SET_CLIENT_ERROR(*error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); } DBG_RETURN(bytes_sent); }