Author: ahristov
Date: 2007-03-03 18:00:33 +0100 (Sat, 03 Mar 2007)
New Revision: 86
Modified:
trunk/ext/mysqli/mysqlnd/mysqlnd.c
trunk/ext/mysqli/mysqlnd/mysqlnd.h
trunk/ext/mysqli/mysqlnd/mysqlnd_ps.c
trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.c
trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.h
Log:
Move things around.
Workaround protocol design error for COM_STMT_SEND_LONG_DATA
(bug #26824)
Modified: trunk/ext/mysqli/mysqlnd/mysqlnd.c
===================================================================
--- trunk/ext/mysqli/mysqlnd/mysqlnd.c 2007-03-03 02:55:30 UTC (rev 85)
+++ trunk/ext/mysqli/mysqlnd/mysqlnd.c 2007-03-03 17:00:33 UTC (rev 86)
@@ -28,23 +28,27 @@
#define MYSQLND_SILENT
-extern MYSQLND_CHARSET *mysqlnd_charsets;
+/*
+ TODO :
+ - Don't bind so tightly the metadata with the result set. This means
+ that the metadata reading should not expect a MYSQLND_RES pointer, it
+ does not need it, but return a pointer to the metadata (MYSQLND_FIELD *).
+ For normal statements we will then just assign it to a member of
+ MYSQLND_RES. For PS statements, it will stay as part of the statement
+ (MYSQLND_STMT) between prepare and execute. At execute the new metadata
+ will be sent by the server, so we will discard the old one and then
+ finally attach it to the result set. This will make the code more clean,
+ as a prepared statement won't have anymore stmt->result != NULL, as it
+ is now, just to have where to store the metadata.
+ - Change mysqlnd_simple_command to accept a heap dynamic array of MYSQLND_STRING
+ terminated by a string with ptr being NULL. Thus, multi-part messages can be
+ sent to the network like writev() and this can save at least for
+ mysqlnd_stmt_send_long_data() new malloc. This change will probably make the
+ code in few other places cleaner.
+*/
-/* {{{ mysqlnd_command_to_text
- */
-static
-const char * const mysqlnd_command_to_text[COM_END] =
-{
- "SLEEP", "QUIT", "INIT_DB", "QUERY", "FIELD_LIST",
- "CREATE_DB", "DROP_DB", "REFRESH", "SHUTDOWN", "STATISTICS",
- "PROCESS_INFO", "CONNECT", "PROCESS_KILL", "DEBUG", "PING",
- "TIME", "DELAYED_INSERT", "CHANGE_USER", "BINLOG_DUMP",
- "TABLE_DUMP", "CONNECT_OUT", "REGISTER_SLAVE",
- "STMT_PREPARE", "STMT_EXECUTE", "STMT_SEND_LONG_DATA", "STMT_CLOSE",
- "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON"
-};
-/* }}} */
+extern MYSQLND_CHARSET *mysqlnd_charsets;
@@ -499,9 +503,6 @@
cmd_packet.arg_len = arg_len;
}
- /* Reset packet_no, or we will get bad handshake! */
- conn->net.packet_no = 0;
-
if (! PACKET_WRITE_ALLOCA(cmd_packet, conn)) {
if (!silent) {
php_error(E_WARNING, "Error while sending %s packet",
mysqlnd_command_to_text[command]);
Modified: trunk/ext/mysqli/mysqlnd/mysqlnd.h
===================================================================
--- trunk/ext/mysqli/mysqlnd/mysqlnd.h 2007-03-03 02:55:30 UTC (rev 85)
+++ trunk/ext/mysqli/mysqlnd/mysqlnd.h 2007-03-03 17:00:33 UTC (rev 86)
@@ -23,7 +23,20 @@
#ifndef MYSQLND_H
#define MYSQLND_H
-#define MYSQLND_INLINE_OPTIMISATIONS 1
+/* This forces inlining some accessor functions */
+#define MYSQLND_USE_OPTIMISATIONS 1
+/*
+ This force mysqlnd to do a single (or more depending on ammount of data)
+ non-blocking read() calls before sending a command to the server. Useful
+ for debugging, if previous function hasn't consumed all the output sent
+ to it - like stmt_send_long_data() error because the data was larger that
+ max_allowed_packet_size, and COM_STMT_SEND_LONG_DATA by protocol doesn't
+ use response packets, thus letting the next command to fail miserably, if
+ the connector implementor is not aware of this deficiency. Should be off
+ on production systems, if of course measured performance degradation is not
+ minimal.
+*/
+#define MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND 1
#include "portability.h"
@@ -455,6 +468,10 @@
/* sequence for simple checking of correct packets */
zend_uchar packet_no;
+#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
+ zend_uchar last_command;
+#endif
+
/* cmd buffer */
MYSQLND_CMD_BUFFER cmd_buffer;
} MYSQLND_NET;
@@ -780,7 +797,7 @@
#define mysqlnd_data_seek(result, row) (result)->m.seek_data((result), (row))
/*****************************************************************************************************/
-#if MYSQLND_INLINE_OPTIMISATIONS
+#if defined(MYSQLND_USE_OPTIMISATIONS) && MYSQLND_USE_OPTIMISATIONS == 1
/* Errors */
#define mysqlnd_errno(conn) (conn)->error_info.error_no
@@ -871,7 +888,7 @@
#define mysqlnd_stmt_errno(stmt) (stmt)->m->get_error_no((stmt))
#define mysqlnd_stmt_error(stmt) (stmt)->m->get_error_str((stmt))
#define mysqlnd_stmt_sqlstate(stmt) (stmt)->m->get_sqlstate((stmt))
-#endif /* MYSQLND_INLINE_OPTIMISATIONS */
+#endif /* MYSQLND_USE_OPTIMISATIONS */
/*****************************************************************************************************/
@@ -935,11 +952,12 @@
void mysqlnd_palloc_free_cache_reference(MYSQLND_ZVAL_PCACHE **cache);
void mysqlnd_palloc_rinit(MYSQLND_ZVAL_PCACHE * const cache);
void mysqlnd_palloc_rshutdown(MYSQLND_ZVAL_PCACHE * const cache);
+void mysqlnd_palloc_stats(const MYSQLND_ZVAL_PCACHE * const cache, zval
*return_value);
+/* There two should not be used from outside */
void * mysqlnd_palloc_get_zval(MYSQLND_ZVAL_PCACHE * const cache, zend_bool
*allocated);
void mysqlnd_palloc_zval_ptr_dtor(zval **zv, MYSQLND_ZVAL_PCACHE * const cache,
zend_bool ps,
zend_bool *copy_ctor_called);
-void mysqlnd_palloc_stats(const MYSQLND_ZVAL_PCACHE * const cache, zval
*return_value);
#endif /* MYSQLND_H */
Modified: trunk/ext/mysqli/mysqlnd/mysqlnd_ps.c
===================================================================
--- trunk/ext/mysqli/mysqlnd/mysqlnd_ps.c 2007-03-03 02:55:30 UTC (rev 85)
+++ trunk/ext/mysqli/mysqlnd/mysqlnd_ps.c 2007-03-03 17:00:33 UTC (rev 86)
@@ -831,6 +831,7 @@
MYSQLND * conn = stmt->conn;
zend_uchar *cmd_buf;
size_t packet_len;
+ enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA;
if (stmt->state < MYSQLND_STMT_PREPARED || !stmt->param_bind) {
@@ -860,16 +861,41 @@
cmd_buf = emalloc(packet_len = STMT_ID_LENGTH + 2 + length);
int4store(cmd_buf, stmt->stmt_id);
- int2store(cmd_buf + 4, param_no);
+ int2store(cmd_buf + STMT_ID_LENGTH, param_no);
+ memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length);
- ret = mysqlnd_simple_command(conn, COM_STMT_SEND_LONG_DATA,
- (char *)cmd_buf, packet_len,
- PROT_LAST /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/,
- FALSE TSRMLS_CC);
+ /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/
+ ret = mysqlnd_simple_command(conn, cmd, (char *)cmd_buf, packet_len,
+ PROT_LAST , FALSE TSRMLS_CC);
efree(cmd_buf);
if (FAIL == ret) {
stmt->error_info = conn->error_info;
}
+ /*
+ Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
+ sent response packets. According to documentation the only way to get an error
+ is to have out-of-memory on the server-side. However, that's not true, as if
+ max_allowed_packet_size is smaller than the chunk being sent to the server, the
+ latter will complain with an error message. However, normally we don't expect
+ an error message, thus we continue. When sending the next command, which expects
+ response we will read the unexpected data and error message will look weird.
+ Therefore we do non-blocking read to clean the line, if there is a need.
+ Nevertheless, there is a built-in protection when sending a command packet, that
+ checks if the line is clear - useful for debug purposes and to be switched off
+ in release builds.
+
+ Maybe we can make it automatic by checking what's the value of
max_allowed_packet_size on
+ the server and resending the data.
+ */
+ if ((packet_len = php_mysqlnd_consume_uneaten_data(conn, cmd TSRMLS_CC))) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error "
+ "while sending long data. Probably max_allowed_packet_size "
+ "is smaller than the data. You have to increase it or send "
+ "smaller chunks of data. Answer was %u bytes long.", packet_len);
+ SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
+ "Server responded to COM_STMT_SEND_LONG_DATA.");
+ ret = FAIL;
+ }
}
return ret;
@@ -931,7 +957,6 @@
/* }}} */
-
/* {{{ _mysqlnd_stmt_bind_result */
static enum_func_status
_mysqlnd_stmt_bind_result(MYSQLND_STMT * const stmt, MYSQLND_RESULT_BIND * const
result_bind)
Modified: trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.c
===================================================================
--- trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.c 2007-03-03 02:55:30 UTC (rev 85)
+++ trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.c 2007-03-03 17:00:33 UTC (rev 86)
@@ -54,6 +54,21 @@
static const char *unknown_sqlstate= "HY000";
+/* {{{ mysqlnd_command_to_text
+ */
+const char * const mysqlnd_command_to_text[COM_END] =
+{
+ "SLEEP", "QUIT", "INIT_DB", "QUERY", "FIELD_LIST",
+ "CREATE_DB", "DROP_DB", "REFRESH", "SHUTDOWN", "STATISTICS",
+ "PROCESS_INFO", "CONNECT", "PROCESS_KILL", "DEBUG", "PING",
+ "TIME", "DELAYED_INSERT", "CHANGE_USER", "BINLOG_DUMP",
+ "TABLE_DUMP", "CONNECT_OUT", "REGISTER_SLAVE",
+ "STMT_PREPARE", "STMT_EXECUTE", "STMT_SEND_LONG_DATA", "STMT_CLOSE",
+ "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON"
+};
+/* }}} */
+
+
/* {{{ php_mysqlnd_net_field_length
Get next field's length */
unsigned long php_mysqlnd_net_field_length(zend_uchar **packet)
@@ -100,13 +115,13 @@
return (mynd_ulonglong) MYSQLND_NULL_LENGTH;
case 252:
(*packet) += 3;
- return (mynd_ulonglong) uint2korr(p+1);
+ return (mynd_ulonglong) uint2korr(p + 1);
case 253:
(*packet) += 4;
- return (mynd_ulonglong) uint3korr(p+1);
+ return (mynd_ulonglong) uint3korr(p + 1);
default:
(*packet) += 9;
- return (mynd_ulonglong) uint8korr(p+1);
+ return (mynd_ulonglong) uint8korr(p + 1);
}
}
/* }}} */
@@ -116,28 +131,70 @@
zend_uchar *php_mysqlnd_net_store_length(zend_uchar *packet, mynd_ulonglong length)
{
if (length < (mynd_ulonglong) L64(251)) {
- *packet=(zend_uchar) length;
- return packet+1;
+ *packet = (zend_uchar) length;
+ return packet + 1;
}
if (length < (mynd_ulonglong) L64(65536)) {
- *packet++= 252;
+ *packet++ = 252;
int2store(packet,(uint) length);
- return packet+2;
+ return packet + 2;
}
if (length < (mynd_ulonglong) L64(16777216)) {
- *packet++= 253;
+ *packet++ = 253;
int3store(packet,(ulong) length);
- return packet+3;
+ return packet + 3;
}
- *packet++= 254;
- int8store(packet,length);
- return packet+8;
+ *packet++ = 254;
+ int8store(packet, length);
+ return packet + 8;
}
/* }}} */
+/* {{{ php_mysqlnd_consume_uneaten_data */
+size_t php_mysqlnd_consume_uneaten_data(MYSQLND * const conn, enum
php_mysqlnd_server_command cmd TSRMLS_DC)
+{
+
+ /*
+ Switch to non-blocking mode and try to consume something from
+ the line, if possible, then continue. This saves us from looking for
+ the actuall place where out-of-order packets have been sent.
+ If someone is completely sure that everything is fine, he can switch it
+ off.
+ */
+ char tmp_buf[256];
+ MYSQLND_NET *net = &conn->net;
+ size_t skipped_bytes = 0;
+ int opt = PHP_STREAM_OPTION_BLOCKING;
+ int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL
TSRMLS_CC);
+
+ if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
+ /* Do a read of 1 byte */
+ int bytes_consumed;
+
+ do {
+ skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf,
sizeof(tmp_buf)));
+ } while (bytes_consumed == sizeof(tmp_buf));
+
+ if (was_blocked) {
+ net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC);
+ }
+
+ if (bytes_consumed) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't
"
+ "consumed all the output from the server",
+ bytes_consumed, mysqlnd_command_to_text[net->last_command]);
+ }
+ }
+ net->last_command = cmd;
+
+ return skipped_bytes;
+}
+/* }}} */
+
+
/* {{{ php_mysqlnd_read_error_from_line */
static
enum_func_status php_mysqlnd_read_error_from_line(zend_uchar *buf, size_t buf_len,
@@ -168,6 +225,7 @@
/* }}} */
+/* {{{ mysqlnd_set_sock_no_delay */
int mysqlnd_set_sock_no_delay(php_stream *stream)
{
int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
@@ -180,6 +238,7 @@
return ret;
}
+/* }}} */
/* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
@@ -197,7 +256,7 @@
`count` is actually the length of the payload data. Thus :
count + MYSQLND_HEADER_SIZE = sizeof(buf) (not the pointer but the actual buffer)
*/
-size_t mysqlnd_stream_write_w_header(MYSQLND *conn, char * const buf, size_t count
TSRMLS_DC)
+size_t mysqlnd_stream_write_w_header(MYSQLND * const conn, char * const buf, size_t count
TSRMLS_DC)
{
zend_uchar safe_storage[MYSQLND_HEADER_SIZE];
MYSQLND_NET *net = &conn->net;
@@ -240,9 +299,10 @@
/* }}} */
+/* {{{ mysqlnd_stream_write_w_command */
#if USE_CORK && defined(TCP_CORK)
static
-size_t mysqlnd_stream_write_w_command(MYSQLND *conn, enum php_mysqlnd_server_command
command,
+size_t mysqlnd_stream_write_w_command(MYSQLND * const conn, enum
php_mysqlnd_server_command command,
const char * const buf, size_t count TSRMLS_DC)
{
zend_uchar safe_storage[MYSQLND_HEADER_SIZE + 1];
@@ -305,16 +365,17 @@
return ret;
}
#endif
+/* }}} */
/* {{{ mysqlnd_read_header */
static enum_func_status
mysqlnd_read_header(MYSQLND *conn, mysqlnd_packet_header *header TSRMLS_DC)
{
+ MYSQLND_NET *net = &conn->net;
char buffer[MYSQLND_HEADER_SIZE];
char *p = buffer;
int to_read = MYSQLND_HEADER_SIZE, ret;
- MYSQLND_NET *net = &conn->net;
do {
if (!(ret= php_stream_read(net->stream, p, to_read))) {
@@ -475,7 +536,7 @@
/* {{{ php_mysqlnd_crypt */
static
-void php_mysqlnd_crypt(unsigned char *buffer, const unsigned char *s1, const unsigned
char *s2, size_t len)
+void php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2,
size_t len)
{
const unsigned char *s1_end= s1 + len;
while (s1 < s1_end) {
@@ -486,8 +547,7 @@
/* {{{ php_mysqlnd_scramble */
-void php_mysqlnd_scramble(unsigned char *buffer, const unsigned char *scramble,
- const unsigned char *password)
+void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble,
const zend_uchar * const password)
{
PHP_SHA1_CTX context;
unsigned char sha1[SHA1_MAX_LENGTH];
@@ -651,7 +711,6 @@
/* }}} */
-
/* {{{ php_mysqlnd_eof_read */
static enum_func_status
php_mysqlnd_eof_read(void *_packet, MYSQLND *conn TSRMLS_DC)
@@ -693,6 +752,16 @@
php_mysql_packet_command *packet= (php_mysql_packet_command *) _packet;
MYSQLND_NET *net = &conn->net;
+ /*
+ Reset packet_no, or we will get bad handshake!
+ Every command starts a new TX and packet numbers are reset to 0.
+ */
+ net->packet_no = 0;
+
+#if MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
+ php_mysqlnd_consume_uneaten_data(conn, packet->command TSRMLS_CC);
+#endif
+
if (!packet->argument || !packet->arg_len) {
char buffer[MYSQLND_HEADER_SIZE + 1];
@@ -704,7 +773,6 @@
#else
size_t tmp_len = packet->arg_len + 1 + MYSQLND_HEADER_SIZE, ret;
zend_uchar *tmp, *p;
-
tmp = (tmp_len > net->cmd_buffer.length)?
emalloc(tmp_len):net->cmd_buffer.buffer;
p = tmp + MYSQLND_HEADER_SIZE; /* skip the header */
Modified: trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.h
===================================================================
--- trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.h 2007-03-03 02:55:30 UTC (rev 85)
+++ trunk/ext/mysqli/mysqlnd/mysqlnd_wireprotocol.h 2007-03-03 17:00:33 UTC (rev 86)
@@ -107,6 +107,8 @@
COM_END
};
+extern const char * const mysqlnd_command_to_text[COM_END];
+
/* Low-level extraction functionality */
typedef struct st_mysqlnd_packet_methods {
size_t struct_size;
@@ -291,14 +293,14 @@
} php_mysql_packet_chg_user_resp;
-size_t mysqlnd_stream_write(MYSQLND *conn, char * const buf, size_t count TSRMLS_DC);
-size_t mysqlnd_stream_write_w_header(MYSQLND *conn, char * const buf, size_t count
TSRMLS_DC);
+size_t mysqlnd_stream_write(MYSQLND * const conn, char * const buf, size_t count
TSRMLS_DC);
+size_t mysqlnd_stream_write_w_header(MYSQLND * const conn, char * const buf, size_t count
TSRMLS_DC);
+size_t php_mysqlnd_consume_uneaten_data(MYSQLND * const conn, enum
php_mysqlnd_server_command cmd TSRMLS_DC);
-void php_mysqlnd_scramble(unsigned char *buffer, const unsigned char *scramble,
- const unsigned char *password);
+void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble,
const zend_uchar * const pass);
-unsigned long php_mysqlnd_net_field_length(zend_uchar **packet);
-zend_uchar *php_mysqlnd_net_store_length(zend_uchar *packet, mynd_ulonglong length);
+unsigned long php_mysqlnd_net_field_length(zend_uchar **packet);
+zend_uchar * php_mysqlnd_net_store_length(zend_uchar *packet, mynd_ulonglong length);
#endif /* MYSQLND_WIREPROTOCOL_H */
| Thread |
|---|
| • PHP mysqlnd svn commit: r86 - trunk/ext/mysqli/mysqlnd | ahristov | 3 Mar |