summaryrefslogtreecommitdiffstats
path: root/sql/sql_servers.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sql/sql_servers.cc1428
1 files changed, 1428 insertions, 0 deletions
diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc
new file mode 100644
index 00000000..d52d6071
--- /dev/null
+++ b/sql/sql_servers.cc
@@ -0,0 +1,1428 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/*
+ 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 "mariadb.h"
+#include "sql_priv.h"
+#include "sql_servers.h"
+#include "unireg.h"
+#include "sql_base.h" // close_mysql_tables
+#include "records.h" // init_read_record, end_read_record
+#include <m_ctype.h>
+#include <stdarg.h>
+#include "sp_head.h"
+#include "sp.h"
+#include "transaction.h"
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+
+/*
+ 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 mysql_rwlock_t THR_LOCK_servers;
+static LEX_CSTRING MYSQL_SERVERS_NAME= {STRING_WITH_LEN("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 FOREIGN_SERVER *
+prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options);
+/* drop functions */
+static int delete_server_record(TABLE *table, LEX_CSTRING *name);
+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 %zd server_name %s",
+ server->server_name_length,
+ server->server_name));
+
+ *length= (uint) server->server_name_length;
+ DBUG_RETURN((uchar*) server->server_name);
+}
+
+static PSI_memory_key key_memory_servers;
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_rwlock_key key_rwlock_THR_LOCK_servers;
+
+static PSI_rwlock_info all_servers_cache_rwlocks[]=
+{
+ { &key_rwlock_THR_LOCK_servers, "THR_LOCK_servers", PSI_FLAG_GLOBAL}
+};
+
+static PSI_memory_info all_servers_cache_memory[]=
+{
+ { &key_memory_servers, "servers_cache", PSI_FLAG_GLOBAL}
+};
+
+static void init_servers_cache_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_servers_cache_rwlocks);
+ PSI_server->register_rwlock(category, all_servers_cache_rwlocks, count);
+
+ count= array_elements(all_servers_cache_memory);
+ mysql_memory_register(category, all_servers_cache_memory, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+
+struct close_cached_connection_tables_arg
+{
+ THD *thd;
+ LEX_CSTRING *connection;
+ TABLE_LIST *tables;
+};
+
+
+static my_bool close_cached_connection_tables_callback(
+ TDC_element *element, close_cached_connection_tables_arg *arg)
+{
+ TABLE_LIST *tmp;
+
+ mysql_mutex_lock(&element->LOCK_table_share);
+ /* Ignore if table is not open or does not have a connect_string */
+ if (!element->share || !element->share->connect_string.length ||
+ !element->ref_count)
+ goto end;
+
+ /* Compare the connection string */
+ if (arg->connection &&
+ (arg->connection->length > element->share->connect_string.length ||
+ (arg->connection->length < element->share->connect_string.length &&
+ (element->share->connect_string.str[arg->connection->length] != '/' &&
+ element->share->connect_string.str[arg->connection->length] != '\\')) ||
+ strncasecmp(arg->connection->str, element->share->connect_string.str,
+ arg->connection->length)))
+ goto end;
+
+ /* close_cached_tables() only uses these elements */
+ if (!(tmp= (TABLE_LIST*) alloc_root(arg->thd->mem_root, sizeof(TABLE_LIST))) ||
+ !(arg->thd->make_lex_string(&tmp->db, element->share->db.str, element->share->db.length)) ||
+ !(arg->thd->make_lex_string(&tmp->table_name, element->share->table_name.str,
+ element->share->table_name.length)))
+ {
+ mysql_mutex_unlock(&element->LOCK_table_share);
+ return TRUE;
+ }
+
+ tmp->next_global= tmp->next_local= arg->tables;
+ MDL_REQUEST_INIT(&tmp->mdl_request, MDL_key::TABLE, tmp->db.str,
+ tmp->table_name.str, MDL_EXCLUSIVE, MDL_TRANSACTION);
+ arg->tables= tmp;
+
+end:
+ mysql_mutex_unlock(&element->LOCK_table_share);
+ return FALSE;
+}
+
+
+/**
+ Close all tables which match specified connection string or
+ if specified string is NULL, then any table with a connection string.
+
+ @return false ok
+ @return true error, some tables may keep using old server info
+*/
+
+static bool close_cached_connection_tables(THD *thd, LEX_CSTRING *connection)
+{
+ close_cached_connection_tables_arg argument= { thd, connection, 0 };
+ DBUG_ENTER("close_cached_connections");
+
+ if (tdc_iterate(thd,
+ (my_hash_walk_action) close_cached_connection_tables_callback,
+ &argument))
+ DBUG_RETURN(true);
+
+ DBUG_RETURN(argument.tables ?
+ close_cached_tables(thd, argument.tables, true,
+ thd->variables.lock_wait_timeout) : false);
+}
+
+
+/*
+ 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");
+
+#ifdef HAVE_PSI_INTERFACE
+ init_servers_cache_psi_keys();
+#endif
+
+ /* init the mutex */
+ if (mysql_rwlock_init(key_rwlock_THR_LOCK_servers, &THR_LOCK_servers))
+ DBUG_RETURN(TRUE);
+
+ /* initialise our servers cache */
+ if (my_hash_init(key_memory_servers, &servers_cache, system_charset_info, 32, 0, 0,
+ (my_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_sql_alloc(key_memory_servers, &mem, ACL_ALLOC_BLOCK_SIZE, 0,
+ MYF(MY_THREAD_SPECIFIC));
+
+ if (dont_read_servers_table)
+ goto end;
+
+ /*
+ To be able to run this from boot, we allocate a temporary THD
+ */
+ if (!(thd=new THD(0)))
+ DBUG_RETURN(TRUE);
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+ /*
+ 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;
+
+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
+
+ TODO
+ Revert back to old list if we failed to load new one.
+*/
+
+static bool servers_load(THD *thd, TABLE_LIST *tables)
+{
+ TABLE *table;
+ READ_RECORD read_record_info;
+ bool return_val= TRUE;
+ DBUG_ENTER("servers_load");
+
+ my_hash_reset(&servers_cache);
+ free_root(&mem, MYF(0));
+ init_sql_alloc(key_memory_servers, &mem, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
+
+ if (init_read_record(&read_record_info,thd,table=tables[0].table, NULL, NULL,
+ 1,0, FALSE))
+ DBUG_RETURN(1);
+ while (!(read_record_info.read_record()))
+ {
+ /* 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");
+
+ DBUG_PRINT("info", ("locking servers_cache"));
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ tables[0].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_SERVERS_NAME, 0, TL_READ);
+
+ if (unlikely(open_and_lock_tables(thd, tables, FALSE,
+ MYSQL_LOCK_IGNORE_TIMEOUT)))
+ {
+ /*
+ Execution might have been interrupted; only print the error message
+ if an error condition has been raised.
+ */
+ if (thd->get_stmt_da()->is_error())
+ sql_print_error("Can't open and lock privilege tables: %s",
+ thd->get_stmt_da()->message());
+ return_val= FALSE;
+ 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_mysql_tables(thd);
+ DBUG_PRINT("info", ("unlocking servers_cache"));
+ mysql_rwlock_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 */
+ ptr= get_field(&mem, table->field[0]);
+ server->server_name= ptr ? ptr : blank;
+ server->server_name_length= (uint) 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 %p",
+ server->server_name, 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");
+
+ tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_SERVERS_NAME, 0, TL_WRITE);
+
+ /* need to open before acquiring THR_LOCK_plugin or it will deadlock */
+ if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ goto end;
+ table->file->row_logging= 0; // Don't log to binary log
+
+ /* insert the server into the table */
+ if (unlikely(error= insert_server_record(table, server)))
+ goto end;
+
+ /* insert the server into the cache */
+ if (unlikely((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 succeeded in insertion of the server to the table, now insert
+ the server to the cache
+ */
+ DBUG_PRINT("info", ("inserting server %s at %p, length %zd",
+ server->server_name, server,
+ server->server_name_length));
+ if (my_hash_insert(&servers_cache, (uchar*) server))
+ {
+ DBUG_PRINT("info", ("had a problem inserting server %s at %p",
+ server->server_name, 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");
+ DBUG_ASSERT(!table->file->row_logging);
+
+ 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 (unlikely((error=
+ table->file->ha_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 (unlikely(error=table->file->ha_write_row(table->record[0])))
+ table->file->print_error(error, MYF(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, 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
+*/
+
+static int drop_server_internal(THD *thd, LEX_SERVER_OPTIONS *server_options)
+{
+ int error;
+ TABLE_LIST tables;
+ TABLE *table;
+
+ DBUG_ENTER("drop_server_internal");
+ DBUG_PRINT("info", ("server name server->server_name %s",
+ server_options->server_name.str));
+
+ tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_SERVERS_NAME, 0, TL_WRITE);
+
+ /* hit the memory hit first */
+ if (unlikely((error= delete_server_record_in_cache(server_options))))
+ goto end;
+
+ if (unlikely(!(table= open_ltable(thd, &tables, TL_WRITE,
+ MYSQL_LOCK_IGNORE_TIMEOUT))))
+ {
+ error= my_errno;
+ goto end;
+ }
+
+ error= delete_server_record(table, &server_options->server_name);
+
+ /* close the servers table before we call closed_cached_connection_tables */
+ close_mysql_tables(thd);
+
+ if (close_cached_connection_tables(thd, &server_options->server_name))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR, "Server connection in use");
+ }
+
+end:
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Drop a server with servers cache mutex lock.
+*/
+int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
+{
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+ int rc= drop_server_internal(thd, server_options);
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ return rc;
+}
+
+
+/*
+
+ 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 %zu",
+ server_options->server_name.str,
+ server_options->server_name.length));
+
+
+ if (!(server= (FOREIGN_SERVER *)
+ my_hash_search(&servers_cache,
+ (uchar*) server_options->server_name.str,
+ server_options->server_name.length)))
+ {
+ DBUG_PRINT("info", ("server_name %s length %zu not found!",
+ server_options->server_name.str,
+ server_options->server_name.length));
+ goto end;
+ }
+ /*
+ We succeeded in deletion of the server to the table, now delete
+ the server from the cache
+ */
+ DBUG_PRINT("info",("deleting server %s length %zd",
+ server->server_name,
+ server->server_name_length));
+
+ my_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");
+
+ tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_SERVERS_NAME, 0, TL_WRITE);
+
+ if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ {
+ error= my_errno;
+ goto end;
+ }
+
+ if (unlikely((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
+ */
+ my_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 %p",
+ altered->server_name,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");
+ DBUG_ASSERT(!table->file->row_logging);
+
+ 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 (unlikely((error=
+ table->file->ha_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 (unlikely((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, LEX_CSTRING *name)
+{
+ int error;
+ DBUG_ENTER("delete_server_record");
+ DBUG_ASSERT(!table->file->row_logging);
+
+ table->use_all_columns();
+
+ /* set the field that's the PK to the value we're looking for */
+ table->field[0]->store(name->str, name->length, system_charset_info);
+
+ if (unlikely((error=
+ table->file->ha_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 (unlikely((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.str));
+
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ /* hit the memory first */
+ if (my_hash_search(&servers_cache, (uchar*) server_options->server_name.str,
+ server_options->server_name.length))
+ {
+ if (thd->lex->create_info.or_replace())
+ {
+ if (unlikely((error= drop_server_internal(thd, server_options))))
+ goto end;
+ }
+ else if (thd->lex->create_info.if_not_exists())
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_FOREIGN_SERVER_EXISTS,
+ ER_THD(thd, ER_FOREIGN_SERVER_EXISTS),
+ server_options->server_name.str);
+ error= 0;
+ goto end;
+ }
+ else
+ goto end;
+ }
+
+ if (!(server= prepare_server_struct_for_insert(server_options)))
+ {
+ /* purecov: begin inspected */
+ error= ER_OUT_OF_RESOURCES;
+ goto end;
+ /* purecov: end */
+ }
+
+ error= insert_server(thd, server);
+
+ DBUG_PRINT("info", ("error returned %d", error));
+
+end:
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+
+ if (unlikely(error))
+ {
+ DBUG_PRINT("info", ("problem creating server <%s>",
+ server_options->server_name.str));
+ my_error(error, MYF(0), server_options->server_name.str);
+ }
+ else
+ my_ok(thd);
+
+ 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;
+ DBUG_ENTER("alter_server");
+ DBUG_PRINT("info", ("server_options->server_name %s",
+ server_options->server_name.str));
+
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ if (!(existing= (FOREIGN_SERVER *) my_hash_search(&servers_cache,
+ (uchar*) server_options->server_name.str,
+ server_options->server_name.length)))
+ goto end;
+
+ 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_mysql_tables(thd);
+
+ if (close_cached_connection_tables(thd, &server_options->server_name))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR, "Server connection in use");
+ }
+
+end:
+ DBUG_PRINT("info", ("error returned %d", error));
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ prepare_server_struct_for_insert()
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+ As FOREIGN_SERVER members are allocated on mem_root, we do not need to
+ free them in case of error.
+
+ RETURN VALUE
+ On success filled FOREIGN_SERVER, or NULL in case out of memory.
+
+*/
+
+static FOREIGN_SERVER *
+prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options)
+{
+ FOREIGN_SERVER *server;
+ ulong default_port= 0;
+ DBUG_ENTER("prepare_server_struct");
+
+ if (!(server= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER))))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+#define SET_SERVER_OR_RETURN(X, DEFAULT) \
+ do { \
+ if (!(server->X= server_options->X.str ? \
+ strmake_root(&mem, server_options->X.str, \
+ server_options->X.length) : "")) \
+ DBUG_RETURN(NULL); \
+ } while(0)
+
+ /* name and scheme are always set (the parser guarantees it) */
+ SET_SERVER_OR_RETURN(server_name, NULL);
+ SET_SERVER_OR_RETURN(scheme, NULL);
+
+ /* scheme-specific checks */
+ if (!strcasecmp(server->scheme, "mysql"))
+ {
+ default_port= MYSQL_PORT;
+ if (!server_options->host.str && !server_options->socket.str)
+ {
+ my_error(ER_CANT_CREATE_FEDERATED_TABLE, MYF(0),
+ "either HOST or SOCKET must be set");
+ DBUG_RETURN(NULL);
+ }
+ }
+
+ SET_SERVER_OR_RETURN(host, "");
+ SET_SERVER_OR_RETURN(db, "");
+ SET_SERVER_OR_RETURN(username, "");
+ SET_SERVER_OR_RETURN(password, "");
+ SET_SERVER_OR_RETURN(socket, "");
+ SET_SERVER_OR_RETURN(owner, "");
+
+ server->server_name_length= server_options->server_name.length;
+
+ /* set to default_port if not specified */
+ server->port= server_options->port > -1 ?
+ server_options->port : default_port;
+
+ DBUG_RETURN(server);
+}
+
+/*
+
+ 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= existing->server_name;
+ altered->server_name_length= existing->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?
+ */
+#define SET_ALTERED(X) \
+ do { \
+ altered->X= \
+ (server_options->X.str && strcmp(server_options->X.str, existing->X)) \
+ ? strmake_root(&mem, server_options->X.str, server_options->X.length) \
+ : 0; \
+ } while(0)
+
+ SET_ALTERED(host);
+ SET_ALTERED(db);
+ SET_ALTERED(username);
+ SET_ALTERED(password);
+ SET_ALTERED(socket);
+ SET_ALTERED(scheme);
+ SET_ALTERED(owner);
+
+ /*
+ 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;
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+
+ SYNOPSIS
+ servers_free()
+ bool end
+
+ NOTES
+
+ RETURN VALUE
+ void
+
+*/
+
+void servers_free(bool end)
+{
+ DBUG_ENTER("servers_free");
+ if (!my_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;
+ }
+ mysql_rwlock_destroy(&THR_LOCK_servers);
+ free_root(&mem,MYF(0));
+ my_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= safe_strdup_root(mem, server->db);
+ buffer->scheme= safe_strdup_root(mem, server->scheme);
+ buffer->username= safe_strdup_root(mem, server->username);
+ buffer->password= safe_strdup_root(mem, server->password);
+ buffer->socket= safe_strdup_root(mem, server->socket);
+ buffer->owner= safe_strdup_root(mem, server->owner);
+ buffer->host= safe_strdup_root(mem, server->host);
+
+ 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)
+{
+ size_t 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"));
+ mysql_rwlock_rdlock(&THR_LOCK_servers);
+ if (!(server= (FOREIGN_SERVER *) my_hash_search(&servers_cache,
+ (uchar*) server_name,
+ server_name_length)))
+ {
+ DBUG_PRINT("info", ("server_name %s length %u not found!",
+ server_name, (unsigned) 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"));
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(server);
+
+}