diff options
Diffstat (limited to 'sql/sql_servers.cc')
-rw-r--r-- | sql/sql_servers.cc | 1428 |
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); + +} |