mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
1305 lines
37 KiB
C++
1305 lines
37 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 FOREIGN_SERVER *
|
|
prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options);
|
|
/* 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
|
|
|
|
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_alloc_root(&mem, ACL_ALLOC_BLOCK_SIZE, 0);
|
|
|
|
init_read_record(&read_record_info,thd,table=tables[0].table,NULL,1,0,
|
|
FALSE);
|
|
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;
|
|
|
|
|
|
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:
|
|
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
|
|
|
|
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)
|
|
{
|
|
char *unset_ptr= (char*)"";
|
|
FOREIGN_SERVER *server;
|
|
DBUG_ENTER("prepare_server_struct");
|
|
|
|
if (!(server= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER))))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
/* these two MUST be set */
|
|
if (!(server->server_name= strdup_root(&mem, server_options->server_name)))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
server->server_name_length= server_options->server_name_length;
|
|
|
|
if (!(server->host= server_options->host ?
|
|
strdup_root(&mem, server_options->host) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
if (!(server->db= server_options->db ?
|
|
strdup_root(&mem, server_options->db) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
if (!(server->username= server_options->username ?
|
|
strdup_root(&mem, server_options->username) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
if (!(server->password= server_options->password ?
|
|
strdup_root(&mem, server_options->password) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
/* set to 0 if not specified */
|
|
server->port= server_options->port > -1 ?
|
|
server_options->port : 0;
|
|
|
|
if (!(server->socket= server_options->socket ?
|
|
strdup_root(&mem, server_options->socket) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
if (!(server->scheme= server_options->scheme ?
|
|
strdup_root(&mem, server_options->scheme) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
if (!(server->owner= server_options->owner ?
|
|
strdup_root(&mem, server_options->owner) : unset_ptr))
|
|
DBUG_RETURN(NULL); /* purecov: inspected */
|
|
|
|
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= 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);
|
|
|
|
}
|