mariadb/sql/sql_servers.cc
unknown 2a0d2fef51 Bug#12713 "Error in a stored function called from a SELECT doesn't
cause ROLLBACK of statement", part 1. Review fixes.

Do not send OK/EOF packets to the client until we reached the end of 
the current statement.
This is a consolidation, to keep the functionality that is shared by all 
SQL statements in one place in the server.
Currently this functionality includes:
- close_thread_tables()
- log_slow_statement().

After this patch and the subsequent patch for Bug#12713, it shall also include:
- ha_autocommit_or_rollback()
- net_end_statement()
- query_cache_end_of_result().

In future it may also include:
- mysql_reset_thd_for_next_command().


include/mysql_com.h:
  Rename now unused members of NET: no_send_ok, no_send_error, report_error.
  These were server-specific variables related to the client/server
  protocol. They have been made obsolete by this patch.
  
  Previously the same members of NET were used to store the error message
  both on the client and on the server. 
  The error message was stored in net.last_error (client: mysql->net.last_error,
  server: thd->net.last_error).
  The error code was stored in net.last_errno (client: mysql->net.last_errno,
  server: thd->net.last_errno).
  The server error code and message are now stored elsewhere 
  (in the Diagnostics_area), thus NET members are no longer used by the
  server.
  Rename last_error to client_last_error, last_errno to client_last_errno
  to avoid potential bugs introduced by merges.
include/mysql_h.ic:
  Update the ABI file to reflect a rename. 
  Renames do not break the binary compatibility.
libmysql/libmysql.c:
  Rename last_error to client_last_error, last_errno to client_last_errno.
  This is necessary to ensure no unnoticed bugs introduced by merged
  changesets.
  
  Remove net.report_error, net.no_send_ok, net.no_send_error.
libmysql/manager.c:
  Rename net.last_errno to net.client_last_errno.
libmysqld/lib_sql.cc:
  Rename net.last_errno to net.client_last_errno.
  
  Update the embedded implementation of the client-server protocol to
  reflect the refactoring of protocol.cc.
libmysqld/libmysqld.c:
  Rename net.last_errno to net.client_last_errno.
mysql-test/r/events.result:
  Update to reflect the change in mysql_rm_db(). Now we drop stored
  routines and events for a given database name only if there
  is a directory for this database name. ha_drop_database() and
  query_cache_invalidate() are called likewise. 
  Previously we would attempt to drop routines/events even if database
  directory was not found (it worked, since routines and events are stored
  in tables). This fixes Bug 29958 "Weird message on DROP DATABASE if mysql.proc
  does not exist".
  The change was done because the previous code used to call send_ok()
  twice, which led to an assertion failure when asserts against it were
  added by this patch.
mysql-test/r/grant.result:
  Fix the patch for Bug 16470, now FLUSH PRIVILEGES produces an error 
  if mysql.procs_priv is missing.
  This fixes the assert that send_ok() must not called after send_error()
  (the original patch for Bug 16470 was prone to this).
mysql-test/suite/rpl/r/rpl_row_tabledefs_2myisam.result:
  Produce a more detailed error message.
mysql-test/suite/rpl/r/rpl_row_tabledefs_3innodb.result:
  Produce a more detailed error message.
mysql-test/t/grant.test:
  Update the test, now FLUSH PRIVILEGES returns an error if mysql.procs_priv
  is missing.
server-tools/instance-manager/mysql_connection.cc:
  Rename net.last_errno to net.client_last_errno.
sql/ha_ndbcluster_binlog.cc:
  Add asserts. 
  
  Use getters to access statement status information.
  
  Add a comment why run_query() is broken. Reset the diagnostics area
  in the end of run_query() to fulfill the invariant that the diagnostics_area
  is never assigned twice per statement (see the comment in the code
  when this can happen). We still do not clear thd->is_fatal_error and
  thd->is_slave_error, which may lead to bugs, I consider the whole affair
  as something to be dealt with separately.
sql/ha_partition.cc:
  fatal_error() doesn't set an error by itself. Perhaps we should
  remove this method altogether and instead add a flag to my_error 
  to set thd->is_fatal_error property.
  
  Meanwhile, this change is a part of inspection made to the entire source
  code with the goal to ensure that fatal_error()
  is always accompanied by my_error().
sql/item_func.cc:
  There is no net.last_error anymore. Remove the obsolete assignment.
sql/log_event.cc:
  Use getters to access statement error status information.
sql/log_event_old.cc:
  Use getters to access statement error status information.
sql/mysqld.cc:
  Previously, if a continue handler for an error was found, my_message_sql() 
  would not set an error in THD. Since the current statement
  must be aborted in any case, find_handler() had a hack to assign 
  thd->net.report_error to 1.
  
  Remove this hack. Set an error in my_message_sql() even if the continue
  handler is found. The error will be cleared anyway when the handler
  is executed. This is one action among many in this patch to ensure the 
  invariant that whenever thd->is_error() is TRUE, we have a message in 
  thd->main_da.message().
sql/net_serv.cc:
  Use a full-blown my_error() in net_serv.cc to report an error,
  instead of just setting net->last_errno. This ensures the invariant that
  whenever thd->is_error() returns TRUE, we have a message in 
  thd->main_da.message().
  
  Remove initialization of removed NET members.
sql/opt_range.cc:
  Use my_error() instead of just raising thd->net.report_error. 
  This ensures the invariant that whenever thd->is_error() returns TRUE, 
  there is a message in thd->main_da.message().
sql/opt_sum.cc:
  Move invocation of fatal_error() right next to the place where
  we set the error message. That makes it easier to track that whenever
  fatal_error() is called, there is a message in THD.
