diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:24:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:24:36 +0000 |
commit | 06eaf7232e9a920468c0f8d74dcf2fe8b555501c (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /sql/protocol.cc | |
parent | Initial commit. (diff) | |
download | mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.tar.xz mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/protocol.cc')
-rw-r--r-- | sql/protocol.cc | 1991 |
1 files changed, 1991 insertions, 0 deletions
diff --git a/sql/protocol.cc b/sql/protocol.cc new file mode 100644 index 00000000..6667129d --- /dev/null +++ b/sql/protocol.cc @@ -0,0 +1,1991 @@ +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2008, 2020, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +/** + @file + + Low level functions for storing data to be send to the MySQL client. + The actual communction is handled by the net_xxx functions in net_serv.cc +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "mariadb.h" +#include "sql_priv.h" +#include "protocol.h" +#include "sql_class.h" // THD +#include <stdarg.h> + +static const unsigned int PACKET_BUFFER_EXTRA_ALLOC= 1024; +#ifndef EMBEDDED_LIBRARY +static bool write_eof_packet(THD *, NET *, uint, uint); +#endif + +CHARSET_INFO *Protocol::character_set_results() const +{ + return thd->variables.character_set_results; +} + + +#ifndef EMBEDDED_LIBRARY +bool Protocol::net_store_data(const uchar *from, size_t length) +#else +bool Protocol_binary::net_store_data(const uchar *from, size_t length) +#endif +{ + ulong packet_length=packet->length(); + /* + The +9 comes from that strings of length longer than 16M require + 9 bytes to be stored (see net_store_length). + */ + if (packet_length+9+length > packet->alloced_length() && + packet->realloc(packet_length+9+length)) + return 1; + uchar *to= net_store_length((uchar*) packet->ptr()+packet_length, length); + if (length) + memcpy(to,from,length); + packet->length((uint) (to+length-(uchar*) packet->ptr())); + return 0; +} + + +/* + net_store_data_cs() - extended version with character set conversion. + + It is optimized for short strings whose length after + conversion is garanteed to be less than 251, which accupies + exactly one byte to store length. It allows not to use + the "convert" member as a temporary buffer, conversion + is done directly to the "packet" member. + The limit 251 is good enough to optimize send_result_set_metadata() + because column, table, database names fit into this limit. +*/ + +#ifndef EMBEDDED_LIBRARY +bool Protocol::net_store_data_cs(const uchar *from, size_t length, + CHARSET_INFO *from_cs, CHARSET_INFO *to_cs) +#else +bool Protocol_binary::net_store_data_cs(const uchar *from, size_t length, + CHARSET_INFO *from_cs, CHARSET_INFO *to_cs) +#endif +{ + uint dummy_errors; + /* Calculate maxumum possible result length */ + size_t conv_length= to_cs->mbmaxlen * length / from_cs->mbminlen; + + if (conv_length > 250) + { + /* + For strings with conv_length greater than 250 bytes + we don't know how many bytes we will need to store length: one or two, + because we don't know result length until conversion is done. + For example, when converting from utf8 (mbmaxlen=3) to latin1, + conv_length=300 means that the result length can vary between 100 to 300. + length=100 needs one byte, length=300 needs to bytes. + + Thus conversion directly to "packet" is not worthy. + Let's use "convert" as a temporary buffer. + */ + return (convert->copy((const char*) from, length, from_cs, + to_cs, &dummy_errors) || + net_store_data((const uchar*) convert->ptr(), convert->length())); + } + + size_t packet_length= packet->length(); + size_t new_length= packet_length + conv_length + 1; + + if (new_length > packet->alloced_length() && packet->realloc(new_length)) + return 1; + + char *length_pos= (char*) packet->ptr() + packet_length; + char *to= length_pos + 1; + + to+= copy_and_convert(to, conv_length, to_cs, + (const char*) from, length, from_cs, &dummy_errors); + + net_store_length((uchar*) length_pos, to - length_pos - 1); + packet->length((uint) (to - packet->ptr())); + return 0; +} + + +/** + Send a error string to client. + + Design note: + + net_printf_error and net_send_error are low-level functions + that shall be used only when a new connection is being + established or at server startup. + + For SIGNAL/RESIGNAL and GET DIAGNOSTICS functionality it's + critical that every error that can be intercepted is issued in one + place only, my_message_sql. + + @param thd Thread handler + @param sql_errno The error code to send + @param err A pointer to the error message + + @return + @retval FALSE The message was sent to the client + @retval TRUE An error occurred and the message wasn't sent properly +*/ + +bool Protocol::net_send_error(THD *thd, uint sql_errno, const char *err, + const char* sqlstate) +{ + bool error; + DBUG_ENTER("Protocol::net_send_error"); + + DBUG_ASSERT(!thd->spcont); + DBUG_ASSERT(sql_errno); + DBUG_ASSERT(err); + + DBUG_PRINT("enter",("sql_errno: %d err: %s", sql_errno, err)); + + if (sqlstate == NULL) + sqlstate= mysql_errno_to_sqlstate(sql_errno); + + /* + It's one case when we can push an error even though there + is an OK or EOF already. + */ + thd->get_stmt_da()->set_overwrite_status(true); + + /* Abort multi-result sets */ + thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS; + + error= net_send_error_packet(thd, sql_errno, err, sqlstate); + + thd->get_stmt_da()->set_overwrite_status(false); + + DBUG_RETURN(error); +} + +/** + Return ok to the client. + + The ok packet has the following structure: + + - 0 : Marker (1 byte) + - affected_rows : Stored in 1-9 bytes + - id : Stored in 1-9 bytes + - server_status : Copy of thd->server_status; Can be used by client + to check if we are inside an transaction. + New in 4.0 protocol + - warning_count : Stored in 2 bytes; New in 4.1 protocol + - message : Stored as packed length (1-9 bytes) + message. + Is not stored if no message. + + @param thd Thread handler + @param server_status The server status + @param statement_warn_count Total number of warnings + @param affected_rows Number of rows changed by statement + @param id Auto_increment id for first row (if used) + @param message Message to send to the client (Used by mysql_status) + @param is_eof this called instead of old EOF packet + + @return + @retval FALSE The message was successfully sent + @retval TRUE An error occurred and the messages wasn't sent properly + +*/ + +#ifndef EMBEDDED_LIBRARY +bool +Protocol::net_send_ok(THD *thd, + uint server_status, uint statement_warn_count, + ulonglong affected_rows, ulonglong id, + const char *message, bool is_eof) +{ + NET *net= &thd->net; + StringBuffer<MYSQL_ERRMSG_SIZE + 10> store; + + bool error= FALSE; + DBUG_ENTER("Protocol::net_send_ok"); + + if (! net->vio) // hack for re-parsing queries + { + DBUG_PRINT("info", ("vio present: NO")); + DBUG_RETURN(FALSE); + } + + /* + OK send instead of EOF still require 0xFE header, but OK packet content. + */ + if (is_eof) + { + DBUG_ASSERT(thd->client_capabilities & CLIENT_DEPRECATE_EOF); + store.q_append((char)254); + } + else + store.q_append('\0'); + + /* affected rows */ + store.q_net_store_length(affected_rows); + + /* last insert id */ + store.q_net_store_length(id); + + /* if client has not session tracking capability, don't send state change flag*/ + if (!(thd->client_capabilities & CLIENT_SESSION_TRACK)) { + server_status &= ~SERVER_SESSION_STATE_CHANGED; + } + + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + DBUG_PRINT("info", + ("affected_rows: %lu id: %lu status: %u warning_count: %u", + (ulong) affected_rows, + (ulong) id, + (uint) (server_status & 0xffff), + (uint) statement_warn_count)); + store.q_append2b(server_status); + + /* We can only return up to 65535 warnings in two bytes */ + uint tmp= MY_MIN(statement_warn_count, 65535); + store.q_append2b(tmp); + } + else if (net->return_status) // For 4.0 protocol + { + store.q_append2b(server_status); + } + thd->get_stmt_da()->set_overwrite_status(true); + + if ((server_status & SERVER_SESSION_STATE_CHANGED) || (message && message[0])) + { + DBUG_ASSERT(safe_strlen(message) <= MYSQL_ERRMSG_SIZE); + store.q_net_store_data((uchar*) safe_str(message), safe_strlen(message)); + } + + if (unlikely(server_status & SERVER_SESSION_STATE_CHANGED)) + { + store.set_charset(thd->variables.collation_database); + thd->session_tracker.store(thd, &store); + thd->server_status&= ~SERVER_SESSION_STATE_CHANGED; + } + + DBUG_ASSERT(store.length() <= MAX_PACKET_LENGTH); + + error= my_net_write(net, (const unsigned char*)store.ptr(), store.length()); + if (likely(!error)) + error= net_flush(net); + + thd->get_stmt_da()->set_overwrite_status(false); + DBUG_PRINT("info", ("OK sent, so no more error sending allowed")); + + DBUG_RETURN(error); +} + + +static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */ + +/** + Send eof (= end of result set) to the client. + + The eof packet has the following structure: + + - 254 : Marker (1 byte) + - warning_count : Stored in 2 bytes; New in 4.1 protocol + - status_flag : Stored in 2 bytes; + For flags like SERVER_MORE_RESULTS_EXISTS. + + Note that the warning count will not be sent if 'no_flush' is set as + we don't want to report the warning count until all data is sent to the + client. + + @param thd Thread handler + @param server_status The server status + @param statement_warn_count Total number of warnings + + @return + @retval FALSE The message was successfully sent + @retval TRUE An error occurred and the message wasn't sent properly +*/ + +bool +Protocol::net_send_eof(THD *thd, uint server_status, uint statement_warn_count) +{ + NET *net= &thd->net; + bool error= FALSE; + DBUG_ENTER("Protocol::net_send_eof"); + DBUG_PRINT("enter", ("status: %u warn_count: %u", + server_status, statement_warn_count)); + + /* + Check if client understand new format packets (OK instead of EOF) + + Normally end of statement reply is signaled by OK packet, but in case + of binlog dump request an EOF packet is sent instead. Also, old clients + expect EOF packet instead of OK + */ + if ((thd->client_capabilities & CLIENT_DEPRECATE_EOF) && + (thd->get_command() != COM_BINLOG_DUMP )) + { + error= net_send_ok(thd, server_status, statement_warn_count, 0, 0, NULL, + true); + DBUG_RETURN(error); + } + + /* Set to TRUE if no active vio, to work well in case of --init-file */ + if (net->vio != 0) + { + thd->get_stmt_da()->set_overwrite_status(true); + error= write_eof_packet(thd, net, server_status, statement_warn_count); + if (likely(!error)) + error= net_flush(net); + thd->get_stmt_da()->set_overwrite_status(false); + DBUG_PRINT("info", ("EOF sent, so no more error sending allowed")); + } + DBUG_RETURN(error); +} + + +/** + Format EOF packet according to the current protocol and + write it to the network output buffer. + + @param thd The thread handler + @param net The network handler + @param server_status The server status + @param statement_warn_count The number of warnings + + + @return + @retval FALSE The message was sent successfully + @retval TRUE An error occurred and the messages wasn't sent properly +*/ + +static bool write_eof_packet(THD *thd, NET *net, + uint server_status, + uint statement_warn_count) +{ + bool error; + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + uchar buff[5]; + /* + Don't send warn count during SP execution, as the warn_list + is cleared between substatements, and mysqltest gets confused + */ + uint tmp= MY_MIN(statement_warn_count, 65535); + buff[0]= 254; + int2store(buff+1, tmp); + /* + The following test should never be true, but it's better to do it + because if 'is_fatal_error' is set the server is not going to execute + other queries (see the if test in dispatch_command / COM_QUERY) + */ + if (unlikely(thd->is_fatal_error)) + server_status&= ~SERVER_MORE_RESULTS_EXISTS; + int2store(buff + 3, server_status); + error= my_net_write(net, buff, 5); + } + else + error= my_net_write(net, eof_buff, 1); + + return error; +} + +/** + @param thd Thread handler + @param sql_errno The error code to send + @param err A pointer to the error message + + @return + @retval FALSE The message was successfully sent + @retval TRUE An error occurred and the messages wasn't sent properly +*/ + +bool Protocol::net_send_error_packet(THD *thd, uint sql_errno, const char *err, + const char* sqlstate) + +{ + NET *net= &thd->net; + uint length; + /* + buff[]: sql_errno:2 + ('#':1 + SQLSTATE_LENGTH:5) + MYSQL_ERRMSG_SIZE:512 + */ + uint error; + char converted_err[MYSQL_ERRMSG_SIZE]; + char buff[2+1+SQLSTATE_LENGTH+MYSQL_ERRMSG_SIZE], *pos; + my_bool ret; + uint8 save_compress; + DBUG_ENTER("Protocol::send_error_packet"); + + if (net->vio == 0) + { + if (thd->bootstrap) + { + /* In bootstrap it's ok to print on stderr */ + fprintf(stderr,"ERROR: %d %s\n",sql_errno,err); + } + DBUG_RETURN(FALSE); + } + + int2store(buff,sql_errno); + pos= buff+2; + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + /* The first # is to make the protocol backward compatible */ + buff[2]= '#'; + pos= strmov(buff+3, sqlstate); + } + + convert_error_message(converted_err, sizeof(converted_err), + thd->variables.character_set_results, + err, strlen(err), system_charset_info, &error); + /* Converted error message is always null-terminated. */ + length= (uint) (strmake(pos, converted_err, MYSQL_ERRMSG_SIZE - 1) - buff); + + /* + Ensure that errors are not compressed. This is to ensure we can + detect out of bands error messages in the client + */ + if ((save_compress= net->compress)) + net->compress= 2; + + /* + Sometimes, we send errors "out-of-band", e.g ER_CONNECTION_KILLED + on an idle connection. The current protocol "sequence number" is 0, + however some client drivers would however always expect packets + coming from server to have seq_no > 0, due to missing awareness + of "out-of-band" operations. Make these clients happy. + */ + if (!net->pkt_nr && + (sql_errno == ER_CONNECTION_KILLED || sql_errno == ER_SERVER_SHUTDOWN || + sql_errno == ER_QUERY_INTERRUPTED)) + { + net->pkt_nr= 1; + } + + ret= net_write_command(net,(uchar) 255, (uchar*) "", 0, (uchar*) buff, + length); + net->compress= save_compress; + DBUG_RETURN(ret); +} + +#endif /* EMBEDDED_LIBRARY */ + +/** + Faster net_store_length when we know that length is less than 65536. + We keep a separate version for that range because it's widely used in + libmysql. + + uint is used as agrument type because of MySQL type conventions: + - uint for 0..65536 + - ulong for 0..4294967296 + - ulonglong for bigger numbers. +*/ + +static uchar *net_store_length_fast(uchar *packet, size_t length) +{ + DBUG_ASSERT(length < UINT_MAX16); + if (length < 251) + { + *packet=(uchar) length; + return packet+1; + } + *packet++=252; + int2store(packet,(uint) length); + return packet+2; +} + +/** + Send the status of the current statement execution over network. + + @param thd in fact, carries two parameters, NET for the transport and + Diagnostics_area as the source of status information. + + In MySQL, there are two types of SQL statements: those that return + a result set and those that return status information only. + + If a statement returns a result set, it consists of 3 parts: + - result set meta-data + - variable number of result set rows (can be 0) + - followed and terminated by EOF or ERROR packet + + Once the client has seen the meta-data information, it always + expects an EOF or ERROR to terminate the result set. If ERROR is + received, the result set rows are normally discarded (this is up + to the client implementation, libmysql at least does discard them). + EOF, on the contrary, means "successfully evaluated the entire + result set". Since we don't know how many rows belong to a result + set until it's evaluated, EOF/ERROR is the indicator of the end + of the row stream. Note, that we can not buffer result set rows + on the server -- there may be an arbitrary number of rows. But + we do buffer the last packet (EOF/ERROR) in the Diagnostics_area and + delay sending it till the very end of execution (here), to be able to + change EOF to an ERROR if commit failed or some other error occurred + during the last cleanup steps taken after execution. + + A statement that does not return a result set doesn't send result + set meta-data either. Instead it returns one of: + - OK packet + - ERROR packet. + Similarly to the EOF/ERROR of the previous statement type, OK/ERROR + packet is "buffered" in the diagnostics area and sent to the client + in the end of statement. + + @note This method defines a template, but delegates actual + sending of data to virtual Protocol::send_{ok,eof,error}. This + allows for implementation of protocols that "intercept" ok/eof/error + messages, and store them in memory, etc, instead of sending to + the client. + + @pre The diagnostics area is assigned or disabled. It can not be empty + -- we assume that every SQL statement or COM_* command + generates OK, ERROR, or EOF status. + + @post The status information is encoded to protocol format and sent to the + client. + + @return We conventionally return void, since the only type of error + that can happen here is a NET (transport) error, and that one + will become visible when we attempt to read from the NET the + next command. + Diagnostics_area::is_sent is set for debugging purposes only. +*/ + +void Protocol::end_statement() +{ +#ifdef WITH_WSREP + /* + Commented out: This sanity check does not hold in general. + Thd->LOCK_thd_data() must be unlocked before sending response + to client, so BF abort may sneak in here. + DBUG_ASSERT(!WSREP(thd) || thd->wsrep_conflict_state() == NO_CONFLICT); + */ + + /* + sanity check, don't send end statement while replaying + */ + DBUG_ASSERT(thd->wsrep_trx().state() != wsrep::transaction::s_replaying); + if (WSREP(thd) && thd->wsrep_trx().state() == + wsrep::transaction::s_replaying) + { + WSREP_ERROR("attempting net_end_statement while replaying"); + return; + } +#endif /* WITH_WSREP */ + + DBUG_ENTER("Protocol::end_statement"); + DBUG_ASSERT(! thd->get_stmt_da()->is_sent()); + bool error= FALSE; + + /* Can not be true, but do not take chances in production. */ + if (thd->get_stmt_da()->is_sent()) + DBUG_VOID_RETURN; + + switch (thd->get_stmt_da()->status()) { + case Diagnostics_area::DA_ERROR: + /* The query failed, send error to log and abort bootstrap. */ + error= send_error(thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message(), + thd->get_stmt_da()->get_sqlstate()); + break; + case Diagnostics_area::DA_EOF: + case Diagnostics_area::DA_EOF_BULK: + error= send_eof(thd->server_status, + thd->get_stmt_da()->statement_warn_count()); + break; + case Diagnostics_area::DA_OK: + case Diagnostics_area::DA_OK_BULK: + error= send_ok(thd->server_status, + thd->get_stmt_da()->statement_warn_count(), + thd->get_stmt_da()->affected_rows(), + thd->get_stmt_da()->last_insert_id(), + thd->get_stmt_da()->message()); + break; + case Diagnostics_area::DA_DISABLED: + break; + case Diagnostics_area::DA_EMPTY: + default: + DBUG_ASSERT(0); + error= send_ok(thd->server_status, 0, 0, 0, NULL); + break; + } + if (likely(!error)) + thd->get_stmt_da()->set_is_sent(true); + DBUG_VOID_RETURN; +} + +/** + A default implementation of "OK" packet response to the client. + + Currently this implementation is re-used by both network-oriented + protocols -- the binary and text one. They do not differ + in their OK packet format, which allows for a significant simplification + on client side. +*/ + +bool Protocol::send_ok(uint server_status, uint statement_warn_count, + ulonglong affected_rows, ulonglong last_insert_id, + const char *message) +{ + DBUG_ENTER("Protocol::send_ok"); + const bool retval= + net_send_ok(thd, server_status, statement_warn_count, + affected_rows, last_insert_id, message, false); + DBUG_RETURN(retval); +} + + +/** + A default implementation of "EOF" packet response to the client. + + Binary and text protocol do not differ in their EOF packet format. +*/ + +bool Protocol::send_eof(uint server_status, uint statement_warn_count) +{ + DBUG_ENTER("Protocol::send_eof"); + bool retval= net_send_eof(thd, server_status, statement_warn_count); + DBUG_RETURN(retval); +} + + +/** + A default implementation of "ERROR" packet response to the client. + + Binary and text protocol do not differ in ERROR packet format. +*/ + +bool Protocol::send_error(uint sql_errno, const char *err_msg, + const char *sql_state) +{ + DBUG_ENTER("Protocol::send_error"); + const bool retval= net_send_error_packet(thd, sql_errno, err_msg, sql_state); + DBUG_RETURN(retval); +} + + +/** + Send a progress report to the client + + What we send is: + header (255,255,255,1) + stage, max_stage as on byte integers + percentage withing the stage as percentage*1000 + (that is, ratio*100000) as a 3 byte integer + proc_info as a string +*/ + +const uchar progress_header[2]= {(uchar) 255, (uchar) 255 }; + +void net_send_progress_packet(THD *thd) +{ + uchar buff[200], *pos; + const char *proc_info= thd->proc_info ? thd->proc_info : ""; + size_t length= strlen(proc_info); + ulonglong progress; + DBUG_ENTER("net_send_progress_packet"); + + if (unlikely(!thd->net.vio)) + DBUG_VOID_RETURN; // Socket is closed + + pos= buff; + /* + Store number of strings first. This allows us to later expand the + progress indicator if needed. + */ + *pos++= (uchar) 1; // Number of strings + *pos++= (uchar) thd->progress.stage + 1; + /* + We have the MY_MAX() here to avoid problems if max_stage is not set, + which may happen during automatic repair of table + */ + *pos++= (uchar) MY_MAX(thd->progress.max_stage, thd->progress.stage + 1); + progress= 0; + if (thd->progress.max_counter) + progress= 100000ULL * thd->progress.counter / thd->progress.max_counter; + int3store(pos, progress); // Between 0 & 100000 + pos+= 3; + pos= net_store_data(pos, (const uchar*) proc_info, + MY_MIN(length, sizeof(buff)-7)); + net_write_command(&thd->net, (uchar) 255, progress_header, + sizeof(progress_header), (uchar*) buff, + (uint) (pos - buff)); + DBUG_VOID_RETURN; +} + + +/**************************************************************************** + Functions used by the protocol functions (like net_send_ok) to store + strings and numbers in the header result packet. +****************************************************************************/ + +/* The following will only be used for short strings < 65K */ + +uchar *net_store_data(uchar *to, const uchar *from, size_t length) +{ + to=net_store_length_fast(to,length); + if (length) + memcpy(to,from,length); + return to+length; +} + +uchar *net_store_data(uchar *to,int32 from) +{ + char buff[22]; + uint length=(uint) (int10_to_str(from,buff,10)-buff); + to=net_store_length_fast(to,length); + memcpy(to,buff,length); + return to+length; +} + +uchar *net_store_data(uchar *to,longlong from) +{ + char buff[22]; + uint length=(uint) (longlong10_to_str(from,buff,10)-buff); + to=net_store_length_fast(to,length); + memcpy(to,buff,length); + return to+length; +} + + +/***************************************************************************** + Default Protocol functions +*****************************************************************************/ + +void Protocol::init(THD *thd_arg) +{ + thd=thd_arg; + packet= &thd->packet; + convert= &thd->convert_buffer; +#ifndef DBUG_OFF + field_handlers= 0; + field_pos= 0; +#endif +} + +/** + Finish the result set with EOF packet, as is expected by the client, + if there is an error evaluating the next row and a continue handler + for the error. +*/ + +void Protocol::end_partial_result_set(THD *thd_arg) +{ + net_send_eof(thd_arg, thd_arg->server_status, + 0 /* no warnings, we're inside SP */); +} + + +bool Protocol::flush() +{ +#ifndef EMBEDDED_LIBRARY + bool error; + thd->get_stmt_da()->set_overwrite_status(true); + error= net_flush(&thd->net); + thd->get_stmt_da()->set_overwrite_status(false); + return error; +#else + return 0; +#endif +} + +#ifndef EMBEDDED_LIBRARY + + +class Send_field_packed_extended_metadata: public Binary_string +{ +public: + bool append_chunk(mariadb_field_attr_t type, const LEX_CSTRING &value) + { + /* + If we eventually support many metadata chunk types and long metadata + values, we'll need to encode type and length using net_store_length() + and do corresponding changes to the unpacking code in libmariadb. + For now let's just assert that type and length fit into one byte. + */ + DBUG_ASSERT(net_length_size(type) == 1); + DBUG_ASSERT(net_length_size(value.length) == 1); + size_t nbytes= 1/*type*/ + 1/*length*/ + value.length; + if (reserve(nbytes)) + return true; + qs_append((char) (uchar) type); + qs_append((char) (uchar) value.length); + qs_append(&value); + return false; + } + bool pack(const Send_field_extended_metadata &src) + { + for (uint i= 0 ; i <= MARIADB_FIELD_ATTR_LAST; i++) + { + const LEX_CSTRING attr= src.attr(i); + if (attr.str && append_chunk((mariadb_field_attr_t) i, attr)) + return true; + } + return false; + } +}; + + +bool Protocol_text::store_field_metadata(const THD * thd, + const Send_field &field, + CHARSET_INFO *charset_for_protocol, + uint fieldnr) +{ + CHARSET_INFO *thd_charset= thd->variables.character_set_results; + char *pos; + DBUG_ASSERT(field.is_sane()); + + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + const LEX_CSTRING def= {STRING_WITH_LEN("def")}; + if (store_ident(def) || + store_ident(field.db_name) || + store_ident(field.table_name) || + store_ident(field.org_table_name) || + store_ident(field.col_name) || + store_ident(field.org_col_name)) + return true; + if (thd->client_capabilities & MARIADB_CLIENT_EXTENDED_METADATA) + { + Send_field_packed_extended_metadata metadata; + metadata.pack(field); + + /* + Don't apply character set conversion: + extended metadata is a binary encoded data. + */ + if (store_binary_string(metadata.ptr(), metadata.length())) + return true; + } + if (packet->realloc(packet->length() + 12)) + return true; + /* Store fixed length fields */ + pos= (char*) packet->end(); + *pos++= 12; // Length of packed fields + /* inject a NULL to test the client */ + DBUG_EXECUTE_IF("poison_rs_fields", pos[-1]= (char) 0xfb;); + if (charset_for_protocol == &my_charset_bin || thd_charset == NULL) + { + /* No conversion */ + uint id= charset_for_protocol->get_id(MY_COLLATION_ID_TYPE_COMPAT_100800); + DBUG_ASSERT(id <= UINT_MAX16); + int2store(pos, (uint16) id); + int4store(pos + 2, field.length); + } + else + { + /* With conversion */ + uint id= thd_charset->get_id(MY_COLLATION_ID_TYPE_COMPAT_100800); + DBUG_ASSERT(id <= UINT_MAX16); + int2store(pos, (uint16) id); + uint32 field_length= field.max_octet_length(charset_for_protocol, + thd_charset); + int4store(pos + 2, field_length); + } + pos[6]= field.type_handler()->type_code_for_protocol(); + int2store(pos + 7, field.flags); + pos[9]= (char) field.decimals; + pos[10]= 0; // For the future + pos[11]= 0; // For the future + pos+= 12; + } + else + { + if (store_ident(field.table_name) || + store_ident(field.col_name) || + packet->realloc(packet->length() + 10)) + return true; + pos= (char*) packet->end(); + pos[0]= 3; + int3store(pos + 1, field.length); + pos[4]= 1; + pos[5]= field.type_handler()->type_code_for_protocol(); + pos[6]= 3; + int2store(pos + 7, field.flags); + pos[9]= (char) field.decimals; + pos+= 10; + } + packet->length((uint) (pos - packet->ptr())); + return false; +} + + +/* + MARIADB_CLIENT_CACHE_METADATA support. + + Bulk of the code below is dedicated to detecting whether column metadata has + changed after prepare, or between executions of a prepared statement. + + For some prepared statements, metadata can't change without going through + Prepared_Statement::reprepare(), which makes detecting changes easy. + + Others, "SELECT ?" & Co, are more fragile, and sensitive to input parameters, + or user variables. Detecting metadata change for this class of PS is harder, + we calculate signature (hash value), and check whether this changes between + executions. This is a more expensive method. +*/ + + +/** + Detect whether column info can be changed without + PS repreparing. + + Such colum info is called fragile. The opposite of + fragile is. + + + @param it - Item representing column info + @return true, if columninfo is "fragile", false if it is stable + + + @todo does not work due to MDEV-23913. Currently, + everything about prepared statements is fragile. +*/ + +static bool is_fragile_columnifo(Item *it) +{ +#define MDEV_23913_FIXED 0 +#if MDEV_23913_FIXED + if (dynamic_cast<Item_param *>(it)) + return true; + + if (dynamic_cast<Item_func_user_var *>(it)) + return true; + + if (dynamic_cast <Item_sp_variable*>(it)) + return true; + + /* Check arguments of functions.*/ + auto item_args= dynamic_cast<Item_args *>(it); + if (!item_args) + return false; + auto args= item_args->arguments(); + auto arg_count= item_args->argument_count(); + for (uint i= 0; i < arg_count; i++) + { + if (is_fragile_columnifo(args[i])) + return true; + } + return false; +#else /* MDEV-23913 fixed*/ + return true; +#endif +} + + +#define INVALID_METADATA_CHECKSUM 0 + + +/** + Calculate signature for column info sent to the client as CRC32 over data, + that goes into the column info packet. + We assume that if checksum does not change, then column info was not + modified. + + @param thd THD + @param list column info + + @return CRC32 of the metadata +*/ + +static uint32 calc_metadata_hash(THD *thd, List<Item> *list) +{ + List_iterator_fast<Item> it(*list); + Item *item; + uint32 crc32_c= 0; + while ((item= it++)) + { + Send_field field(thd, item); + auto field_type= item->type_handler()->field_type(); + auto charset= item->charset_for_protocol(); + /* + The data below should contain everything that influences + content of the column info packet. + */ + LEX_CSTRING data[]= + { + field.table_name, + field.org_table_name, + field.col_name, + field.org_col_name, + field.db_name, + field.attr(MARIADB_FIELD_ATTR_DATA_TYPE_NAME), + field.attr(MARIADB_FIELD_ATTR_FORMAT_NAME), + {(const char *) &field.length, sizeof(field.length)}, + {(const char *) &field.flags, sizeof(field.flags)}, + {(const char *) &field.decimals, sizeof(field.decimals)}, + {(const char *) &charset, sizeof(charset)}, + {(const char *) &field_type, sizeof(field_type)}, + }; + for (const auto &chunk : data) + crc32_c= my_crc32c(crc32_c, chunk.str, chunk.length); + } + + if (crc32_c == INVALID_METADATA_CHECKSUM) + return 1; + return crc32_c; +} + + + +/** + Check if metadata columns have changed since last call to this + function. + + @param send_column_info_state saved state, changed if the function + return true. + @param thd THD + @param list columninfo Items + @return true,if metadata columns have changed since last call, + false otherwise +*/ + +static bool metadata_columns_changed(send_column_info_state &state, THD *thd, + List<Item> &list) +{ + if (!state.initialized) + { + state.initialized= true; + state.immutable= true; + Item *item; + List_iterator_fast<Item> it(list); + while ((item= it++)) + { + if (is_fragile_columnifo(item)) + { + state.immutable= false; + state.checksum= calc_metadata_hash(thd, &list); + break; + } + } + state.last_charset= thd->variables.character_set_client; + return true; + } + + /* + Since column info can change under our feet, we use more expensive + checksumming to check if column metadata has not changed since last time. + */ + if (!state.immutable) + { + uint32 checksum= calc_metadata_hash(thd, &list); + if (checksum != state.checksum) + { + state.checksum= checksum; + state.last_charset= thd->variables.character_set_client; + return true; + } + } + + /* + Character_set_client influences result set metadata, thus resend metadata + whenever it changes. + */ + if (state.last_charset != thd->variables.character_set_client) + { + state.last_charset= thd->variables.character_set_client; + return true; + } + + return false; +} + + +/** + Determine whether column info must be sent to the client. + Skip column info, if client supports caching, and (prepared) statement + output fields have not changed. + + @param thd THD + @param list column info + @param flags send flags. If Protocol::SEND_FORCE_COLUMN_INFO is set, + this function will return true + @return true, if column info must be sent to the client. + false otherwise +*/ + +static bool should_send_column_info(THD* thd, List<Item>* list, uint flags) +{ + if (!(thd->client_capabilities & MARIADB_CLIENT_CACHE_METADATA)) + { + /* Client does not support abbreviated metadata.*/ + return true; + } + + if (!thd->cur_stmt) + { + /* Neither COM_PREPARE nor COM_EXECUTE run.*/ + return true; + } + + if (thd->spcont) + { + /* Always sent full metadata from inside the stored procedure.*/ + return true; + } + + if (flags & Protocol::SEND_FORCE_COLUMN_INFO) + return true; + + auto &column_info_state= thd->cur_stmt->column_info_state; +#ifndef DBUG_OFF + auto cmd= thd->get_command(); +#endif + + DBUG_ASSERT(cmd == COM_STMT_EXECUTE || cmd == COM_STMT_PREPARE + || cmd == COM_STMT_BULK_EXECUTE); + DBUG_ASSERT(cmd != COM_STMT_PREPARE || !column_info_state.initialized); + + bool ret= metadata_columns_changed(column_info_state, thd, *list); + + DBUG_ASSERT(cmd != COM_STMT_PREPARE || ret); + if (!ret) + thd->status_var.skip_metadata_count++; + + return ret; +} + + +/** + Send name and type of result to client. + + Sum fields has table name empty and field_name. + + @param THD Thread data object + @param list List of items to send to client + @param flag Bit mask with the following functions: + - 1 send number of rows + - 2 send default values + - 4 don't write eof packet + + @retval + 0 ok + @retval + 1 Error (Note that in this case the error is not sent to the + client) +*/ +bool Protocol::send_result_set_metadata(List<Item> *list, uint flags) +{ + DBUG_ENTER("Protocol::send_result_set_metadata"); + + bool send_column_info= should_send_column_info(thd, list, flags); + + if (flags & SEND_NUM_ROWS) + { + /* + Packet with number of columns. + + Will also have a 1 byte column info indicator, in case + MARIADB_CLIENT_CACHE_METADATA client capability is set. + */ + uchar buff[MAX_INT_WIDTH+1]; + uchar *pos= net_store_length(buff, list->elements); + if (thd->client_capabilities & MARIADB_CLIENT_CACHE_METADATA) + *pos++= (uchar)send_column_info; + + DBUG_ASSERT(pos <= buff + sizeof(buff)); + if (my_net_write(&thd->net, buff, (size_t) (pos-buff))) + DBUG_RETURN(1); + } + + if (send_column_info) + { + List_iterator_fast<Item> it(*list); + Item *item; + Protocol_text prot(thd, thd->variables.net_buffer_length); +#ifndef DBUG_OFF + field_handlers= (const Type_handler **) thd->alloc( + sizeof(field_handlers[0]) * list->elements); +#endif + + for (uint pos= 0; (item= it++); pos++) + { + prot.prepare_for_resend(); + if (prot.store_item_metadata(thd, item, pos)) + goto err; + if (prot.write()) + DBUG_RETURN(1); +#ifndef DBUG_OFF + field_handlers[pos]= item->type_handler(); +#endif + } + } + + if (flags & SEND_EOF) + { + + /* if it is new client do not send EOF packet */ + if (!(thd->client_capabilities & CLIENT_DEPRECATE_EOF)) + { + /* + Mark the end of meta-data result set, and store thd->server_status, + to show that there is no cursor. + Send no warning information, as it will be sent at statement end. + */ + if (write_eof_packet(thd, &thd->net, thd->server_status, + thd->get_stmt_da()->current_statement_warn_count())) + DBUG_RETURN(1); + } + } + DBUG_RETURN(prepare_for_send(list->elements)); + +err: + my_message(ER_OUT_OF_RESOURCES, ER_THD(thd, ER_OUT_OF_RESOURCES), + MYF(0)); /* purecov: inspected */ + DBUG_RETURN(1); /* purecov: inspected */ +} + + +bool Protocol::send_list_fields(List<Field> *list, const TABLE_LIST *table_list) +{ + DBUG_ENTER("Protocol::send_list_fields"); + List_iterator_fast<Field> it(*list); + Field *fld; + Protocol_text prot(thd, thd->variables.net_buffer_length); + +#ifndef DBUG_OFF + field_handlers= (const Type_handler **) thd->alloc(sizeof(field_handlers[0]) * + list->elements); +#endif + + for (uint pos= 0; (fld= it++); pos++) + { + prot.prepare_for_resend(); + if (prot.store_field_metadata_for_list_fields(thd, fld, table_list, pos)) + goto err; + prot.store(fld); // Send default value + if (prot.write()) + DBUG_RETURN(1); +#ifndef DBUG_OFF + /* + Historically all BLOB variant Fields are displayed as + MYSQL_TYPE_BLOB in metadata. + See Field_blob::make_send_field() for more comments. + */ + field_handlers[pos]= Send_field(fld).type_handler(); +#endif + } + DBUG_RETURN(prepare_for_send(list->elements)); + +err: + my_message(ER_OUT_OF_RESOURCES, ER_THD(thd, ER_OUT_OF_RESOURCES), MYF(0)); + DBUG_RETURN(1); +} + + +bool Protocol::write() +{ + DBUG_ENTER("Protocol::write"); + DBUG_RETURN(my_net_write(&thd->net, (uchar*) packet->ptr(), + packet->length())); +} +#endif /* EMBEDDED_LIBRARY */ + + +bool Protocol_text::store_item_metadata(THD *thd, Item *item, uint pos) +{ + Send_field field(thd, item); + return store_field_metadata(thd, field, item->charset_for_protocol(), pos); +} + + +bool Protocol_text::store_field_metadata_for_list_fields(const THD *thd, + Field *fld, + const TABLE_LIST *tl, + uint pos) +{ + Send_field field= tl->view ? + Send_field(fld, tl->view_db, tl->view_name) : + Send_field(fld); + return store_field_metadata(thd, field, fld->charset_for_protocol(), pos); +} + + +/** + Send one result set row. + + @param row_items a collection of column values for that row + + @return Error status. + @retval TRUE Error. + @retval FALSE Success. +*/ + +bool Protocol::send_result_set_row(List<Item> *row_items) +{ + List_iterator_fast<Item> it(*row_items); + ValueBuffer<MAX_FIELD_WIDTH> value_buffer; + DBUG_ENTER("Protocol::send_result_set_row"); + + for (Item *item= it++; item; item= it++) + { + value_buffer.reset_buffer(); + if (item->send(this, &value_buffer)) + { + // If we're out of memory, reclaim some, to help us recover. + this->free(); + DBUG_RETURN(TRUE); + } + /* Item::send() may generate an error. If so, abort the loop. */ + if (unlikely(thd->is_error())) + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(FALSE); +} + + +/** + Send \\0 end terminated string or NULL + + @param from NullS or \\0 terminated string + + @note + In most cases one should use store(from, length) instead of this function + + @retval + 0 ok + @retval + 1 error +*/ + +bool Protocol::store_string_or_null(const char *from, CHARSET_INFO *cs) +{ + if (!from) + return store_null(); + return store(from, strlen(from), cs); +} + + +/** + Send a set of strings as one long string with ',' in between. +*/ + +bool Protocol::store(I_List<i_string>* str_list) +{ + char buf[256]; + String tmp(buf, sizeof(buf), &my_charset_bin); + size_t len= 0; + I_List_iterator<i_string> it(*str_list); + i_string* s; + const char *delimiter= ","; + + tmp.length(0); + while ((s=it++)) + { + tmp.append(delimiter, len); + tmp.append(s->ptr, strlen(s->ptr)); + len= 1; + } + + return store((char*) tmp.ptr(), tmp.length(), tmp.charset()); +} + + +/** + Send a set of strings as a string of key-value pairs with ',' in between. +*/ + +bool Protocol::store(I_List<i_string_pair>* str_list) +{ + char buf[256]; + const char *delimiter= ","; + String tmp(buf, sizeof(buf), &my_charset_bin); + size_t delim_len= 0; + I_List_iterator<i_string_pair> it(*str_list); + i_string_pair* s; + + tmp.length(0); + while ((s=it++)) + { + tmp.append(delimiter, delim_len); + tmp.append(s->key, strlen(s->key)); + tmp.append(STRING_WITH_LEN("->")); + tmp.append(s->val, strlen(s->val)); + delim_len= 1; + } + return store((char*) tmp.ptr(), tmp.length(), tmp.charset()); +} + + +/**************************************************************************** + Functions to handle the simple (default) protocol where everything is + This protocol is the one that is used by default between the MySQL server + and client when you are not using prepared statements. + + All data are sent as 'packed-string-length' followed by 'string-data' +****************************************************************************/ + +#ifndef EMBEDDED_LIBRARY +void Protocol_text::prepare_for_resend() +{ + packet->length(0); +#ifndef DBUG_OFF + field_pos= 0; +#endif +} + +bool Protocol_text::store_null() +{ +#ifndef DBUG_OFF + field_pos++; +#endif + char buff[1]; + buff[0]= (char)251; + return packet->append(buff, sizeof(buff), PACKET_BUFFER_EXTRA_ALLOC); +} +#endif + + +/** + Auxilary function to convert string to the given character set + and store in network buffer. +*/ + +bool Protocol::store_string_aux(const char *from, size_t length, + CHARSET_INFO *fromcs, CHARSET_INFO *tocs) +{ + /* 'tocs' is set 0 when client issues SET character_set_results=NULL */ + if (needs_conversion(fromcs, tocs)) + { + /* Store with conversion */ + return net_store_data_cs((uchar*) from, length, fromcs, tocs); + } + /* Store without conversion */ + return net_store_data((uchar*) from, length); +} + + +bool Protocol_text::store_numeric_string_aux(const char *from, size_t length) +{ + CHARSET_INFO *tocs= thd->variables.character_set_results; + // 'tocs' is NULL when the client issues SET character_set_results=NULL + if (tocs && (tocs->state & MY_CS_NONASCII)) // Conversion needed + return net_store_data_cs((uchar*) from, length, &my_charset_latin1, tocs); + return net_store_data((uchar*) from, length); // No conversion +} + + +bool Protocol::store_warning(const char *from, size_t length) +{ + BinaryStringBuffer<MYSQL_ERRMSG_SIZE> tmp; + CHARSET_INFO *cs= thd->variables.character_set_results; + if (!cs || cs == &my_charset_bin) + cs= system_charset_info; + if (tmp.copy_printable_hhhh(cs, system_charset_info, from, length)) + return net_store_data((const uchar*)"", 0); + return net_store_data((const uchar *) tmp.ptr(), tmp.length()); +} + + +bool Protocol_text::store_str(const char *from, size_t length, + CHARSET_INFO *fromcs, CHARSET_INFO *tocs) +{ +#ifndef DBUG_OFF + DBUG_PRINT("info", ("Protocol_text::store field %u : %.*b", field_pos, + (int) length, (length == 0 ? "" : from))); + DBUG_ASSERT(field_handlers == 0 || field_pos < field_count); + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_STRING)); + field_pos++; +#endif + return store_string_aux(from, length, fromcs, tocs); +} + + +bool Protocol_text::store_numeric_zerofill_str(const char *from, + size_t length, + protocol_send_type_t send_type) +{ +#ifndef DBUG_OFF + DBUG_PRINT("info", + ("Protocol_text::store_numeric_zerofill_str field %u : %.*b", + field_pos, (int) length, (length == 0 ? "" : from))); + DBUG_ASSERT(field_handlers == 0 || field_pos < field_count); + DBUG_ASSERT(valid_handler(field_pos, send_type)); + field_pos++; +#endif + return store_numeric_string_aux(from, length); +} + + +bool Protocol_text::store_tiny(longlong from) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_TINY)); + field_pos++; +#endif + char buff[22]; + size_t length= (size_t) (int10_to_str((int) from, buff, -10) - buff); + return store_numeric_string_aux(buff, length); +} + + +bool Protocol_text::store_short(longlong from) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_SHORT)); + field_pos++; +#endif + char buff[22]; + size_t length= (size_t) (int10_to_str((int) from, buff, -10) - buff); + return store_numeric_string_aux(buff, length); +} + + +bool Protocol_text::store_long(longlong from) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_LONG)); + field_pos++; +#endif + char buff[22]; + size_t length= (size_t) (int10_to_str((long int)from, buff, + (from < 0) ? - 10 : 10) - buff); + return store_numeric_string_aux(buff, length); +} + + +bool Protocol_text::store_longlong(longlong from, bool unsigned_flag) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_LONGLONG)); + field_pos++; +#endif + char buff[22]; + size_t length= (size_t) (longlong10_to_str(from, buff, + unsigned_flag ? 10 : -10) - + buff); + return store_numeric_string_aux(buff, length); +} + + +bool Protocol_text::store_decimal(const my_decimal *d) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(0); // This method is not used yet + field_pos++; +#endif + StringBuffer<DECIMAL_MAX_STR_LENGTH> str; + (void) d->to_string(&str); + return store_numeric_string_aux(str.ptr(), str.length()); +} + + +bool Protocol_text::store_float(float from, uint32 decimals) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_FLOAT)); + field_pos++; +#endif + Float(from).to_string(&buffer, decimals); + return store_numeric_string_aux(buffer.ptr(), buffer.length()); +} + + +bool Protocol_text::store_double(double from, uint32 decimals) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_DOUBLE)); + field_pos++; +#endif + buffer.set_real(from, decimals, thd->charset()); + return store_numeric_string_aux(buffer.ptr(), buffer.length()); +} + + +bool Protocol_text::store(Field *field) +{ + if (field->is_null()) + return store_null(); +#ifdef DBUG_ASSERT_EXISTS + TABLE *table= field->table; + MY_BITMAP *old_map= 0; + if (table->file) + old_map= dbug_tmp_use_all_columns(table, &table->read_set); +#endif + + bool rc= field->send(this); + +#ifdef DBUG_ASSERT_EXISTS + if (old_map) + dbug_tmp_restore_column_map(&table->read_set, old_map); +#endif + + return rc; +} + + +bool Protocol_text::store_datetime(MYSQL_TIME *tm, int decimals) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_DATETIME)); + field_pos++; +#endif + char buff[MAX_DATE_STRING_REP_LENGTH]; + uint length= my_datetime_to_str(tm, buff, decimals); + return store_numeric_string_aux(buff, length); +} + + +bool Protocol_text::store_date(MYSQL_TIME *tm) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_DATE)); + field_pos++; +#endif + char buff[MAX_DATE_STRING_REP_LENGTH]; + size_t length= my_date_to_str(tm, buff); + return store_numeric_string_aux(buff, length); +} + + +bool Protocol_text::store_time(MYSQL_TIME *tm, int decimals) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(valid_handler(field_pos, PROTOCOL_SEND_TIME)); + field_pos++; +#endif + char buff[MAX_DATE_STRING_REP_LENGTH]; + uint length= my_time_to_str(tm, buff, decimals); + return store_numeric_string_aux(buff, length); +} + +/** + Assign OUT-parameters to user variables. + + @param sp_params List of PS/SP parameters (both input and output). + + @return Error status. + @retval FALSE Success. + @retval TRUE Error. +*/ + +bool Protocol_text::send_out_parameters(List<Item_param> *sp_params) +{ + DBUG_ASSERT(sp_params->elements == thd->lex->prepared_stmt.param_count()); + + List_iterator_fast<Item_param> item_param_it(*sp_params); + List_iterator_fast<Item> param_it(thd->lex->prepared_stmt.params()); + + while (true) + { + Item_param *item_param= item_param_it++; + Item *param= param_it++; + Settable_routine_parameter *sparam; + + if (!item_param || !param) + break; + + if (!item_param->get_out_param_info()) + continue; // It's an IN-parameter. + + if (!(sparam= param->get_settable_routine_parameter())) + { + DBUG_ASSERT(0); + continue; + } + + DBUG_ASSERT(sparam->get_item_param() == NULL); + sparam->set_value(thd, thd->spcont, reinterpret_cast<Item **>(&item_param)); + } + + return FALSE; +} + +/**************************************************************************** + Functions to handle the binary protocol used with prepared statements + + Data format: + + [ok:1] reserved ok packet + [null_field:(field_count+7+2)/8] reserved to send null data. The size is + calculated using: + bit_fields= (field_count+7+2)/8; + 2 bits are reserved for identifying type + of package. + [[length]data] data field (the length applies only for + string/binary/time/timestamp fields and + rest of them are not sent as they have + the default length that client understands + based on the field type + [..]..[[length]data] data +****************************************************************************/ + +bool Protocol_binary::prepare_for_send(uint num_columns) +{ + Protocol::prepare_for_send(num_columns); + bit_fields= (field_count+9)/8; + return packet->alloc(bit_fields+1); + + /* prepare_for_resend will be called after this one */ +} + + +void Protocol_binary::prepare_for_resend() +{ + packet->length(bit_fields+1); + bzero((uchar*) packet->ptr(), 1+bit_fields); + field_pos=0; +} + + +bool Protocol_binary::store_str(const char *from, size_t length, + CHARSET_INFO *fromcs, CHARSET_INFO *tocs) +{ + field_pos++; + return store_string_aux(from, length, fromcs, tocs); +} + +bool Protocol_binary::store_null() +{ + uint offset= (field_pos+2)/8+1, bit= (1 << ((field_pos+2) & 7)); + /* Room for this as it's allocated in prepare_for_send */ + char *to= (char*) packet->ptr()+offset; + *to= (char) ((uchar) *to | (uchar) bit); + field_pos++; + return 0; +} + + +bool Protocol_binary::store_tiny(longlong from) +{ + char buff[1]; + field_pos++; + buff[0]= (uchar) from; + return packet->append(buff, sizeof(buff), PACKET_BUFFER_EXTRA_ALLOC); +} + + +bool Protocol_binary::store_short(longlong from) +{ + field_pos++; + char *to= packet->prep_append(2, PACKET_BUFFER_EXTRA_ALLOC); + if (!to) + return 1; + int2store(to, (int) from); + return 0; +} + + +bool Protocol_binary::store_long(longlong from) +{ + field_pos++; + char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC); + if (!to) + return 1; + int4store(to, from); + return 0; +} + + +bool Protocol_binary::store_longlong(longlong from, bool unsigned_flag) +{ + field_pos++; + char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC); + if (!to) + return 1; + int8store(to, from); + return 0; +} + +bool Protocol_binary::store_decimal(const my_decimal *d) +{ +#ifndef DBUG_OFF + DBUG_ASSERT(0); // This method is not used yet +#endif + StringBuffer<DECIMAL_MAX_STR_LENGTH> str; + (void) d->to_string(&str); + return store_str(str.ptr(), str.length(), str.charset(), + thd->variables.character_set_results); +} + +bool Protocol_binary::store_float(float from, uint32 decimals) +{ + field_pos++; + char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC); + if (!to) + return 1; + float4store(to, from); + return 0; +} + + +bool Protocol_binary::store_double(double from, uint32 decimals) +{ + field_pos++; + char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC); + if (!to) + return 1; + float8store(to, from); + return 0; +} + + +bool Protocol_binary::store(Field *field) +{ + /* + We should not increment field_pos here as send() will call another + protocol function to do this for us + */ + if (field->is_null()) + return store_null(); + return field->send(this); +} + + +bool Protocol_binary::store_datetime(MYSQL_TIME *tm, int decimals) +{ + char buff[12],*pos; + uint length; + field_pos++; + pos= buff+1; + + int2store(pos, tm->year); + pos[2]= (uchar) tm->month; + pos[3]= (uchar) tm->day; + pos[4]= (uchar) tm->hour; + pos[5]= (uchar) tm->minute; + pos[6]= (uchar) tm->second; + DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS || + (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS)); + if (decimals != AUTO_SEC_PART_DIGITS) + my_datetime_trunc(tm, decimals); + int4store(pos+7, tm->second_part); + if (tm->second_part) + length=11; + else if (tm->hour || tm->minute || tm->second) + length=7; + else if (tm->year || tm->month || tm->day) + length=4; + else + length=0; + buff[0]=(char) length; // Length is stored first + return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC); +} + +bool Protocol_binary::store_date(MYSQL_TIME *tm) +{ + tm->hour= tm->minute= tm->second=0; + tm->second_part= 0; + return Protocol_binary::store_datetime(tm, 0); +} + + +bool Protocol_binary::store_time(MYSQL_TIME *tm, int decimals) +{ + char buff[13], *pos; + uint length; + field_pos++; + pos= buff+1; + pos[0]= tm->neg ? 1 : 0; + if (tm->hour >= 24) + { + uint days= tm->hour/24; + tm->hour-= days*24; + tm->day+= days; + } + int4store(pos+1, tm->day); + pos[5]= (uchar) tm->hour; + pos[6]= (uchar) tm->minute; + pos[7]= (uchar) tm->second; + DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS || + (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS)); + if (decimals != AUTO_SEC_PART_DIGITS) + my_time_trunc(tm, decimals); + int4store(pos+8, tm->second_part); + if (tm->second_part) + length=12; + else if (tm->hour || tm->minute || tm->second || tm->day) + length=8; + else + length=0; + buff[0]=(char) length; // Length is stored first + return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC); +} + +/** + Send a result set with OUT-parameter values by means of PS-protocol. + + @param sp_params List of PS/SP parameters (both input and output). + + @return Error status. + @retval FALSE Success. + @retval TRUE Error. +*/ + +bool Protocol_binary::send_out_parameters(List<Item_param> *sp_params) +{ + bool ret; + if (!(thd->client_capabilities & CLIENT_PS_MULTI_RESULTS)) + { + /* The client does not support OUT-parameters. */ + return FALSE; + } + + List<Item> out_param_lst; + + { + List_iterator_fast<Item_param> item_param_it(*sp_params); + + while (true) + { + Item_param *item_param= item_param_it++; + + if (!item_param) + break; + + if (!item_param->get_out_param_info()) + continue; // It's an IN-parameter. + + if (out_param_lst.push_back(item_param, thd->mem_root)) + return TRUE; + } + } + + if (!out_param_lst.elements) + return FALSE; + + /* + We have to set SERVER_PS_OUT_PARAMS in THD::server_status, because it + is used in send_result_set_metadata(). + */ + + thd->server_status|= SERVER_PS_OUT_PARAMS | SERVER_MORE_RESULTS_EXISTS; + + /* Send meta-data. */ + if (send_result_set_metadata(&out_param_lst, + SEND_NUM_ROWS | SEND_EOF | SEND_FORCE_COLUMN_INFO)) + return TRUE; + + /* Send data. */ + + prepare_for_resend(); + + if (send_result_set_row(&out_param_lst)) + return TRUE; + + if (write()) + return TRUE; + + ret= net_send_eof(thd, thd->server_status, 0); + + /* + Reset server_status: + - SERVER_MORE_RESULTS_EXISTS bit, because this is the last packet for sure. + - Restore SERVER_PS_OUT_PARAMS status. + */ + thd->server_status&= ~(SERVER_PS_OUT_PARAMS | SERVER_MORE_RESULTS_EXISTS); + + return ret ? FALSE : TRUE; +} |