sql/protocol.cc:
  Rename send_ok() and send_eof() to net_send_ok() and net_send_eof() 
  respectively. These functions write directly to the network and are not 
  for use anywhere outside the client/server protocol code. 
  
  Remove the code that was responsible for cases when either there is 
  no error code, or no error message, or both.
  Instead the calling code ensures that they are always present. Asserts
  are added to enforce the invariant.
  
  Instead of a direct access to thd->server_status and thd->total_warn_count
  use function parameters, since these from now on don't always come directly
  from THD.
  
  Introduce net_end_statement(), the single-entry-point replacement API for 
  send_ok(), send_eof() and net_send_error().
  
  Implement Protocol::end_partial_result_set to use in select_send::abort()
  when there is a continue handler.
sql/protocol.h:
  Update declarations.
sql/repl_failsafe.cc:
  Use getters to access statement status information in THD.
  Rename net.last_error to net.client_last_error.
sql/rpl_record.cc:
  Set an error message in prepare_record() if there is no default
  value for the field -- later we do print this message to the client.
sql/rpl_rli.cc:
  Use getters to access statement status information in THD.
sql/slave.cc:
  In create_table_from_dump() (a common function that is used in 
  LOAD MASTER TABLE SQL statement and COM_LOAD_MASTER_DATA), instead of hacks
  with no_send_ok, clear the diagnostics area when mysql_rm_table() succeeded.
  
  Update has_temporary_error() to work correctly when no error is set.
  This is the case when Incident_log_event is executed: it always returns
  an error but does not set an error message.
  
  Use getters to access error status information.
sql/sp_head.cc:
  Instead of hacks with no_send_error, work through the diagnostics area 
  interface to suppress sending of OK/ERROR packets to the client.
  
  Move query_cache_end_of_result before log_slow_statement(), similarly
  to how it's done in dispatch_command().
sql/sp_rcontext.cc:
  Remove hacks with assignment of thd->net.report_error, they are not
  necessary any more (see the changes in mysqld.cc).
sql/sql_acl.cc:
  Use getters to access error status information in THD.
sql/sql_base.cc:
  Access thd->main_da.sql_errno() only if there is an error. This fixes
  a bug when auto-discovery, that was effectively disabled under pre-locking.
sql/sql_binlog.cc:
  Remove hacks with no_send_ok/no_send_error, they are not necessary 
  anymore: the caller is responsible for network communication.
sql/sql_cache.cc:
  Disable sending of OK/ERROR/EOF packet in the end of dispatch_command
  if the response has been served from the query cache. This raises the 
  question whether we should store EOF packet in the query cache at all,
  or generate it anew for each statement (we should generate it anew), but
  this is to be addressed separately.
sql/sql_class.cc:
  Implement class Diagnostics_area. Please see comments in sql_class.h
  for details.
  
  Fix a subtle coding mistake in select_send::send_data: when on slave, 
  an error in Item::send() was ignored.
  The problem became visible due to asserts that the diagnostics area is
  never double assigned.
  
  Remove initialization of removed NET members.
  
  In select_send::abort() do not call select_send::send_eof(). This is
  not inheritance-safe. Even if a stored procedure continue handler is
  found, the current statement is aborted, not succeeded.
  Instead introduce a Protocol API to send the required response, 
  Protocol::end_partial_result_set().
  
  This simplifies implementation of select_send::send_eof(). No need
  to add more asserts that there is no error, there is an assert inside
  Diagnostics_area::set_ok_status() already.
  
  Leave no trace of no_send_* in the code.
sql/sql_class.h:
  Declare class Diagnostics_area. 
  
  Remove the hack with no_send_ok from
  Substatement_state.
  
  Provide inline implementations of send_ok/send_eof.
  
  Add commetns.
sql/sql_connect.cc:
  Remove hacks with no_send_error. 
  
  Since now an error in THD is always set if net->error, it's not necessary
  to check both net->error and thd->is_error() in the do_command loop.
  
  Use thd->main_da.message() instead of net->last_errno.
  
  Remove the hack with is_slave_error in sys_init_connect. Since now we do not
  reset the diagnostics area in net_send_error (it's reset at the beginning
  of the next statement), we can access it safely even after 
  execute_init_command.
sql/sql_db.cc:
  Update the code to satisfy the invariant that the diagnostics area is never
  assigned twice.
  Incidentally, this fixes Bug 29958 "Weird message on DROP DATABASE if 
  mysql.proc does not exist".
sql/sql_delete.cc:
  Change multi-delete to abort in abort(), as per select_send protocol.
  Fixes the merge error with the test for Bug 29136
sql/sql_derived.cc:
  Use getters to access error information.
sql/sql_insert.cc:
  Use getters to access error information.
sql-common/client.c:
  Rename last_error to client_last_error, last_errno to client_last_errno.
sql/sql_parse.cc:
  Remove hacks with no_send_error. Deploy net_end_statement().
  
  The story of COM_SHUTDOWN is interesting. Long story short, the server 
  would become on its death's door, and only no_send_ok/no_send_error assigned
  by send_ok()/net_send_error() would hide its babbling from the client.
  
  First of all, COM_QUIT does not require a response. So, the comment saying
  "Let's send a response to possible COM_QUIT" is not only groundless 
  (even mysqladmin shutdown/mysql_shutdown() doesn't send COM_QUIT after 
  COM_SHUTDOWN), it's plainly incorrect.
  
  Secondly, besides this additional 'OK' packet to respond to a hypothetical
  COM_QUIT, there was the following code in dispatch_command():
  
  if (thd->killed)
    thd->send_kill_message();
  if (thd->is_error()
    net_send_error(thd);
  
  This worked out really funny for the thread through which COM_SHUTDOWN
  was delivered: we would get COM_SHUTDOWN, say okay, say okay again, 
  kill everybody, get the kill signal ourselves, and then attempt to say 
  "Server shutdown in progress" to the client that is very likely long gone.
  
  This all became visible when asserts were added that the Diagnostics_area
  is not assigned twice.
  
  Move query_cache_end_of_result() to the end of dispatch_command(), since
  net_send_eof() has been moved there. This is safe, query_cache_end_of_result()
  is a no-op if there is no started query in the cache.
  
  Consistently use select_send interface to call abort() or send_eof()
  depending on the operation result.
  
  Remove thd->fatal_error() from reset_master(), it was a no-op. 
  in hacks with no_send_error woudl save us
  from complete breakage of the client/server protocol.
  
  Consistently use select_send::abort() whenever there is an error, 
  and select_send::send_eof() in case of success.
  The issue became visible due to added asserts.
sql/sql_partition.cc:
  Always set an error in THD whenever there is a call to fatal_error().
sql/sql_prepare.cc:
  Deploy class Diagnostics_area.
  Remove the unnecessary juggling with the protocol in 
  Select_fetch_protocol_binary::send_eof(). EOF packet format is 
  protocol-independent.
sql/sql_select.cc:
  Call fatal_error() directly in opt_sum_query.
  Call my_error() whenever we call thd->fatal_error().
sql/sql_servers.cc:
  Use getters to access error information in THD.
sql/sql_show.cc:
  Use getters to access error information in THD.
  
  Add comments.
  
  Call my_error() whenever we call fatal_error().
sql/sql_table.cc:
  Replace hacks with no_send_ok with the interface of the diagnostics area.
  
  Clear the error if ENOENT error in ha_delete_table().
sql/sql_update.cc:
  Introduce multi_update::abort(), which is the proper way to abort a
  multi-update. This fixes the merge conflict between this patch and
  the patch for Bug 29136.
sql/table.cc:
  Use a getter to access error information in THD.
sql/tztime.cc:
  Use a getter to access error information in THD.
2007-12-12 18:21:01 +03:00

1286 lines
36 KiB
C++

/* Copyright (C) 2000-2003 MySQL AB
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
The servers are saved in the system table "servers"
Currently, when the user performs an ALTER SERVER or a DROP SERVER
operation, it will cause all open tables which refer to the named
server connection to be flushed. This may cause some undesirable
behaviour with regard to currently running transactions. It is
expected that the DBA knows what s/he is doing when s/he performs
the ALTER SERVER or DROP SERVER operation.
TODO:
It is desirable for us to implement a callback mechanism instead where
callbacks can be registered for specific server protocols. The callback
will be fired when such a server name has been created/altered/dropped
or when statistics are to be gathered such as how many actual connections.
Storage engines etc will be able to make use of the callback so that
currently running transactions etc will not be disrupted.
*/
#include "mysql_priv.h"
#include "hash_filo.h"
#include <m_ctype.h>
#include <stdarg.h>
#include "sp_head.h"
#include "sp.h"
/*
We only use 1 mutex to guard the data structures - THR_LOCK_servers.
Read locked when only reading data and write-locked for all other access.
*/
static HASH servers_cache;
static MEM_ROOT mem;
static rw_lock_t THR_LOCK_servers;
static bool get_server_from_table_to_cache(TABLE *table);
/* insert functions */
static int insert_server(THD *thd, FOREIGN_SERVER *server_options);
static int insert_server_record(TABLE *table, FOREIGN_SERVER *server);
static int insert_server_record_into_cache(FOREIGN_SERVER *server);
static void prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options,
FOREIGN_SERVER *server);
/* drop functions */
static int delete_server_record(TABLE *table,
char *server_name,
int server_name_length);
static int delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options);
/* update functions */
static void prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options,
FOREIGN_SERVER *existing,
FOREIGN_SERVER *altered);
static int update_server(THD *thd, FOREIGN_SERVER *existing,
FOREIGN_SERVER *altered);
static int update_server_record(TABLE *table, FOREIGN_SERVER *server);
static int update_server_record_in_cache(FOREIGN_SERVER *existing,
FOREIGN_SERVER *altered);
/* utility functions */
static void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to);
static uchar *servers_cache_get_key(FOREIGN_SERVER *server, size_t *length,
my_bool not_used __attribute__((unused)))
{
DBUG_ENTER("servers_cache_get_key");
DBUG_PRINT("info", ("server_name_length %d server_name %s",
server->server_name_length,
server->server_name));
*length= (uint) server->server_name_length;
DBUG_RETURN((uchar*) server->server_name);
}
/*
Initialize structures responsible for servers used in federated
server scheme information for them from the server
table in the 'mysql' database.
SYNOPSIS
servers_init()
dont_read_server_table TRUE if we want to skip loading data from
server table and disable privilege checking.
NOTES
This function is mostly responsible for preparatory steps, main work
on initialization and grants loading is done in servers_reload().
RETURN VALUES
0 ok
1 Could not initialize servers
*/
bool servers_init(bool dont_read_servers_table)
{
THD *thd;
bool return_val= FALSE;
DBUG_ENTER("servers_init");
/* init the mutex */
if (my_rwlock_init(&THR_LOCK_servers, NULL))
DBUG_RETURN(TRUE);
/* initialise our servers cache */
if (hash_init(&servers_cache, system_charset_info, 32, 0, 0,
(hash_get_key) servers_cache_get_key, 0, 0))
{
return_val= TRUE; /* we failed, out of memory? */
goto end;
}
/* Initialize the mem root for data */
init_alloc_root(&mem, ACL_ALLOC_BLOCK_SIZE, 0);
if (dont_read_servers_table)
goto end;
/*
To be able to run this from boot, we allocate a temporary THD
*/
if (!(thd=new THD))
DBUG_RETURN(TRUE);
thd->thread_stack= (char*) &thd;
thd->store_globals();
lex_start(thd);
/*
It is safe to call servers_reload() since servers_* arrays and hashes which
will be freed there are global static objects and thus are initialized
by zeros at startup.
*/
return_val= servers_reload(thd);
delete thd;
/* Remember that we don't have a THD */
my_pthread_setspecific_ptr(THR_THD, 0);
end:
DBUG_RETURN(return_val);
}
/*
Initialize server structures
SYNOPSIS
servers_load()
thd Current thread
tables List containing open "mysql.servers"
RETURN VALUES
FALSE Success
TRUE Error
*/
static bool servers_load(THD *thd, TABLE_LIST *tables)
{
TABLE *table;
READ_RECORD read_record_info;
bool return_val= TRUE;
DBUG_ENTER("servers_load");
/* first, send all cached rows to sleep with the fishes, oblivion!
I expect this crappy comment replaced */
free_root(&mem, MYF(MY_MARK_BLOCKS_FREE));
my_hash_reset(&servers_cache);
init_read_record(&read_record_info,thd,table=tables[0].table,NULL,1,0);
while (!(read_record_info.read_record(&read_record_info)))
{
/* return_val is already TRUE, so no need to set */
if ((get_server_from_table_to_cache(table)))
goto end;
}
return_val= FALSE;
end:
end_read_record(&read_record_info);
DBUG_RETURN(return_val);
}
/*
Forget current servers cache and read new servers
from the conneciton table.
SYNOPSIS
servers_reload()
thd Current thread
NOTE
All tables of calling thread which were open and locked by LOCK TABLES
statement will be unlocked and closed.
This function is also used for initialization of structures responsible
for user/db-level privilege checking.
RETURN VALUE
FALSE Success
TRUE Failure
*/
bool servers_reload(THD *thd)
{
TABLE_LIST tables[1];
bool return_val= TRUE;
DBUG_ENTER("servers_reload");
if (thd->locked_tables)
{ // Can't have locked tables here
thd->lock=thd->locked_tables;
thd->locked_tables=0;
close_thread_tables(thd);
}
DBUG_PRINT("info", ("locking servers_cache"));
rw_wrlock(&THR_LOCK_servers);
bzero((char*) tables, sizeof(tables));
tables[0].alias= tables[0].table_name= (char*) "servers";
tables[0].db= (char*) "mysql";
tables[0].lock_type= TL_READ;
if (simple_open_n_lock_tables(thd, tables))
{
sql_print_error("Can't open and lock privilege tables: %s",
thd->main_da.message());
goto end;
}
if ((return_val= servers_load(thd, tables)))
{ // Error. Revert to old list
/* blast, for now, we have no servers, discuss later way to preserve */
DBUG_PRINT("error",("Reverting to old privileges"));
servers_free();
}
end:
close_thread_tables(thd);
DBUG_PRINT("info", ("unlocking servers_cache"));
rw_unlock(&THR_LOCK_servers);
DBUG_RETURN(return_val);
}
/*
Initialize structures responsible for servers used in federated
server scheme information for them from the server
table in the 'mysql' database.
SYNOPSIS
get_server_from_table_to_cache()
TABLE *table open table pointer
NOTES
This function takes a TABLE pointer (pointing to an opened
table). With this open table, a FOREIGN_SERVER struct pointer
is allocated into root memory, then each member of the FOREIGN_SERVER
struct is populated. A char pointer takes the return value of get_field
for each column we're interested in obtaining, and if that pointer
isn't 0x0, the FOREIGN_SERVER member is set to that value, otherwise,
is set to the value of an empty string, since get_field would set it to
0x0 if the column's value is empty, even if the default value for that
column is NOT NULL.
RETURN VALUES
0 ok
1 could not insert server struct into global servers cache
*/
static bool
get_server_from_table_to_cache(TABLE *table)
{
/* alloc a server struct */
char *ptr;
char * const blank= (char*)"";
FOREIGN_SERVER *server= (FOREIGN_SERVER *)alloc_root(&mem,
sizeof(FOREIGN_SERVER));
DBUG_ENTER("get_server_from_table_to_cache");
table->use_all_columns();
/* get each field into the server struct ptr */
server->server_name= get_field(&mem, table->field[0]);
server->server_name_length= strlen(server->server_name);
ptr= get_field(&mem, table->field[1]);
server->host= ptr ? ptr : blank;
ptr= get_field(&mem, table->field[2]);
server->db= ptr ? ptr : blank;
ptr= get_field(&mem, table->field[3]);
server->username= ptr ? ptr : blank;
ptr= get_field(&mem, table->field[4]);
server->password= ptr ? ptr : blank;
ptr= get_field(&mem, table->field[5]);
server->sport= ptr ? ptr : blank;
server->port= server->sport ? atoi(server->sport) : 0;
ptr= get_field(&mem, table->field[6]);
server->socket= ptr && strlen(ptr) ? ptr : blank;
ptr= get_field(&mem, table->field[7]);
server->scheme= ptr ? ptr : blank;
ptr= get_field(&mem, table->field[8]);
server->owner= ptr ? ptr : blank;
DBUG_PRINT("info", ("server->server_name %s", server->server_name));
DBUG_PRINT("info", ("server->host %s", server->host));
DBUG_PRINT("info", ("server->db %s", server->db));
DBUG_PRINT("info", ("server->username %s", server->username));
DBUG_PRINT("info", ("server->password %s", server->password));
DBUG_PRINT("info", ("server->socket %s", server->socket));
if (my_hash_insert(&servers_cache, (uchar*) server))
{
DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
server->server_name, (long unsigned int) server));
// error handling needed here
DBUG_RETURN(TRUE);
}
DBUG_RETURN(FALSE);
}
/*
SYNOPSIS
insert_server()
THD *thd - thread pointer
FOREIGN_SERVER *server - pointer to prepared FOREIGN_SERVER struct
NOTES
This function takes a server object that is has all members properly
prepared, ready to be inserted both into the mysql.servers table and
the servers cache.
THR_LOCK_servers must be write locked.
RETURN VALUES
0 - no error
other - error code
*/
static int
insert_server(THD *thd, FOREIGN_SERVER *server)
{
int error= -1;
TABLE_LIST tables;
TABLE *table;
DBUG_ENTER("insert_server");
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.alias= tables.table_name= (char*) "servers";
/* need to open before acquiring THR_LOCK_plugin or it will deadlock */
if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
goto end;
/* insert the server into the table */
if ((error= insert_server_record(table, server)))
goto end;
/* insert the server into the cache */
if ((error= insert_server_record_into_cache(server)))
goto end;
end:
DBUG_RETURN(error);
}
/*
SYNOPSIS
int insert_server_record_into_cache()
FOREIGN_SERVER *server
NOTES
This function takes a FOREIGN_SERVER pointer to an allocated (root mem)
and inserts it into the global servers cache
THR_LOCK_servers must be write locked.
RETURN VALUE
0 - no error
>0 - error code
*/
static int
insert_server_record_into_cache(FOREIGN_SERVER *server)
{
int error=0;
DBUG_ENTER("insert_server_record_into_cache");
/*
We succeded in insertion of the server to the table, now insert
the server to the cache
*/
DBUG_PRINT("info", ("inserting server %s at %lx, length %d",
server->server_name, (long unsigned int) server,
server->server_name_length));
if (my_hash_insert(&servers_cache, (uchar*) server))
{
DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
server->server_name, (long unsigned int) server));
// error handling needed here
error= 1;
}
DBUG_RETURN(error);
}
/*
SYNOPSIS
store_server_fields()
TABLE *table
FOREIGN_SERVER *server
NOTES
This function takes an opened table object, and a pointer to an
allocated FOREIGN_SERVER struct, and then stores each member of
the FOREIGN_SERVER to the appropriate fields in the table, in
advance of insertion into the mysql.servers table
RETURN VALUE
VOID
*/
static void
store_server_fields(TABLE *table, FOREIGN_SERVER *server)
{
table->use_all_columns();
/*
"server" has already been prepped by prepare_server_struct_for_<>
so, all we need to do is check if the value is set (> -1 for port)
If this happens to be an update, only the server members that
have changed will be set. If an insert, then all will be set,
even if with empty strings
*/
if (server->host)
table->field[1]->store(server->host,
(uint) strlen(server->host), system_charset_info);
if (server->db)
table->field[2]->store(server->db,
(uint) strlen(server->db), system_charset_info);
if (server->username)
table->field[3]->store(server->username,
(uint) strlen(server->username), system_charset_info);
if (server->password)
table->field[4]->store(server->password,
(uint) strlen(server->password), system_charset_info);
if (server->port > -1)
table->field[5]->store(server->port);
if (server->socket)
table->field[6]->store(server->socket,
(uint) strlen(server->socket), system_charset_info);
if (server->scheme)
table->field[7]->store(server->scheme,
(uint) strlen(server->scheme), system_charset_info);
if (server->owner)
table->field[8]->store(server->owner,
(uint) strlen(server->owner), system_charset_info);
}
/*
SYNOPSIS
insert_server_record()
TABLE *table
FOREIGN_SERVER *server
NOTES
This function takes the arguments of an open table object and a pointer
to an allocated FOREIGN_SERVER struct. It stores the server_name into
the first field of the table (the primary key, server_name column). With
this, index_read_idx is called, if the record is found, an error is set
to ER_FOREIGN_SERVER_EXISTS (the server with that server name exists in the
table), if not, then store_server_fields stores all fields of the
FOREIGN_SERVER to the table, then ha_write_row is inserted. If an error
is encountered in either index_read_idx or ha_write_row, then that error
is returned
RETURN VALUE
0 - no errors
>0 - error code
*/
static
int insert_server_record(TABLE *table, FOREIGN_SERVER *server)
{
int error;
DBUG_ENTER("insert_server_record");
table->use_all_columns();
empty_record(table);
/* set the field that's the PK to the value we're looking for */
table->field[0]->store(server->server_name,
server->server_name_length,
system_charset_info);
/* read index until record is that specified in server_name */
if ((error= table->file->index_read_idx_map(table->record[0], 0,
(uchar *)table->field[0]->ptr,
HA_WHOLE_KEY,
HA_READ_KEY_EXACT)))
{
/* if not found, err */
if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
{
table->file->print_error(error, MYF(0));
error= 1;
}
/* store each field to be inserted */
store_server_fields(table, server);
DBUG_PRINT("info",("record for server '%s' not found!",
server->server_name));
/* write/insert the new server */
if ((error=table->file->ha_write_row(table->record[0])))
{
table->file->print_error(error, MYF(0));
}
else
error= 0;
}
else
error= ER_FOREIGN_SERVER_EXISTS;
DBUG_RETURN(error);
}
/*
SYNOPSIS
drop_server()
THD *thd
LEX_SERVER_OPTIONS *server_options
NOTES
This function takes as its arguments a THD object pointer and a pointer
to a LEX_SERVER_OPTIONS struct from the parser. The member 'server_name'
of this LEX_SERVER_OPTIONS struct contains the value of the server to be
deleted. The mysql.servers table is opened via open_ltable, a table object
returned, the servers cache mutex locked, then delete_server_record is
called with this table object and LEX_SERVER_OPTIONS server_name and
server_name_length passed, containing the name of the server to be
dropped/deleted, then delete_server_record_in_cache is called to delete
the server from the servers cache.
RETURN VALUE
0 - no error
> 0 - error code
*/
int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
{
int error;
TABLE_LIST tables;
TABLE *table;
LEX_STRING name= { server_options->server_name,
server_options->server_name_length };
DBUG_ENTER("drop_server");
DBUG_PRINT("info", ("server name server->server_name %s",
server_options->server_name));
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.alias= tables.table_name= (char*) "servers";
rw_wrlock(&THR_LOCK_servers);
/* hit the memory hit first */
if ((error= delete_server_record_in_cache(server_options)))
goto end;
if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
{
error= my_errno;
goto end;
}
error= delete_server_record(table, name.str, name.length);
/* close the servers table before we call closed_cached_connection_tables */
close_thread_tables(thd);
if (close_cached_connection_tables(thd, TRUE, &name))
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_UNKNOWN_ERROR, "Server connection in use");
}
end:
rw_unlock(&THR_LOCK_servers);
DBUG_RETURN(error);
}
/*
SYNOPSIS
delete_server_record_in_cache()
LEX_SERVER_OPTIONS *server_options
NOTES
This function's argument is a LEX_SERVER_OPTIONS struct pointer. This
function uses the "server_name" and "server_name_length" members of the
lex->server_options to search for the server in the servers_cache. Upon
returned the server (pointer to a FOREIGN_SERVER struct), it then deletes
that server from the servers_cache hash.
RETURN VALUE
0 - no error
*/
static int
delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options)
{
int error= ER_FOREIGN_SERVER_DOESNT_EXIST;
FOREIGN_SERVER *server;
DBUG_ENTER("delete_server_record_in_cache");
DBUG_PRINT("info",("trying to obtain server name %s length %d",
server_options->server_name,
server_options->server_name_length));
if (!(server= (FOREIGN_SERVER *) hash_search(&servers_cache,
(uchar*) server_options->server_name,
server_options->server_name_length)))
{
DBUG_PRINT("info", ("server_name %s length %d not found!",
server_options->server_name,
server_options->server_name_length));
goto end;
}
/*
We succeded in deletion of the server to the table, now delete
the server from the cache
*/
DBUG_PRINT("info",("deleting server %s length %d",
server->server_name,
server->server_name_length));
VOID(hash_delete(&servers_cache, (uchar*) server));
error= 0;
end:
DBUG_RETURN(error);
}
/*
SYNOPSIS
update_server()
THD *thd
FOREIGN_SERVER *existing
FOREIGN_SERVER *altered
NOTES
This function takes as arguments a THD object pointer, and two pointers,
one pointing to the existing FOREIGN_SERVER struct "existing" (which is
the current record as it is) and another pointer pointing to the
FOREIGN_SERVER struct with the members containing the modified/altered
values that need to be updated in both the mysql.servers table and the
servers_cache. It opens a table, passes the table and the altered
FOREIGN_SERVER pointer, which will be used to update the mysql.servers
table for the particular server via the call to update_server_record,
and in the servers_cache via update_server_record_in_cache.
THR_LOCK_servers must be write locked.
RETURN VALUE
0 - no error
>0 - error code
*/
int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered)
{
int error;
TABLE *table;
TABLE_LIST tables;
DBUG_ENTER("update_server");
bzero((char*) &tables, sizeof(tables));
tables.db= (char*)"mysql";
tables.alias= tables.table_name= (char*)"servers";
if (!(table= open_ltable(thd, &tables, TL_WRITE, 0)))
{
error= my_errno;
goto end;
}
if ((error= update_server_record(table, altered)))
goto end;
error= update_server_record_in_cache(existing, altered);
/*
Perform a reload so we don't have a 'hole' in our mem_root
*/
servers_load(thd, &tables);
end:
DBUG_RETURN(error);
}
/*
SYNOPSIS
update_server_record_in_cache()
FOREIGN_SERVER *existing
FOREIGN_SERVER *altered
NOTES
This function takes as an argument the FOREIGN_SERVER structi pointer
for the existing server and the FOREIGN_SERVER struct populated with only
the members which have been updated. It then "merges" the "altered" struct
members to the existing server, the existing server then represents an
updated server. Then, the existing record is deleted from the servers_cache
HASH, then the updated record inserted, in essence replacing the old
record.
THR_LOCK_servers must be write locked.
RETURN VALUE
0 - no error
1 - error
*/
int update_server_record_in_cache(FOREIGN_SERVER *existing,
FOREIGN_SERVER *altered)
{
int error= 0;
DBUG_ENTER("update_server_record_in_cache");
/*
update the members that haven't been change in the altered server struct
with the values of the existing server struct
*/
merge_server_struct(existing, altered);
/*
delete the existing server struct from the server cache
*/
VOID(hash_delete(&servers_cache, (uchar*)existing));
/*
Insert the altered server struct into the server cache
*/
if (my_hash_insert(&servers_cache, (uchar*)altered))
{
DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
altered->server_name, (long unsigned int) altered));
error= ER_OUT_OF_RESOURCES;
}
DBUG_RETURN(error);
}
/*
SYNOPSIS
merge_server_struct()
FOREIGN_SERVER *from
FOREIGN_SERVER *to
NOTES
This function takes as its arguments two pointers each to an allocated
FOREIGN_SERVER struct. The first FOREIGN_SERVER struct represents the struct
that we will obtain values from (hence the name "from"), the second
FOREIGN_SERVER struct represents which FOREIGN_SERVER struct we will be
"copying" any members that have a value to (hence the name "to")
RETURN VALUE
VOID
*/
void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to)
{
DBUG_ENTER("merge_server_struct");
if (!to->host)
to->host= strdup_root(&mem, from->host);
if (!to->db)
to->db= strdup_root(&mem, from->db);
if (!to->username)
to->username= strdup_root(&mem, from->username);
if (!to->password)
to->password= strdup_root(&mem, from->password);
if (to->port == -1)
to->port= from->port;
if (!to->socket && from->socket)
to->socket= strdup_root(&mem, from->socket);
if (!to->scheme && from->scheme)
to->scheme= strdup_root(&mem, from->scheme);
if (!to->owner)
to->owner= strdup_root(&mem, from->owner);
DBUG_VOID_RETURN;
}
/*
SYNOPSIS
update_server_record()
TABLE *table
FOREIGN_SERVER *server
NOTES
This function takes as its arguments an open TABLE pointer, and a pointer
to an allocated FOREIGN_SERVER structure representing an updated record
which needs to be inserted. The primary key, server_name is stored to field
0, then index_read_idx is called to read the index to that record, the
record then being ready to be updated, if found. If not found an error is
set and error message printed. If the record is found, store_record is
called, then store_server_fields stores each field from the the members of
the updated FOREIGN_SERVER struct.
RETURN VALUE
0 - no error
*/
static int
update_server_record(TABLE *table, FOREIGN_SERVER *server)
{
int error=0;
DBUG_ENTER("update_server_record");
table->use_all_columns();
/* set the field that's the PK to the value we're looking for */
table->field[0]->store(server->server_name,
server->server_name_length,
system_charset_info);
if ((error= table->file->index_read_idx_map(table->record[0], 0,
(uchar *)table->field[0]->ptr,
~(longlong)0,
HA_READ_KEY_EXACT)))
{
if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
table->file->print_error(error, MYF(0));
DBUG_PRINT("info",("server not found!"));
error= ER_FOREIGN_SERVER_DOESNT_EXIST;
}
else
{
/* ok, so we can update since the record exists in the table */
store_record(table,record[1]);
store_server_fields(table, server);
if ((error=table->file->ha_update_row(table->record[1],
table->record[0])) &&
error != HA_ERR_RECORD_IS_THE_SAME)
{
DBUG_PRINT("info",("problems with ha_update_row %d", error));
goto end;
}
else
error= 0;
}
end:
DBUG_RETURN(error);
}
/*
SYNOPSIS
delete_server_record()
TABLE *table
char *server_name
int server_name_length
NOTES
RETURN VALUE
0 - no error
*/
static int
delete_server_record(TABLE *table,
char *server_name, int server_name_length)
{
int error;
DBUG_ENTER("delete_server_record");
table->use_all_columns();
/* set the field that's the PK to the value we're looking for */
table->field[0]->store(server_name, server_name_length, system_charset_info);
if ((error= table->file->index_read_idx_map(table->record[0], 0,
(uchar *)table->field[0]->ptr,
HA_WHOLE_KEY,
HA_READ_KEY_EXACT)))
{
if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
table->file->print_error(error, MYF(0));
DBUG_PRINT("info",("server not found!"));
error= ER_FOREIGN_SERVER_DOESNT_EXIST;
}
else
{
if ((error= table->file->ha_delete_row(table->record[0])))
table->file->print_error(error, MYF(0));
}
DBUG_RETURN(error);
}
/*
SYNOPSIS
create_server()
THD *thd
LEX_SERVER_OPTIONS *server_options
NOTES
RETURN VALUE
0 - no error
*/
int create_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
{
int error= ER_FOREIGN_SERVER_EXISTS;
FOREIGN_SERVER *server;
DBUG_ENTER("create_server");
DBUG_PRINT("info", ("server_options->server_name %s",
server_options->server_name));
rw_wrlock(&THR_LOCK_servers);
/* hit the memory first */
if (hash_search(&servers_cache, (uchar*) server_options->server_name,
server_options->server_name_length))
goto end;
server= (FOREIGN_SERVER *)alloc_root(&mem,
sizeof(FOREIGN_SERVER));
prepare_server_struct_for_insert(server_options, server);
error= insert_server(thd, server);
DBUG_PRINT("info", ("error returned %d", error));
end:
rw_unlock(&THR_LOCK_servers);
DBUG_RETURN(error);
}
/*
SYNOPSIS
alter_server()
THD *thd
LEX_SERVER_OPTIONS *server_options
NOTES
RETURN VALUE
0 - no error
*/
int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
{
int error= ER_FOREIGN_SERVER_DOESNT_EXIST;
FOREIGN_SERVER *altered, *existing;
LEX_STRING name= { server_options->server_name,
server_options->server_name_length };
DBUG_ENTER("alter_server");
DBUG_PRINT("info", ("server_options->server_name %s",
server_options->server_name));
rw_wrlock(&THR_LOCK_servers);
if (!(existing= (FOREIGN_SERVER *) hash_search(&servers_cache,
(uchar*) name.str,
name.length)))
goto end;
altered= (FOREIGN_SERVER *)alloc_root(&mem,
sizeof(FOREIGN_SERVER));
prepare_server_struct_for_update(server_options, existing, altered);
error= update_server(thd, existing, altered);
/* close the servers table before we call closed_cached_connection_tables */
close_thread_tables(thd);
if (close_cached_connection_tables(thd, FALSE, &name))
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_UNKNOWN_ERROR, "Server connection in use");
}
end:
DBUG_PRINT("info", ("error returned %d", error));
rw_unlock(&THR_LOCK_servers);
DBUG_RETURN(error);
}
/*
SYNOPSIS
prepare_server_struct_for_insert()
LEX_SERVER_OPTIONS *server_options
FOREIGN_SERVER *server
NOTES
RETURN VALUE
none
*/
static void
prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options,
FOREIGN_SERVER *server)
{
char *unset_ptr= (char*)"";
DBUG_ENTER("prepare_server_struct");
/* these two MUST be set */
server->server_name= strdup_root(&mem, server_options->server_name);
server->server_name_length= server_options->server_name_length;
server->host= server_options->host ?
strdup_root(&mem, server_options->host) : unset_ptr;
server->db= server_options->db ?
strdup_root(&mem, server_options->db) : unset_ptr;
server->username= server_options->username ?
strdup_root(&mem, server_options->username) : unset_ptr;
server->password= server_options->password ?
strdup_root(&mem, server_options->password) : unset_ptr;
/* set to 0 if not specified */
server->port= server_options->port > -1 ?
server_options->port : 0;
server->socket= server_options->socket ?
strdup_root(&mem, server_options->socket) : unset_ptr;
server->scheme= server_options->scheme ?
strdup_root(&mem, server_options->scheme) : unset_ptr;
server->owner= server_options->owner ?
strdup_root(&mem, server_options->owner) : unset_ptr;
DBUG_VOID_RETURN;
}
/*
SYNOPSIS
prepare_server_struct_for_update()
LEX_SERVER_OPTIONS *server_options
NOTES
RETURN VALUE
0 - no error
*/
static void
prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options,
FOREIGN_SERVER *existing,
FOREIGN_SERVER *altered)
{
DBUG_ENTER("prepare_server_struct_for_update");
altered->server_name= strdup_root(&mem, server_options->server_name);
altered->server_name_length= server_options->server_name_length;
DBUG_PRINT("info", ("existing name %s altered name %s",
existing->server_name, altered->server_name));
/*
The logic here is this: is this value set AND is it different
than the existing value?
*/
altered->host=
(server_options->host && (strcmp(server_options->host, existing->host))) ?
strdup_root(&mem, server_options->host) : 0;
altered->db=
(server_options->db && (strcmp(server_options->db, existing->db))) ?
strdup_root(&mem, server_options->db) : 0;
altered->username=
(server_options->username &&
(strcmp(server_options->username, existing->username))) ?
strdup_root(&mem, server_options->username) : 0;
altered->password=
(server_options->password &&
(strcmp(server_options->password, existing->password))) ?
strdup_root(&mem, server_options->password) : 0;
/*
port is initialised to -1, so if unset, it will be -1
*/
altered->port= (server_options->port > -1 &&
server_options->port != existing->port) ?
server_options->port : -1;
altered->socket=
(server_options->socket &&
(strcmp(server_options->socket, existing->socket))) ?
strdup_root(&mem, server_options->socket) : 0;
altered->scheme=
(server_options->scheme &&
(strcmp(server_options->scheme, existing->scheme))) ?
strdup_root(&mem, server_options->scheme) : 0;
altered->owner=
(server_options->owner &&
(strcmp(server_options->owner, existing->owner))) ?
strdup_root(&mem, server_options->owner) : 0;
DBUG_VOID_RETURN;
}
/*
SYNOPSIS
servers_free()
bool end
NOTES
RETURN VALUE
void
*/
void servers_free(bool end)
{
DBUG_ENTER("servers_free");
if (!hash_inited(&servers_cache))
DBUG_VOID_RETURN;
if (!end)
{
free_root(&mem, MYF(MY_MARK_BLOCKS_FREE));
my_hash_reset(&servers_cache);
DBUG_VOID_RETURN;
}
rwlock_destroy(&THR_LOCK_servers);
free_root(&mem,MYF(0));
hash_free(&servers_cache);
DBUG_VOID_RETURN;
}
/*
SYNOPSIS
clone_server(MEM_ROOT *mem_root, FOREIGN_SERVER *orig, FOREIGN_SERVER *buff)
Create a clone of FOREIGN_SERVER. If the supplied mem_root is of
thd->mem_root then the copy is automatically disposed at end of statement.
NOTES
ARGS
MEM_ROOT pointer (strings are copied into this mem root)
FOREIGN_SERVER pointer (made a copy of)
FOREIGN_SERVER buffer (if not-NULL, this pointer is returned)
RETURN VALUE
FOREIGN_SEVER pointer (copy of one supplied FOREIGN_SERVER)
*/
static FOREIGN_SERVER *clone_server(MEM_ROOT *mem, const FOREIGN_SERVER *server,
FOREIGN_SERVER *buffer)
{
DBUG_ENTER("sql_server.cc:clone_server");
if (!buffer)
buffer= (FOREIGN_SERVER *) alloc_root(mem, sizeof(FOREIGN_SERVER));
buffer->server_name= strmake_root(mem, server->server_name,
server->server_name_length);
buffer->port= server->port;
buffer->server_name_length= server->server_name_length;
/* TODO: We need to examine which of these can really be NULL */
buffer->db= server->db ? strdup_root(mem, server->db) : NULL;
buffer->scheme= server->scheme ? strdup_root(mem, server->scheme) : NULL;
buffer->username= server->username? strdup_root(mem, server->username): NULL;
buffer->password= server->password? strdup_root(mem, server->password): NULL;
buffer->socket= server->socket ? strdup_root(mem, server->socket) : NULL;
buffer->owner= server->owner ? strdup_root(mem, server->owner) : NULL;
buffer->host= server->host ? strdup_root(mem, server->host) : NULL;
DBUG_RETURN(buffer);
}
/*
SYNOPSIS
get_server_by_name()
const char *server_name
NOTES
RETURN VALUE
FOREIGN_SERVER *
*/
FOREIGN_SERVER *get_server_by_name(MEM_ROOT *mem, const char *server_name,
FOREIGN_SERVER *buff)
{
uint server_name_length;
FOREIGN_SERVER *server;
DBUG_ENTER("get_server_by_name");
DBUG_PRINT("info", ("server_name %s", server_name));
server_name_length= strlen(server_name);
if (! server_name || !strlen(server_name))
{
DBUG_PRINT("info", ("server_name not defined!"));
DBUG_RETURN((FOREIGN_SERVER *)NULL);
}
DBUG_PRINT("info", ("locking servers_cache"));
rw_rdlock(&THR_LOCK_servers);
if (!(server= (FOREIGN_SERVER *) hash_search(&servers_cache,
(uchar*) server_name,
server_name_length)))
{
DBUG_PRINT("info", ("server_name %s length %d not found!",
server_name, server_name_length));
server= (FOREIGN_SERVER *) NULL;
}
/* otherwise, make copy of server */
else
server= clone_server(mem, server, buff);
DBUG_PRINT("info", ("unlocking servers_cache"));
rw_unlock(&THR_LOCK_servers);
DBUG_RETURN(server);
}