mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 20:12:31 +01:00
5a28c2caca
Warning handling and initial prepared statement handling (last not complete yet) Changed a lot of functions that returned 0/1 to my_bool type. GRANT handling now uses read/write locks instead of mutex Change basic net functions to use THD instead of NET (needed for 4.1 protocol) Use my_sprintf instead of sprintf() + strlen() Added alloc_query() to be able to chare query initialization code with prepared statements. Cleanup handling of SHOW COUNT(*) WARNINGS and SELECT LAST_INSERT_ID() Note that the following test fails (will be fixed ASAP): sub_select, union, rpl_rotate_logs and rpl_mystery22 BitKeeper/deleted/.del-README~3449730baf983117: Delete: mysql-test/t/README BitKeeper/deleted/.del-sql_error.cc~2f1caca8d2485dbe: Delete: libmysqld/sql_error.cc BitKeeper/deleted/.del-sql_prepare.cc~f703729793935ed6: Delete: libmysqld/sql_prepare.cc Docs/manual.texi: Updated variable list client/mysql.cc: Show warning count to user. client/mysqltest.c: Add warnings to test results configure.in: New shared library version number include/errmsg.h: Indentation cleanup include/mysql.h: Removed MYSQL_ERROR Indentaion cleanups include/mysql_com.h: Changed functions to returns true/false to my_bool. include/mysqld_error.h: New error messages isam/pack_isam.c: Indentation change libmysql/Makefile.am: Fix of wrong merge libmysql/Makefile.shared: Indentation cleanup libmysql/errmsg.c: Removed not used errors libmysql/libmysql.c: Change functions to return 1 on error (not -1) Change type of functions that returns 0/1 to my_bool Lot of code optimizations. Lot of changes for prepared statements. This now handles sending of binary data to server. Receving of binary data is not yet done (will have to wait until server code for this is ready) mysql_warning_count and mysql_warnings() implemented. libmysql/libmysql.def: Added mysql_warnings and mysql_warning_count libmysql/manager.c: Fixed wrong testing of result from my_connect() libmysqld/lib_sql.cc: Removed global variable THR_NET Change basic net functions to use THD instead of NET GRANT handling now uses read/write locks instead of mutex libmysqld/libmysqld.c: Changed functions to be my_bool myisam/ft_boolean_search.c: Trivial code cleanup myisam/ft_stopwords.c: Trivial code cleanup myisam/mi_check.c: Update to 4.1 structures myisam/myisampack.c: Trivial code cleanup myisam/rt_key.c: Code cleanup myisam/rt_test.c: Code cleanup Removed compiler warnings myisam/sp_key.c: Indentation changes myisam/sp_test.c: Removed compiler warnings mysql-test/README: Updated to reflect the new --external flag. mysql-test/mysql-test-run.sh: --local (start new server) is now default. Use --external to test against external server. mysql-test/r/rollback.result: Updated for 4.1 warnings mysql-test/r/rpl_log.result: Update for 4.1 mysql-test/t/rollback.test: Updated for 4.1 warnings mysql-test/t/rpl_log_pos.test: Portability fix mysys/hash.c: Indentation change mysys/my_error.c: Indentation change mysys/tree.c: Updated file description sql/field.cc: Fixed bugs introduced by merge Use my_sprintf instead of sprintf() + strlen() sql/field.h: Add CHARSET_INFO to field structure sql/gstream.h: Indentation changes. Added GPL copyright header sql/ha_innodb.cc: Updated parameters for net functions. sql/item.cc: Updates of Item_param Indentation changes sql/item.h: Removed size_of() function from item. sql/item_func.cc: Update function usage for 4.1 Added get_system_var() sql/item_func.h: Indentation change sql/item_strfunc.cc: Removed not needed inclusion of gstream.h Update to use system variables (from 4.0) sql/item_sum.h: Removed size_of() functions from item. sql/item_timefunc.cc: Change sprintf() + strlen() -> my_sprintf() Added length parameter to ->append() sql/item_timefunc.h: Removed size_of() functions from item. sql/item_uniq.h: Removed size_of() functions from item. sql/lex.h: Removed SQL_ERROR_COUNT variable sql/log.cc: Change sprintf() + strlen() -> my_sprintf() sql/log_event.cc: Change sprintf() + strlen() -> my_sprintf() sql/mini_client.cc: Added check that one always specifies a length to mc_mysql_query() sql/mysql_priv.h: New prototypes Change of NET -> THD parameter for net functions. sql/mysqld.cc: New startup options: 'max_prepared_statements', 'max_error_count' Updated usage of net functions. sql/net_pkg.cc: Change basic net functions to use THD instead of NET (needed to be able to handle 4.0 and 4.1 protocols) Lots of function comments sql/net_serv.cc: Change int return values -> my_bool Updated net_write_command() to take an extra header block to be added to the packet. (This made the prepared statement code much nicer and more efficient) sql/repl_failsafe.cc: Update net functions to use THD instead of NET sql/set_var.cc: Added @@error_count and @@warning_count variables. Updated to 4.1 function usage sql/set_var.h: Added @@error_count and @@warning_count variables. sql/share/czech/errmsg.txt: Removed Warning: from warning error messages. sql/share/english/errmsg.txt: Removed Warning: from warning error messages. sql/share/greek/errmsg.txt: Removed Warning: from warning error messages. sql/share/hungarian/errmsg.txt: Removed Warning: from warning error messages. sql/share/japanese/errmsg.txt: Removed Warning: from warning error messages. sql/share/korean/errmsg.txt: Removed Warning: from warning error messages. sql/share/norwegian-ny/errmsg.txt: Removed Warning: from warning error messages. sql/share/norwegian/errmsg.txt: Removed Warning: from warning error messages. sql/share/polish/errmsg.txt: Removed Warning: from warning error messages. sql/share/romanian/errmsg.txt: Removed Warning: from warning error messages. sql/share/slovak/errmsg.txt: Removed Warning: from warning error messages. sql/share/swedish/errmsg.txt: Removed Warning: from warning error messages. sql/slave.cc: Change basic net functions to use THD instead of NET skip_load_data_file recoded to fit new client/server protocol sql/spatial.h: Added copyright header Indentation cleanups sql/sql_acl.cc: Change basic net functions to use THD instead of NET GRANT handling now uses read/write locks instead of mutex sql/sql_analyse.cc: Change basic net functions to use THD instead of NET sprintf() + strlen() -> my_sprintf() sql/sql_base.cc: More DBUG statements sql/sql_class.cc: Change basic net functions to use THD instead of NET warning and prepared statement handling sql/sql_class.h: Change basic net functions to use THD instead of NET warning and prepared statement handling sql/sql_db.cc: Code cleanup & optimization. sql/sql_delete.cc: Change basic net functions to use THD instead of NET sql/sql_derived.cc: Change basic net functions to use THD instead of NET sql/sql_do.cc: Change basic net functions to use THD instead of NET sql/sql_error.cc: Big rewrite of error handling. sql/sql_handler.cc: Change basic net functions to use THD instead of NET sql/sql_insert.cc: Change basic net functions to use THD instead of NET sql/sql_lex.cc: Change basic net functions to use THD instead of NET sql/sql_lex.h: Added param_count to st_select_lex_node sql/sql_list.h: Removed not needed error list. sql/sql_load.cc: Change basic net functions to use THD instead of NET sql/sql_parse.cc: Change basic net functions to use THD instead of NET Added alloc_query() to be able to chare query initialization code with prepared statements. Update of warning handling. Added create_select_for_variable() (for SHOW COUNT(*) WARNINGS) sql/sql_prepare.cc: Initial prepared statement handling sql/sql_rename.cc: Change basic net functions to use THD instead of NET sql/sql_repl.cc: Change basic net functions to use THD instead of NET sql/sql_select.cc: Small code cleanups Added missing initialization of error that caused some queries that returned an empty result set to fail sql/sql_select.h: Ensure that JOIN.error is properly initialized sql/sql_show.cc: Change basic net functions to use THD instead of NET A lot of optimization sql/sql_table.cc: Change basic net functions to use THD instead of NET Indentaion cleanup sql/sql_udf.cc: Change basic net functions to use THD instead of NET sql/sql_union.cc: Change basic net functions to use THD instead of NET sql/sql_update.cc: Change basic net functions to use THD instead of NET sql/sql_yacc.yy: Change basic net functions to use THD instead of NET Cleanup handling of SHOW COUNT(*) WARNINGS and SELECT LAST_INSERT_ID() sql/structs.h: Moved structures to files where they was used sql/table.cc: Don't accept empty database names sql/uniques.cc: Indentation cleanup sql/unireg.cc: Change basic net functions to use THD instead of NET sql/unireg.h: Added defaults for warnings and prepared statements strings/ctype-simple.c: optimization tests/client_test.c: Fixed wrong paramaters to printf()
2144 lines
60 KiB
C++
2144 lines
60 KiB
C++
/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
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 */
|
|
|
|
|
|
/* drop and alter of tables */
|
|
|
|
#include "mysql_priv.h"
|
|
#include <hash.h>
|
|
#include <myisam.h>
|
|
#include <assert.h>
|
|
|
|
#ifdef __WIN__
|
|
#include <io.h>
|
|
#endif
|
|
|
|
extern HASH open_cache;
|
|
static const char *primary_key_name="PRIMARY";
|
|
|
|
static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end);
|
|
static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end);
|
|
static int copy_data_between_tables(TABLE *from,TABLE *to,
|
|
List<create_field> &create,
|
|
enum enum_duplicates handle_duplicates,
|
|
ORDER *order,
|
|
ha_rows *copied,ha_rows *deleted);
|
|
|
|
/*****************************************************************************
|
|
** Remove all possbile tables and give a compact errormessage for all
|
|
** wrong tables.
|
|
** This will wait for all users to free the table before dropping it
|
|
*****************************************************************************/
|
|
|
|
int mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists)
|
|
{
|
|
int error;
|
|
DBUG_ENTER("mysql_rm_table");
|
|
|
|
/* mark for close and remove all cached entries */
|
|
|
|
thd->mysys_var->current_mutex= &LOCK_open;
|
|
thd->mysys_var->current_cond= &COND_refresh;
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
|
|
if (global_read_lock)
|
|
{
|
|
if (thd->global_read_lock)
|
|
{
|
|
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE,MYF(0),
|
|
tables->real_name);
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
while (global_read_lock && ! thd->killed)
|
|
{
|
|
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
|
|
}
|
|
|
|
}
|
|
error=mysql_rm_table_part2(thd,tables,if_exists,0);
|
|
|
|
err:
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh
|
|
|
|
pthread_mutex_lock(&thd->mysys_var->mutex);
|
|
thd->mysys_var->current_mutex= 0;
|
|
thd->mysys_var->current_cond= 0;
|
|
pthread_mutex_unlock(&thd->mysys_var->mutex);
|
|
|
|
if (error)
|
|
DBUG_RETURN(-1);
|
|
send_ok(thd);
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
int mysql_rm_table_part2_with_lock(THD *thd,
|
|
TABLE_LIST *tables, bool if_exists,
|
|
bool dont_log_query)
|
|
{
|
|
int error;
|
|
thd->mysys_var->current_mutex= &LOCK_open;
|
|
thd->mysys_var->current_cond= &COND_refresh;
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
|
|
error=mysql_rm_table_part2(thd,tables, if_exists, dont_log_query);
|
|
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh
|
|
|
|
pthread_mutex_lock(&thd->mysys_var->mutex);
|
|
thd->mysys_var->current_mutex= 0;
|
|
thd->mysys_var->current_cond= 0;
|
|
pthread_mutex_unlock(&thd->mysys_var->mutex);
|
|
return error;
|
|
}
|
|
|
|
|
|
int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
|
|
bool dont_log_query)
|
|
{
|
|
TABLE_LIST *table;
|
|
char path[FN_REFLEN];
|
|
String wrong_tables;
|
|
db_type table_type;
|
|
int error;
|
|
bool some_tables_deleted=0;
|
|
DBUG_ENTER("mysql_rm_table_part2");
|
|
|
|
for (table=tables ; table ; table=table->next)
|
|
{
|
|
char *db=table->db ? table->db : thd->db;
|
|
if (!close_temporary_table(thd, db, table->real_name))
|
|
{
|
|
some_tables_deleted=1; // Log query
|
|
continue; // removed temporary table
|
|
}
|
|
|
|
abort_locked_tables(thd,db,table->real_name);
|
|
while (remove_table_from_cache(thd,db,table->real_name) && !thd->killed)
|
|
{
|
|
dropping_tables++;
|
|
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
|
|
dropping_tables--;
|
|
}
|
|
drop_locked_tables(thd,db,table->real_name);
|
|
if (thd->killed)
|
|
DBUG_RETURN(-1);
|
|
|
|
/* remove form file and isam files */
|
|
(void) sprintf(path,"%s/%s/%s%s",mysql_data_home,db,table->real_name,
|
|
reg_ext);
|
|
(void) unpack_filename(path,path);
|
|
error=0;
|
|
|
|
table_type=get_table_type(path);
|
|
|
|
if (access(path,F_OK))
|
|
{
|
|
if (!if_exists)
|
|
error=1;
|
|
}
|
|
else
|
|
{
|
|
char *end;
|
|
*(end=fn_ext(path))=0; // Remove extension
|
|
error=ha_delete_table(table_type, path);
|
|
if (error == ENOENT && if_exists)
|
|
error = 0;
|
|
if (!error || error == ENOENT)
|
|
{
|
|
/* Delete the table definition file */
|
|
strmov(end,reg_ext);
|
|
if (!(error=my_delete(path,MYF(MY_WME))))
|
|
some_tables_deleted=1;
|
|
}
|
|
}
|
|
if (error)
|
|
{
|
|
if (wrong_tables.length())
|
|
wrong_tables.append(',');
|
|
wrong_tables.append(String(table->real_name,default_charset_info));
|
|
}
|
|
}
|
|
if (some_tables_deleted)
|
|
{
|
|
query_cache_invalidate3(thd, tables, 0);
|
|
if (!dont_log_query)
|
|
{
|
|
mysql_update_log.write(thd, thd->query,thd->query_length);
|
|
if (mysql_bin_log.is_open())
|
|
{
|
|
Query_log_event qinfo(thd, thd->query, thd->query_length);
|
|
mysql_bin_log.write(&qinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
error = 0;
|
|
if (wrong_tables.length())
|
|
{
|
|
my_error(ER_BAD_TABLE_ERROR,MYF(0),wrong_tables.c_ptr());
|
|
error=1;
|
|
}
|
|
DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
int quick_rm_table(enum db_type base,const char *db,
|
|
const char *table_name)
|
|
{
|
|
char path[FN_REFLEN];
|
|
int error=0;
|
|
(void) sprintf(path,"%s/%s/%s%s",mysql_data_home,db,table_name,reg_ext);
|
|
unpack_filename(path,path);
|
|
if (my_delete(path,MYF(0)))
|
|
error=1; /* purecov: inspected */
|
|
sprintf(path,"%s/%s/%s",mysql_data_home,db,table_name);
|
|
unpack_filename(path,path);
|
|
return ha_delete_table(base,path) || error;
|
|
}
|
|
|
|
/*
|
|
Sort keys in the following order:
|
|
- PRIMARY KEY
|
|
- UNIQUE keyws where all column are NOT NULL
|
|
- Other UNIQUE keys
|
|
- Normal keys
|
|
- Fulltext keys
|
|
|
|
This will make checking for duplicated keys faster and ensure that
|
|
PRIMARY keys are prioritized.
|
|
*/
|
|
|
|
static int sort_keys(KEY *a, KEY *b)
|
|
{
|
|
if (a->flags & HA_NOSAME)
|
|
{
|
|
if (!(b->flags & HA_NOSAME))
|
|
return -1;
|
|
if ((a->flags ^ b->flags) & HA_NULL_PART_KEY)
|
|
{
|
|
/* Sort NOT NULL keys before other keys */
|
|
return (a->flags & HA_NULL_PART_KEY) ? 1 : -1;
|
|
}
|
|
if (a->name == primary_key_name)
|
|
return -1;
|
|
if (b->name == primary_key_name)
|
|
return 1;
|
|
}
|
|
else if (b->flags & HA_NOSAME)
|
|
return 1; // Prefer b
|
|
|
|
if ((a->flags ^ b->flags) & HA_FULLTEXT)
|
|
{
|
|
return (a->flags & HA_FULLTEXT) ? 1 : -1;
|
|
}
|
|
/*
|
|
Prefer original key order. usable_key_parts contains here
|
|
the original key position.
|
|
*/
|
|
return ((a->usable_key_parts < b->usable_key_parts) ? -1 :
|
|
(a->usable_key_parts > b->usable_key_parts) ? 1 :
|
|
0);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* Create a table.
|
|
* If one creates a temporary table, this is automaticly opened
|
|
****************************************************************************/
|
|
|
|
int mysql_create_table(THD *thd,const char *db, const char *table_name,
|
|
HA_CREATE_INFO *create_info,
|
|
List<create_field> &fields,
|
|
List<Key> &keys,bool tmp_table,bool no_log,
|
|
uint select_field_count)
|
|
{
|
|
char path[FN_REFLEN];
|
|
const char *key_name;
|
|
create_field *sql_field,*dup_field;
|
|
int error= -1;
|
|
uint db_options,field,null_fields,blob_columns;
|
|
ulong pos;
|
|
KEY *key_info,*key_info_buffer;
|
|
KEY_PART_INFO *key_part_info;
|
|
int auto_increment=0;
|
|
handler *file;
|
|
int field_no,dup_no;
|
|
DBUG_ENTER("mysql_create_table");
|
|
|
|
/*
|
|
Check for duplicate fields and check type of table to create
|
|
*/
|
|
|
|
if (!fields.elements)
|
|
{
|
|
my_error(ER_TABLE_MUST_HAVE_COLUMNS,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
List_iterator<create_field> it(fields),it2(fields);
|
|
int select_field_pos=fields.elements - select_field_count;
|
|
null_fields=blob_columns=0;
|
|
db_options=create_info->table_options;
|
|
if (create_info->row_type == ROW_TYPE_DYNAMIC)
|
|
db_options|=HA_OPTION_PACK_RECORD;
|
|
file=get_new_handler((TABLE*) 0, create_info->db_type);
|
|
|
|
if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
|
|
(file->table_flags() & HA_NO_TEMP_TABLES))
|
|
{
|
|
my_error(ER_ILLEGAL_HA,MYF(0),table_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
for (field_no=0; (sql_field=it++) ; field_no++)
|
|
{
|
|
/* Don't pack keys in old tables if the user has requested this */
|
|
if ((sql_field->flags & BLOB_FLAG) ||
|
|
sql_field->sql_type == FIELD_TYPE_VAR_STRING &&
|
|
create_info->row_type != ROW_TYPE_FIXED)
|
|
{
|
|
db_options|=HA_OPTION_PACK_RECORD;
|
|
}
|
|
if (!(sql_field->flags & NOT_NULL_FLAG))
|
|
null_fields++;
|
|
|
|
/* Check if we have used the same field name before */
|
|
for (dup_no=0; (dup_field=it2++) != sql_field; dup_no++)
|
|
{
|
|
if (my_strcasecmp(system_charset_info,
|
|
sql_field->field_name,
|
|
dup_field->field_name) == 0)
|
|
{
|
|
/*
|
|
If this was a CREATE ... SELECT statement, accept a field
|
|
redefinition if we are changing a field in the SELECT part
|
|
*/
|
|
if (field_no < select_field_pos || dup_no >= select_field_pos)
|
|
{
|
|
my_error(ER_DUP_FIELDNAME,MYF(0),sql_field->field_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
else
|
|
{
|
|
/* Field redefined */
|
|
sql_field->length= dup_field->length;
|
|
sql_field->decimals= dup_field->decimals;
|
|
sql_field->flags= dup_field->flags;
|
|
sql_field->pack_length= dup_field->pack_length;
|
|
sql_field->unireg_check= dup_field->unireg_check;
|
|
sql_field->sql_type= dup_field->sql_type;
|
|
it2.remove(); // Remove first (create) definition
|
|
select_field_pos--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
it2.rewind();
|
|
}
|
|
/* If fixed row records, we need one bit to check for deleted rows */
|
|
if (!(db_options & HA_OPTION_PACK_RECORD))
|
|
null_fields++;
|
|
pos=(null_fields+7)/8;
|
|
|
|
it.rewind();
|
|
while ((sql_field=it++))
|
|
{
|
|
switch (sql_field->sql_type) {
|
|
case FIELD_TYPE_BLOB:
|
|
case FIELD_TYPE_MEDIUM_BLOB:
|
|
case FIELD_TYPE_TINY_BLOB:
|
|
case FIELD_TYPE_LONG_BLOB:
|
|
sql_field->pack_flag=FIELDFLAG_BLOB |
|
|
pack_length_to_packflag(sql_field->pack_length -
|
|
portable_sizeof_char_ptr);
|
|
if (sql_field->flags & BINARY_FLAG)
|
|
sql_field->pack_flag|=FIELDFLAG_BINARY;
|
|
sql_field->length=8; // Unireg field length
|
|
sql_field->unireg_check=Field::BLOB_FIELD;
|
|
blob_columns++;
|
|
break;
|
|
case FIELD_TYPE_VAR_STRING:
|
|
case FIELD_TYPE_STRING:
|
|
sql_field->pack_flag=0;
|
|
if (sql_field->flags & BINARY_FLAG)
|
|
sql_field->pack_flag|=FIELDFLAG_BINARY;
|
|
break;
|
|
case FIELD_TYPE_ENUM:
|
|
sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) |
|
|
FIELDFLAG_INTERVAL;
|
|
sql_field->unireg_check=Field::INTERVAL_FIELD;
|
|
break;
|
|
case FIELD_TYPE_SET:
|
|
sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) |
|
|
FIELDFLAG_BITFIELD;
|
|
sql_field->unireg_check=Field::BIT_FIELD;
|
|
break;
|
|
case FIELD_TYPE_DATE: // Rest of string types
|
|
case FIELD_TYPE_NEWDATE:
|
|
case FIELD_TYPE_TIME:
|
|
case FIELD_TYPE_DATETIME:
|
|
case FIELD_TYPE_NULL:
|
|
sql_field->pack_flag=f_settype((uint) sql_field->sql_type);
|
|
break;
|
|
case FIELD_TYPE_TIMESTAMP:
|
|
sql_field->unireg_check=Field::TIMESTAMP_FIELD;
|
|
/* fall through */
|
|
default:
|
|
sql_field->pack_flag=(FIELDFLAG_NUMBER |
|
|
(sql_field->flags & UNSIGNED_FLAG ? 0 :
|
|
FIELDFLAG_DECIMAL) |
|
|
(sql_field->flags & ZEROFILL_FLAG ?
|
|
FIELDFLAG_ZEROFILL : 0) |
|
|
f_settype((uint) sql_field->sql_type) |
|
|
(sql_field->decimals << FIELDFLAG_DEC_SHIFT));
|
|
break;
|
|
}
|
|
if (!(sql_field->flags & NOT_NULL_FLAG))
|
|
sql_field->pack_flag|=FIELDFLAG_MAYBE_NULL;
|
|
sql_field->offset= pos;
|
|
if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
|
|
auto_increment++;
|
|
if(!sql_field->charset)
|
|
sql_field->charset = create_info->table_charset ?
|
|
create_info->table_charset :
|
|
thd->db_charset? thd->db_charset :
|
|
default_charset_info;
|
|
pos+=sql_field->pack_length;
|
|
}
|
|
if (auto_increment > 1)
|
|
{
|
|
my_error(ER_WRONG_AUTO_KEY,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (auto_increment &&
|
|
(file->table_flags() & HA_NO_AUTO_INCREMENT))
|
|
{
|
|
my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
if (blob_columns && (file->table_flags() & HA_NO_BLOBS))
|
|
{
|
|
my_error(ER_TABLE_CANT_HANDLE_BLOB,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
/* Create keys */
|
|
|
|
List_iterator<Key> key_iterator(keys);
|
|
uint key_parts=0, key_count=0, fk_key_count=0;
|
|
List<Key> keys_in_order; // Add new keys here
|
|
bool primary_key=0,unique_key=0;
|
|
Key *key;
|
|
uint tmp, key_number;
|
|
|
|
/* Calculate number of key segements */
|
|
|
|
while ((key=key_iterator++))
|
|
{
|
|
if (key->type == Key::FOREIGN_KEY)
|
|
{
|
|
fk_key_count++;
|
|
foreign_key *fk_key= (foreign_key*) key;
|
|
if (fk_key->ref_columns.elements &&
|
|
fk_key->ref_columns.elements != fk_key->columns.elements)
|
|
{
|
|
my_error(ER_WRONG_FK_DEF, MYF(0), fk_key->name ? fk_key->name :
|
|
"foreign key without name",
|
|
ER(ER_KEY_REF_DO_NOT_MATCH_TABLE_REF));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
continue;
|
|
}
|
|
key_count++;
|
|
tmp=max(file->max_key_parts(),MAX_REF_PARTS);
|
|
if (key->columns.elements > tmp)
|
|
{
|
|
my_error(ER_TOO_MANY_KEY_PARTS,MYF(0),tmp);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (key->name && strlen(key->name) > NAME_LEN)
|
|
{
|
|
my_error(ER_TOO_LONG_IDENT, MYF(0), key->name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
key_parts+=key->columns.elements;
|
|
}
|
|
tmp=min(file->max_keys(), MAX_KEY);
|
|
if (key_count > tmp)
|
|
{
|
|
my_error(ER_TOO_MANY_KEYS,MYF(0),tmp);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
key_info_buffer=key_info=(KEY*) sql_calloc(sizeof(KEY)*key_count);
|
|
key_part_info=(KEY_PART_INFO*) sql_calloc(sizeof(KEY_PART_INFO)*key_parts);
|
|
if (!key_info_buffer || ! key_part_info)
|
|
DBUG_RETURN(-1); // Out of memory
|
|
|
|
key_iterator.rewind();
|
|
key_number=0;
|
|
for (; (key=key_iterator++) ; key_info++, key_number++)
|
|
{
|
|
uint key_length=0;
|
|
key_part_spec *column;
|
|
|
|
switch(key->type){
|
|
case Key::MULTIPLE:
|
|
key_info->flags = 0;
|
|
break;
|
|
case Key::FULLTEXT:
|
|
key_info->flags = HA_FULLTEXT;
|
|
break;
|
|
case Key::SPATIAL:
|
|
key_info->flags = HA_SPATIAL;
|
|
break;
|
|
case Key::FOREIGN_KEY:
|
|
key_number--; // Skip this key
|
|
continue;
|
|
default:
|
|
key_info->flags = HA_NOSAME;
|
|
}
|
|
|
|
key_info->key_parts=(uint8) key->columns.elements;
|
|
key_info->key_part=key_part_info;
|
|
key_info->usable_key_parts= key_number;
|
|
key_info->algorithm=key->algorithm;
|
|
|
|
/* TODO: Add proper checks if handler supports key_type and algorithm */
|
|
if (key->type == Key::FULLTEXT)
|
|
{
|
|
if (!(file->table_flags() & HA_CAN_FULLTEXT))
|
|
{
|
|
my_error(ER_TABLE_CANT_HANDLE_FULLTEXT, MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
/*
|
|
Make SPATIAL to be RTREE by default
|
|
SPATIAL only on BLOB or at least BINARY, this
|
|
actually should be replaced by special GEOM type
|
|
in near future when new frm file is ready
|
|
checking for proper key parts number:
|
|
*/
|
|
|
|
if (key_info->flags == HA_SPATIAL)
|
|
{
|
|
if (key_info->key_parts != 1)
|
|
{
|
|
my_printf_error(ER_WRONG_ARGUMENTS,
|
|
ER(ER_WRONG_ARGUMENTS),MYF(0),"SPATIAL INDEX");
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
else if (key_info->algorithm == HA_KEY_ALG_RTREE)
|
|
{
|
|
if ((key_info->key_parts & 1) == 1)
|
|
{
|
|
my_printf_error(ER_WRONG_ARGUMENTS,
|
|
ER(ER_WRONG_ARGUMENTS),MYF(0),"RTREE INDEX");
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
|
|
List_iterator<key_part_spec> cols(key->columns);
|
|
for (uint column_nr=0 ; (column=cols++) ; column_nr++)
|
|
{
|
|
it.rewind();
|
|
field=0;
|
|
while ((sql_field=it++) &&
|
|
my_strcasecmp(system_charset_info,
|
|
column->field_name,
|
|
sql_field->field_name))
|
|
field++;
|
|
if (!sql_field)
|
|
{
|
|
my_printf_error(ER_KEY_COLUMN_DOES_NOT_EXITS,
|
|
ER(ER_KEY_COLUMN_DOES_NOT_EXITS),MYF(0),
|
|
column->field_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (f_is_blob(sql_field->pack_flag))
|
|
{
|
|
if (!(file->table_flags() & HA_BLOB_KEY))
|
|
{
|
|
my_printf_error(ER_BLOB_USED_AS_KEY,ER(ER_BLOB_USED_AS_KEY),MYF(0),
|
|
column->field_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (!column->length)
|
|
{
|
|
if (key->type == Key::FULLTEXT)
|
|
column->length=1; /* ft-code ignores it anyway :-) */
|
|
else if (key->type == Key::SPATIAL)
|
|
{
|
|
/*
|
|
BAR: 4 is: (Xmin,Xmax,Ymin,Ymax), this is for 2D case
|
|
Lately we'll extend this code to support more dimensions
|
|
*/
|
|
column->length=4*sizeof(double);
|
|
}
|
|
else
|
|
{
|
|
my_printf_error(ER_BLOB_KEY_WITHOUT_LENGTH,
|
|
ER(ER_BLOB_KEY_WITHOUT_LENGTH),MYF(0),
|
|
column->field_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
}
|
|
if (!(sql_field->flags & NOT_NULL_FLAG))
|
|
{
|
|
if (key->type == Key::PRIMARY)
|
|
{
|
|
my_error(ER_PRIMARY_CANT_HAVE_NULL, MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (!(file->table_flags() & HA_NULL_KEY))
|
|
{
|
|
my_printf_error(ER_NULL_COLUMN_IN_INDEX,ER(ER_NULL_COLUMN_IN_INDEX),
|
|
MYF(0),column->field_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
key_info->flags|= HA_NULL_PART_KEY;
|
|
}
|
|
if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
|
|
{
|
|
if (column_nr == 0 || (file->table_flags() & HA_AUTO_PART_KEY))
|
|
auto_increment--; // Field is used
|
|
}
|
|
key_part_info->fieldnr= field;
|
|
key_part_info->offset= (uint16) sql_field->offset;
|
|
key_part_info->key_type=sql_field->pack_flag;
|
|
uint length=sql_field->pack_length;
|
|
if (column->length)
|
|
{
|
|
if (f_is_blob(sql_field->pack_flag))
|
|
{
|
|
if ((length=column->length) > file->max_key_length() ||
|
|
length > file->max_key_part_length())
|
|
{
|
|
my_error(ER_WRONG_SUB_KEY,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
else if (column->length > length ||
|
|
((f_is_packed(sql_field->pack_flag) ||
|
|
((file->table_flags() & HA_NO_PREFIX_CHAR_KEYS) &&
|
|
(key_info->flags & HA_NOSAME))) &&
|
|
column->length != length))
|
|
{
|
|
my_error(ER_WRONG_SUB_KEY,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (!(file->table_flags() & HA_NO_PREFIX_CHAR_KEYS))
|
|
length=column->length;
|
|
}
|
|
else if (length == 0)
|
|
{
|
|
my_printf_error(ER_WRONG_KEY_COLUMN, ER(ER_WRONG_KEY_COLUMN), MYF(0),
|
|
column->field_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
key_part_info->length=(uint8) length;
|
|
/* Use packed keys for long strings on the first column */
|
|
if (!(db_options & HA_OPTION_NO_PACK_KEYS) &&
|
|
(length >= KEY_DEFAULT_PACK_LENGTH &&
|
|
(sql_field->sql_type == FIELD_TYPE_STRING ||
|
|
sql_field->sql_type == FIELD_TYPE_VAR_STRING ||
|
|
sql_field->pack_flag & FIELDFLAG_BLOB)))
|
|
{
|
|
if (column_nr == 0 && (sql_field->pack_flag & FIELDFLAG_BLOB))
|
|
key_info->flags|= HA_BINARY_PACK_KEY;
|
|
else
|
|
key_info->flags|= HA_PACK_KEY;
|
|
}
|
|
key_length+=length;
|
|
key_part_info++;
|
|
|
|
/* Create the key name based on the first column (if not given) */
|
|
if (column_nr == 0)
|
|
{
|
|
if (key->type == Key::PRIMARY)
|
|
{
|
|
if (primary_key)
|
|
{
|
|
my_error(ER_MULTIPLE_PRI_KEY,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
key_name=primary_key_name;
|
|
primary_key=1;
|
|
}
|
|
else if (!(key_name = key->name))
|
|
key_name=make_unique_key_name(sql_field->field_name,
|
|
key_info_buffer,key_info);
|
|
if (check_if_keyname_exists(key_name,key_info_buffer,key_info))
|
|
{
|
|
my_error(ER_DUP_KEYNAME,MYF(0),key_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
key_info->name=(char*) key_name;
|
|
}
|
|
}
|
|
if (!(key_info->flags & HA_NULL_PART_KEY))
|
|
unique_key=1;
|
|
key_info->key_length=(uint16) key_length;
|
|
uint max_key_length= max(file->max_key_length(), MAX_KEY_LENGTH);
|
|
if (key_length > max_key_length && key->type != Key::FULLTEXT)
|
|
{
|
|
my_error(ER_TOO_LONG_KEY,MYF(0),max_key_length);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
if (!unique_key && !primary_key &&
|
|
(file->table_flags() & HA_REQUIRE_PRIMARY_KEY))
|
|
{
|
|
my_error(ER_REQUIRES_PRIMARY_KEY,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (auto_increment > 0)
|
|
{
|
|
my_error(ER_WRONG_AUTO_KEY,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
/* Sort keys in optimized order */
|
|
qsort((gptr) key_info_buffer, key_count, sizeof(KEY), (qsort_cmp) sort_keys);
|
|
|
|
/* Check if table exists */
|
|
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
|
|
{
|
|
sprintf(path,"%s%s%lx_%lx_%x%s",mysql_tmpdir,tmp_file_prefix,
|
|
current_pid, thd->thread_id, thd->tmp_table++,reg_ext);
|
|
create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE;
|
|
}
|
|
else
|
|
(void) sprintf(path,"%s/%s/%s%s",mysql_data_home,db,table_name,reg_ext);
|
|
unpack_filename(path,path);
|
|
/* Check if table already exists */
|
|
if ((create_info->options & HA_LEX_CREATE_TMP_TABLE)
|
|
&& find_temporary_table(thd,db,table_name))
|
|
{
|
|
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
|
|
DBUG_RETURN(0);
|
|
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
if (!tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE))
|
|
{
|
|
if (!access(path,F_OK))
|
|
{
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
|
|
DBUG_RETURN(0);
|
|
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
|
|
thd->proc_info="creating table";
|
|
|
|
create_info->create_statement = thd->query;
|
|
create_info->table_options=db_options;
|
|
if (rea_create_table(thd, path, create_info, fields, key_count,
|
|
key_info_buffer))
|
|
{
|
|
/* my_error(ER_CANT_CREATE_TABLE,MYF(0),table_name,my_errno); */
|
|
goto end;
|
|
}
|
|
if (!tmp_table && !no_log)
|
|
{
|
|
// Must be written before unlock
|
|
mysql_update_log.write(thd,thd->query, thd->query_length);
|
|
if (mysql_bin_log.is_open())
|
|
{
|
|
Query_log_event qinfo(thd, thd->query, thd->query_length);
|
|
mysql_bin_log.write(&qinfo);
|
|
}
|
|
}
|
|
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
|
|
{
|
|
/* Open table and put in temporary table list */
|
|
if (!(open_temporary_table(thd, path, db, table_name, 1)))
|
|
{
|
|
(void) rm_temporary_table(create_info->db_type, path);
|
|
goto end;
|
|
}
|
|
}
|
|
error=0;
|
|
end:
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
thd->proc_info="After create";
|
|
DBUG_RETURN(error);
|
|
}
|
|
|
|
/*
|
|
** Give the key name after the first field with an optional '_#' after
|
|
**/
|
|
|
|
static bool
|
|
check_if_keyname_exists(const char *name, KEY *start, KEY *end)
|
|
{
|
|
for (KEY *key=start ; key != end ; key++)
|
|
if (!my_strcasecmp(system_charset_info,name,key->name))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static char *
|
|
make_unique_key_name(const char *field_name,KEY *start,KEY *end)
|
|
{
|
|
char buff[MAX_FIELD_NAME],*buff_end;
|
|
|
|
if (!check_if_keyname_exists(field_name,start,end))
|
|
return (char*) field_name; // Use fieldname
|
|
buff_end=strmake(buff,field_name,MAX_FIELD_NAME-4);
|
|
for (uint i=2 ; ; i++)
|
|
{
|
|
sprintf(buff_end,"_%d",i);
|
|
if (!check_if_keyname_exists(buff,start,end))
|
|
return sql_strdup(buff);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
** Create table from a list of fields and items
|
|
****************************************************************************/
|
|
|
|
TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
|
|
const char *db, const char *name,
|
|
List<create_field> *extra_fields,
|
|
List<Key> *keys,
|
|
List<Item> *items,
|
|
MYSQL_LOCK **lock)
|
|
{
|
|
TABLE tmp_table; // Used during 'create_field()'
|
|
TABLE *table;
|
|
tmp_table.table_name=0;
|
|
uint select_field_count= items->elements;
|
|
DBUG_ENTER("create_table_from_items");
|
|
|
|
/* Add selected items to field list */
|
|
List_iterator_fast<Item> it(*items);
|
|
Item *item;
|
|
Field *tmp_field;
|
|
tmp_table.db_create_options=0;
|
|
tmp_table.null_row=tmp_table.maybe_null=0;
|
|
tmp_table.blob_ptr_size=portable_sizeof_char_ptr;
|
|
tmp_table.db_low_byte_first= test(create_info->db_type == DB_TYPE_MYISAM ||
|
|
create_info->db_type == DB_TYPE_HEAP);
|
|
|
|
while ((item=it++))
|
|
{
|
|
create_field *cr_field;
|
|
if (strlen(item->name) > NAME_LEN ||
|
|
check_column_name(item->name))
|
|
{
|
|
my_error(ER_WRONG_COLUMN_NAME,MYF(0),item->name);
|
|
DBUG_RETURN(0);
|
|
}
|
|
Field *field;
|
|
if (item->type() == Item::FUNC_ITEM)
|
|
field=item->tmp_table_field(&tmp_table);
|
|
else
|
|
field=create_tmp_field(thd, &tmp_table, item, item->type(),
|
|
(Item_result_field***) 0, &tmp_field,0,0);
|
|
if (!field ||
|
|
!(cr_field=new create_field(field,(item->type() == Item::FIELD_ITEM ?
|
|
((Item_field *)item)->field :
|
|
(Field*) 0))))
|
|
DBUG_RETURN(0);
|
|
extra_fields->push_back(cr_field);
|
|
}
|
|
/* create and lock table */
|
|
/* QQ: This should be done atomic ! */
|
|
if (mysql_create_table(thd,db,name,create_info,*extra_fields,
|
|
*keys,0,1,select_field_count)) // no logging
|
|
DBUG_RETURN(0);
|
|
if (!(table=open_table(thd,db,name,name,(bool*) 0)))
|
|
{
|
|
quick_rm_table(create_info->db_type,db,name);
|
|
DBUG_RETURN(0);
|
|
}
|
|
table->reginfo.lock_type=TL_WRITE;
|
|
if (!((*lock)=mysql_lock_tables(thd,&table,1)))
|
|
{
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
hash_delete(&open_cache,(byte*) table);
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
quick_rm_table(create_info->db_type,db,name);
|
|
DBUG_RETURN(0);
|
|
}
|
|
table->file->extra(HA_EXTRA_WRITE_CACHE);
|
|
DBUG_RETURN(table);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
** Alter a table definition
|
|
****************************************************************************/
|
|
|
|
bool
|
|
mysql_rename_table(enum db_type base,
|
|
const char *old_db,
|
|
const char * old_name,
|
|
const char *new_db,
|
|
const char * new_name)
|
|
{
|
|
char from[FN_REFLEN],to[FN_REFLEN];
|
|
handler *file=get_new_handler((TABLE*) 0, base);
|
|
int error=0;
|
|
DBUG_ENTER("mysql_rename_table");
|
|
(void) sprintf(from,"%s/%s/%s",mysql_data_home,old_db,old_name);
|
|
(void) sprintf(to,"%s/%s/%s",mysql_data_home,new_db,new_name);
|
|
fn_format(from,from,"","",4);
|
|
fn_format(to,to, "","",4);
|
|
if (!(error=file->rename_table((const char*) from,(const char *) to)))
|
|
{
|
|
if (rename_file_ext(from,to,reg_ext))
|
|
{
|
|
error=my_errno;
|
|
/* Restore old file name */
|
|
file->rename_table((const char*) to,(const char *) from);
|
|
}
|
|
}
|
|
delete file;
|
|
if (error)
|
|
my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error);
|
|
DBUG_RETURN(error != 0);
|
|
}
|
|
|
|
/*
|
|
close table in this thread and force close + reopen in other threads
|
|
This assumes that the calling thread has lock on LOCK_open
|
|
Win32 clients must also have a WRITE LOCK on the table !
|
|
*/
|
|
|
|
bool close_cached_table(THD *thd,TABLE *table)
|
|
{
|
|
bool result=0;
|
|
DBUG_ENTER("close_cached_table");
|
|
safe_mutex_assert_owner(&LOCK_open);
|
|
|
|
if (table)
|
|
{
|
|
DBUG_PRINT("enter",("table: %s", table->table_name));
|
|
VOID(table->file->extra(HA_EXTRA_FORCE_REOPEN)); // Close all data files
|
|
/* Mark all tables that are in use as 'old' */
|
|
mysql_lock_abort(thd,table); // end threads waiting on lock
|
|
|
|
#if defined(USING_TRANSACTIONS) || defined( __WIN__) || defined( __EMX__) || !defined(OS2)
|
|
/* Wait until all there are no other threads that has this table open */
|
|
while (remove_table_from_cache(thd,table->table_cache_key,
|
|
table->table_name))
|
|
{
|
|
dropping_tables++;
|
|
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
|
|
dropping_tables--;
|
|
}
|
|
#else
|
|
(void) remove_table_from_cache(thd,table->table_cache_key,
|
|
table->table_name);
|
|
#endif
|
|
/* When lock on LOCK_open is freed other threads can continue */
|
|
pthread_cond_broadcast(&COND_refresh);
|
|
|
|
/* Close lock if this is not got with LOCK TABLES */
|
|
if (thd->lock)
|
|
{
|
|
mysql_unlock_tables(thd, thd->lock); thd->lock=0; // Start locked threads
|
|
}
|
|
/* Close all copies of 'table'. This also frees all LOCK TABLES lock */
|
|
thd->open_tables=unlink_open_table(thd,thd->open_tables,table);
|
|
}
|
|
DBUG_RETURN(result);
|
|
}
|
|
|
|
static int send_check_errmsg(THD* thd, TABLE_LIST* table,
|
|
const char* operator_name, const char* errmsg)
|
|
|
|
{
|
|
|
|
String* packet = &thd->packet;
|
|
packet->length(0);
|
|
net_store_data(packet, table->name);
|
|
net_store_data(packet, (char*)operator_name);
|
|
net_store_data(packet, "error");
|
|
net_store_data(packet, errmsg);
|
|
thd->net.last_error[0]=0;
|
|
if (my_net_write(&thd->net, (char*) thd->packet.ptr(),
|
|
packet->length()))
|
|
return -1;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int prepare_for_restore(THD* thd, TABLE_LIST* table,
|
|
HA_CHECK_OPT *check_opt)
|
|
{
|
|
DBUG_ENTER("prepare_for_restore");
|
|
|
|
if (table->table) // do not overwrite existing tables on restore
|
|
{
|
|
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
|
|
"table exists, will not overwrite on restore"
|
|
));
|
|
}
|
|
else
|
|
{
|
|
char* backup_dir = thd->lex.backup_dir;
|
|
char src_path[FN_REFLEN], dst_path[FN_REFLEN];
|
|
char* table_name = table->name;
|
|
char* db = thd->db ? thd->db : table->db;
|
|
|
|
if (fn_format_relative_to_data_home(src_path, table_name, backup_dir,
|
|
reg_ext))
|
|
DBUG_RETURN(-1); // protect buffer overflow
|
|
|
|
sprintf(dst_path, "%s/%s/%s", mysql_real_data_home, db, table_name);
|
|
|
|
if (lock_and_wait_for_table_name(thd,table))
|
|
DBUG_RETURN(-1);
|
|
|
|
if (my_copy(src_path,
|
|
fn_format(dst_path, dst_path,"", reg_ext, 4),
|
|
MYF(MY_WME)))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
|
|
"Failed copying .frm file"));
|
|
}
|
|
if (mysql_truncate(thd, table, 1))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
|
|
"Failed generating table from .frm file"));
|
|
}
|
|
}
|
|
|
|
/*
|
|
Now we should be able to open the partially restored table
|
|
to finish the restore in the handler later on
|
|
*/
|
|
if (!(table->table = reopen_name_locked_table(thd, table)))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
}
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
static int prepare_for_repair(THD* thd, TABLE_LIST* table,
|
|
HA_CHECK_OPT *check_opt)
|
|
{
|
|
DBUG_ENTER("prepare_for_repair");
|
|
|
|
if (!(check_opt->sql_flags & TT_USEFRM))
|
|
{
|
|
DBUG_RETURN(0);
|
|
}
|
|
else
|
|
{
|
|
|
|
char from[FN_REFLEN],tmp[FN_REFLEN];
|
|
char* db = thd->db ? thd->db : table->db;
|
|
|
|
sprintf(from, "%s/%s/%s", mysql_real_data_home, db, table->name);
|
|
fn_format(from, from, "", MI_NAME_DEXT, 4);
|
|
sprintf(tmp,"%s-%lx_%lx", from, current_pid, thd->thread_id);
|
|
|
|
close_cached_table(thd,table->table);
|
|
|
|
if (lock_and_wait_for_table_name(thd,table))
|
|
DBUG_RETURN(-1);
|
|
|
|
if (my_rename(from, tmp, MYF(MY_WME)))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
DBUG_RETURN(send_check_errmsg(thd, table, "repair",
|
|
"Failed renaming .MYD file"));
|
|
}
|
|
if (mysql_truncate(thd, table, 1))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
DBUG_RETURN(send_check_errmsg(thd, table, "repair",
|
|
"Failed generating table from .frm file"));
|
|
}
|
|
if (my_rename(tmp, from, MYF(MY_WME)))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
DBUG_RETURN(send_check_errmsg(thd, table, "repair",
|
|
"Failed restoring .MYD file"));
|
|
}
|
|
}
|
|
|
|
/*
|
|
Now we should be able to open the partially repaired table
|
|
to finish the repair in the handler later on.
|
|
*/
|
|
if (!(table->table = reopen_name_locked_table(thd, table)))
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
unlock_table_name(thd, table);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
}
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
static int mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
|
HA_CHECK_OPT* check_opt,
|
|
const char *operator_name,
|
|
thr_lock_type lock_type,
|
|
bool open_for_modify,
|
|
uint extra_open_options,
|
|
int (*prepare_func)(THD *, TABLE_LIST *,
|
|
HA_CHECK_OPT *),
|
|
int (handler::*operator_func)
|
|
(THD *, HA_CHECK_OPT *))
|
|
{
|
|
TABLE_LIST *table;
|
|
List<Item> field_list;
|
|
Item* item;
|
|
String* packet = &thd->packet;
|
|
DBUG_ENTER("mysql_admin_table");
|
|
|
|
field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2));
|
|
item->maybe_null = 1;
|
|
field_list.push_back(item = new Item_empty_string("Op", 10));
|
|
item->maybe_null = 1;
|
|
field_list.push_back(item = new Item_empty_string("Msg_type", 10));
|
|
item->maybe_null = 1;
|
|
field_list.push_back(item = new Item_empty_string("Msg_text", 255));
|
|
item->maybe_null = 1;
|
|
if (send_fields(thd, field_list, 1))
|
|
DBUG_RETURN(-1);
|
|
|
|
for (table = tables; table; table = table->next)
|
|
{
|
|
char table_name[NAME_LEN*2+2];
|
|
char* db = (table->db) ? table->db : thd->db;
|
|
bool fatal_error=0;
|
|
strxmov(table_name,db ? db : "",".",table->name,NullS);
|
|
|
|
thd->open_options|= extra_open_options;
|
|
table->table = open_ltable(thd, table, lock_type);
|
|
thd->open_options&= ~extra_open_options;
|
|
packet->length(0);
|
|
if (prepare_func)
|
|
{
|
|
switch ((*prepare_func)(thd, table, check_opt)) {
|
|
case 1: continue; // error, message written to net
|
|
case -1: goto err; // error, message could be written to net
|
|
default: ; // should be 0 otherwise
|
|
}
|
|
}
|
|
|
|
if (!table->table)
|
|
{
|
|
const char *err_msg;
|
|
net_store_data(packet, table_name);
|
|
net_store_data(packet, operator_name);
|
|
net_store_data(packet, "error");
|
|
if (!(err_msg=thd->net.last_error))
|
|
err_msg=ER(ER_CHECK_NO_SUCH_TABLE);
|
|
net_store_data(packet, err_msg);
|
|
thd->net.last_error[0]=0;
|
|
if (my_net_write(&thd->net, (char*) thd->packet.ptr(),
|
|
packet->length()))
|
|
goto err;
|
|
continue;
|
|
}
|
|
if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify)
|
|
{
|
|
char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE];
|
|
net_store_data(packet, table_name);
|
|
net_store_data(packet, operator_name);
|
|
net_store_data(packet, "error");
|
|
sprintf(buff, ER(ER_OPEN_AS_READONLY), table_name);
|
|
net_store_data(packet, buff);
|
|
close_thread_tables(thd);
|
|
table->table=0; // For query cache
|
|
if (my_net_write(&thd->net, (char*) thd->packet.ptr(),
|
|
packet->length()))
|
|
goto err;
|
|
continue;
|
|
}
|
|
|
|
/* Close all instances of the table to allow repair to rename files */
|
|
if (lock_type == TL_WRITE && table->table->version)
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open,
|
|
"Waiting to get writelock");
|
|
mysql_lock_abort(thd,table->table);
|
|
while (remove_table_from_cache(thd, table->table->table_cache_key,
|
|
table->table->real_name) &&
|
|
! thd->killed)
|
|
{
|
|
dropping_tables++;
|
|
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
|
|
dropping_tables--;
|
|
}
|
|
thd->exit_cond(old_message);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
if (thd->killed)
|
|
goto err;
|
|
open_for_modify=0;
|
|
}
|
|
|
|
int result_code = (table->table->file->*operator_func)(thd, check_opt);
|
|
packet->length(0);
|
|
net_store_data(packet, table_name);
|
|
net_store_data(packet, operator_name);
|
|
|
|
switch (result_code) {
|
|
case HA_ADMIN_NOT_IMPLEMENTED:
|
|
net_store_data(packet, "error");
|
|
net_store_data(packet, ER(ER_CHECK_NOT_IMPLEMENTED));
|
|
break;
|
|
|
|
case HA_ADMIN_OK:
|
|
net_store_data(packet, "status");
|
|
net_store_data(packet, "OK");
|
|
break;
|
|
|
|
case HA_ADMIN_FAILED:
|
|
net_store_data(packet, "status");
|
|
net_store_data(packet, "Operation failed");
|
|
break;
|
|
|
|
case HA_ADMIN_ALREADY_DONE:
|
|
net_store_data(packet, "status");
|
|
net_store_data(packet, "Table is already up to date");
|
|
break;
|
|
|
|
case HA_ADMIN_CORRUPT:
|
|
net_store_data(packet, "error");
|
|
net_store_data(packet, "Corrupt");
|
|
fatal_error=1;
|
|
break;
|
|
|
|
case HA_ADMIN_INVALID:
|
|
net_store_data(packet, "error");
|
|
net_store_data(packet, "Invalid argument");
|
|
break;
|
|
|
|
default: // Probably HA_ADMIN_INTERNAL_ERROR
|
|
net_store_data(packet, "error");
|
|
net_store_data(packet, "Unknown - internal error during operation");
|
|
fatal_error=1;
|
|
break;
|
|
}
|
|
if (fatal_error)
|
|
table->table->version=0; // Force close of table
|
|
else if (open_for_modify)
|
|
{
|
|
pthread_mutex_lock(&LOCK_open);
|
|
remove_table_from_cache(thd, table->table->table_cache_key,
|
|
table->table->real_name);
|
|
pthread_mutex_unlock(&LOCK_open);
|
|
/* May be something modified consequently we have to invalidate cache */
|
|
query_cache_invalidate3(thd, table->table, 0);
|
|
}
|
|
close_thread_tables(thd);
|
|
table->table=0; // For query cache
|
|
if (my_net_write(&thd->net, (char*) packet->ptr(),
|
|
packet->length()))
|
|
goto err;
|
|
}
|
|
|
|
send_eof(thd);
|
|
DBUG_RETURN(0);
|
|
err:
|
|
close_thread_tables(thd); // Shouldn't be needed
|
|
if (table)
|
|
table->table=0;
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
|
|
int mysql_backup_table(THD* thd, TABLE_LIST* table_list)
|
|
{
|
|
DBUG_ENTER("mysql_backup_table");
|
|
DBUG_RETURN(mysql_admin_table(thd, table_list, 0,
|
|
"backup", TL_READ, 0, 0, 0,
|
|
&handler::backup));
|
|
}
|
|
|
|
|
|
int mysql_restore_table(THD* thd, TABLE_LIST* table_list)
|
|
{
|
|
DBUG_ENTER("mysql_restore_table");
|
|
DBUG_RETURN(mysql_admin_table(thd, table_list, 0,
|
|
"restore", TL_WRITE, 1, 0,
|
|
&prepare_for_restore,
|
|
&handler::restore));
|
|
}
|
|
|
|
|
|
int mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
|
|
{
|
|
DBUG_ENTER("mysql_repair_table");
|
|
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
|
|
"repair", TL_WRITE, 1, HA_OPEN_FOR_REPAIR,
|
|
&prepare_for_repair,
|
|
&handler::repair));
|
|
}
|
|
|
|
|
|
int mysql_optimize_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
|
|
{
|
|
DBUG_ENTER("mysql_optimize_table");
|
|
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
|
|
"optimize", TL_WRITE, 1,0,0,
|
|
&handler::optimize));
|
|
}
|
|
|
|
|
|
int mysql_analyze_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
|
|
{
|
|
#ifdef OS2
|
|
thr_lock_type lock_type = TL_WRITE;
|
|
#else
|
|
thr_lock_type lock_type = TL_READ_NO_INSERT;
|
|
#endif
|
|
|
|
DBUG_ENTER("mysql_analyze_table");
|
|
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
|
|
"analyze", lock_type, 1,0,0,
|
|
&handler::analyze));
|
|
}
|
|
|
|
|
|
int mysql_check_table(THD* thd, TABLE_LIST* tables,HA_CHECK_OPT* check_opt)
|
|
{
|
|
#ifdef OS2
|
|
thr_lock_type lock_type = TL_WRITE;
|
|
#else
|
|
thr_lock_type lock_type = TL_READ_NO_INSERT;
|
|
#endif
|
|
|
|
DBUG_ENTER("mysql_check_table");
|
|
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
|
|
"check", lock_type,
|
|
0, HA_OPEN_FOR_REPAIR, 0,
|
|
&handler::check));
|
|
}
|
|
|
|
|
|
int mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
|
HA_CREATE_INFO *create_info,
|
|
TABLE_LIST *table_list,
|
|
List<create_field> &fields,
|
|
List<Key> &keys,List<Alter_drop> &drop_list,
|
|
List<Alter_column> &alter_list,
|
|
ORDER *order,
|
|
bool drop_primary,
|
|
enum enum_duplicates handle_duplicates,
|
|
enum enum_enable_or_disable keys_onoff,
|
|
bool simple_alter)
|
|
{
|
|
TABLE *table,*new_table;
|
|
int error;
|
|
char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN],
|
|
*table_name,*db;
|
|
char index_file[FN_REFLEN], data_file[FN_REFLEN];
|
|
bool use_timestamp=0;
|
|
ha_rows copied,deleted;
|
|
ulonglong next_insert_id;
|
|
uint save_time_stamp,db_create_options, used_fields;
|
|
enum db_type old_db_type,new_db_type;
|
|
DBUG_ENTER("mysql_alter_table");
|
|
|
|
thd->proc_info="init";
|
|
table_name=table_list->real_name;
|
|
db=table_list->db;
|
|
if (!new_db || !strcmp(new_db,db))
|
|
new_db=db;
|
|
used_fields=create_info->used_fields;
|
|
|
|
if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ)))
|
|
DBUG_RETURN(-1);
|
|
|
|
/* Check that we are not trying to rename to an existing table */
|
|
if (new_name)
|
|
{
|
|
strmov(new_name_buff,new_name);
|
|
fn_same(new_name_buff,table_name,3);
|
|
if (lower_case_table_names)
|
|
my_casedn_str(system_charset_info,new_name);
|
|
if ((lower_case_table_names &&
|
|
!my_strcasecmp(system_charset_info, new_name_buff,table_name)) ||
|
|
(!lower_case_table_names &&
|
|
!strcmp(new_name_buff,table_name)))
|
|
new_name=table_name; // No. Make later check easier
|
|
else
|
|
{
|
|
if (table->tmp_table)
|
|
{
|
|
if (find_temporary_table(thd,new_db,new_name_buff))
|
|
{
|
|
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),new_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!access(fn_format(new_name_buff,new_name_buff,new_db,reg_ext,0),
|
|
F_OK))
|
|
{
|
|
/* Table will be closed in do_command() */
|
|
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),new_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
new_name=table_name;
|
|
|
|
old_db_type=table->db_type;
|
|
if (create_info->db_type == DB_TYPE_DEFAULT)
|
|
create_info->db_type=old_db_type;
|
|
new_db_type=create_info->db_type= ha_checktype(create_info->db_type);
|
|
if (create_info->row_type == ROW_TYPE_NOT_USED)
|
|
create_info->row_type=table->row_type;
|
|
|
|
/* In some simple cases we need not to recreate the table */
|
|
|
|
thd->proc_info="setup";
|
|
if (simple_alter && !table->tmp_table)
|
|
{
|
|
error=0;
|
|
if (new_name != table_name || new_db != db)
|
|
{
|
|
thd->proc_info="rename";
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
/* Then do a 'simple' rename of the table */
|
|
error=0;
|
|
if (!access(new_name_buff,F_OK))
|
|
{
|
|
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),new_name);
|
|
error= -1;
|
|
}
|
|
else
|
|
{
|
|
*fn_ext(new_name)=0;
|
|
close_cached_table(thd,table);
|
|
if (mysql_rename_table(old_db_type,db,table_name,new_db,new_name))
|
|
error= -1;
|
|
}
|
|
VOID(pthread_cond_broadcast(&COND_refresh));
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
}
|
|
if (!error)
|
|
{
|
|
switch (keys_onoff) {
|
|
case LEAVE_AS_IS:
|
|
break;
|
|
case ENABLE:
|
|
error=table->file->activate_all_index(thd);
|
|
break;
|
|
case DISABLE:
|
|
table->file->deactivate_non_unique_index(HA_POS_ERROR);
|
|
break;
|
|
}
|
|
}
|
|
if (!error)
|
|
{
|
|
mysql_update_log.write(thd, thd->query, thd->query_length);
|
|
if (mysql_bin_log.is_open())
|
|
{
|
|
Query_log_event qinfo(thd, thd->query, thd->query_length);
|
|
mysql_bin_log.write(&qinfo);
|
|
}
|
|
send_ok(thd);
|
|
}
|
|
DBUG_RETURN(error);
|
|
}
|
|
|
|
/* Full alter table */
|
|
restore_record(table,2); // Empty record for DEFAULT
|
|
List_iterator<Alter_drop> drop_it(drop_list);
|
|
List_iterator<create_field> def_it(fields);
|
|
List_iterator<Alter_column> alter_it(alter_list);
|
|
List<create_field> create_list; // Add new fields here
|
|
List<Key> key_list; // Add new keys here
|
|
|
|
/*
|
|
First collect all fields from table which isn't in drop_list
|
|
*/
|
|
|
|
create_field *def;
|
|
Field **f_ptr,*field;
|
|
for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
|
|
{
|
|
/* Check if field should be droped */
|
|
Alter_drop *drop;
|
|
drop_it.rewind();
|
|
while ((drop=drop_it++))
|
|
{
|
|
if (drop->type == Alter_drop::COLUMN &&
|
|
!my_strcasecmp(system_charset_info,field->field_name, drop->name))
|
|
{
|
|
/* Reset auto_increment value if it was dropped */
|
|
if (MTYP_TYPENR(field->unireg_check) == Field::NEXT_NUMBER &&
|
|
!(used_fields & HA_CREATE_USED_AUTO))
|
|
{
|
|
create_info->auto_increment_value=0;
|
|
create_info->used_fields|=HA_CREATE_USED_AUTO;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (drop)
|
|
{
|
|
drop_it.remove();
|
|
continue;
|
|
}
|
|
/* Check if field is changed */
|
|
def_it.rewind();
|
|
while ((def=def_it++))
|
|
{
|
|
if (def->change &&
|
|
!my_strcasecmp(system_charset_info,field->field_name, def->change))
|
|
break;
|
|
}
|
|
if (def)
|
|
{ // Field is changed
|
|
def->field=field;
|
|
if (def->sql_type == FIELD_TYPE_TIMESTAMP)
|
|
use_timestamp=1;
|
|
if (!def->after)
|
|
{
|
|
create_list.push_back(def);
|
|
def_it.remove();
|
|
}
|
|
}
|
|
else
|
|
{ // Use old field value
|
|
create_list.push_back(def=new create_field(field,field));
|
|
if (def->sql_type == FIELD_TYPE_TIMESTAMP)
|
|
use_timestamp=1;
|
|
|
|
alter_it.rewind(); // Change default if ALTER
|
|
Alter_column *alter;
|
|
while ((alter=alter_it++))
|
|
{
|
|
if (!my_strcasecmp(system_charset_info,field->field_name, alter->name))
|
|
break;
|
|
}
|
|
if (alter)
|
|
{
|
|
if (def->sql_type == FIELD_TYPE_BLOB)
|
|
{
|
|
my_error(ER_BLOB_CANT_HAVE_DEFAULT,MYF(0),def->change);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
def->def=alter->def; // Use new default
|
|
alter_it.remove();
|
|
}
|
|
}
|
|
}
|
|
def_it.rewind();
|
|
List_iterator<create_field> find_it(create_list);
|
|
while ((def=def_it++)) // Add new columns
|
|
{
|
|
if (def->change && ! def->field)
|
|
{
|
|
my_error(ER_BAD_FIELD_ERROR,MYF(0),def->change,table_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (!def->after)
|
|
create_list.push_back(def);
|
|
else if (def->after == first_keyword)
|
|
create_list.push_front(def);
|
|
else
|
|
{
|
|
create_field *find;
|
|
find_it.rewind();
|
|
while ((find=find_it++)) // Add new columns
|
|
{
|
|
if (!my_strcasecmp(system_charset_info,def->after, find->field_name))
|
|
break;
|
|
}
|
|
if (!find)
|
|
{
|
|
my_error(ER_BAD_FIELD_ERROR,MYF(0),def->after,table_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
find_it.after(def); // Put element after this
|
|
}
|
|
}
|
|
if (alter_list.elements)
|
|
{
|
|
my_error(ER_BAD_FIELD_ERROR,MYF(0),alter_list.head()->name,table_name);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
if (!create_list.elements)
|
|
{
|
|
my_error(ER_CANT_REMOVE_ALL_FIELDS,MYF(0));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
/*
|
|
Collect all keys which isn't in drop list. Add only those
|
|
for which some fields exists.
|
|
*/
|
|
|
|
List_iterator<Key> key_it(keys);
|
|
List_iterator<create_field> field_it(create_list);
|
|
List<key_part_spec> key_parts;
|
|
|
|
KEY *key_info=table->key_info;
|
|
for (uint i=0 ; i < table->keys ; i++,key_info++)
|
|
{
|
|
if (drop_primary && (key_info->flags & HA_NOSAME))
|
|
{
|
|
drop_primary=0;
|
|
continue;
|
|
}
|
|
|
|
char *key_name=key_info->name;
|
|
Alter_drop *drop;
|
|
drop_it.rewind();
|
|
while ((drop=drop_it++))
|
|
{
|
|
if (drop->type == Alter_drop::KEY &&
|
|
!my_strcasecmp(system_charset_info,key_name, drop->name))
|
|
break;
|
|
}
|
|
if (drop)
|
|
{
|
|
drop_it.remove();
|
|
continue;
|
|
}
|
|
|
|
KEY_PART_INFO *key_part= key_info->key_part;
|
|
key_parts.empty();
|
|
for (uint j=0 ; j < key_info->key_parts ; j++,key_part++)
|
|
{
|
|
if (!key_part->field)
|
|
continue; // Wrong field (from UNIREG)
|
|
const char *key_part_name=key_part->field->field_name;
|
|
create_field *cfield;
|
|
field_it.rewind();
|
|
while ((cfield=field_it++))
|
|
{
|
|
if (cfield->change)
|
|
{
|
|
if (!my_strcasecmp(system_charset_info, key_part_name,
|
|
cfield->change))
|
|
break;
|
|
}
|
|
else if (!my_strcasecmp(system_charset_info,
|
|
key_part_name, cfield->field_name))
|
|
break;
|
|
}
|
|
if (!cfield)
|
|
continue; // Field is removed
|
|
uint key_part_length=key_part->length;
|
|
if (cfield->field) // Not new field
|
|
{ // Check if sub key
|
|
if (cfield->field->type() != FIELD_TYPE_BLOB &&
|
|
(cfield->field->pack_length() == key_part_length ||
|
|
cfield->length != cfield->pack_length ||
|
|
cfield->pack_length <= key_part_length))
|
|
key_part_length=0; // Use whole field
|
|
}
|
|
key_parts.push_back(new key_part_spec(cfield->field_name,
|
|
key_part_length));
|
|
}
|
|
if (key_parts.elements)
|
|
key_list.push_back(new Key(key_info->flags & HA_SPATIAL ? Key::SPATIAL :
|
|
(key_info->flags & HA_NOSAME ?
|
|
(!my_strcasecmp(system_charset_info,
|
|
key_name, "PRIMARY") ?
|
|
Key::PRIMARY : Key::UNIQUE) :
|
|
(key_info->flags & HA_FULLTEXT ?
|
|
Key::FULLTEXT : Key::MULTIPLE)),
|
|
key_name,
|
|
key_info->algorithm,
|
|
key_parts));
|
|
}
|
|
{
|
|
Key *key;
|
|
while ((key=key_it++)) // Add new keys
|
|
{
|
|
if (key->type != Key::FOREIGN_KEY)
|
|
key_list.push_back(key);
|
|
}
|
|
}
|
|
|
|
if (drop_list.elements)
|
|
{
|
|
my_error(ER_CANT_DROP_FIELD_OR_KEY,MYF(0),drop_list.head()->name);
|
|
goto err;
|
|
}
|
|
if (alter_list.elements)
|
|
{
|
|
my_error(ER_CANT_DROP_FIELD_OR_KEY,MYF(0),alter_list.head()->name);
|
|
goto err;
|
|
}
|
|
|
|
db_create_options=table->db_create_options & ~(HA_OPTION_PACK_RECORD);
|
|
(void) sprintf(tmp_name,"%s-%lx_%lx", tmp_file_prefix, current_pid,
|
|
thd->thread_id);
|
|
create_info->db_type=new_db_type;
|
|
if (!create_info->comment)
|
|
create_info->comment=table->comment;
|
|
|
|
/* let new create options override the old ones */
|
|
if (!(used_fields & HA_CREATE_USED_MIN_ROWS))
|
|
create_info->min_rows=table->min_rows;
|
|
if (!(used_fields & HA_CREATE_USED_MAX_ROWS))
|
|
create_info->max_rows=table->max_rows;
|
|
if (!(used_fields & HA_CREATE_USED_AVG_ROW_LENGTH))
|
|
create_info->avg_row_length=table->avg_row_length;
|
|
if (!(used_fields & HA_CREATE_USED_CHARSET))
|
|
create_info->table_charset=table->table_charset;
|
|
|
|
table->file->update_create_info(create_info);
|
|
if ((create_info->table_options &
|
|
(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS)) ||
|
|
(used_fields & HA_CREATE_USED_PACK_KEYS))
|
|
db_create_options&= ~(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS);
|
|
if (create_info->table_options &
|
|
(HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM))
|
|
db_create_options&= ~(HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM);
|
|
if (create_info->table_options &
|
|
(HA_OPTION_DELAY_KEY_WRITE | HA_OPTION_NO_DELAY_KEY_WRITE))
|
|
db_create_options&= ~(HA_OPTION_DELAY_KEY_WRITE |
|
|
HA_OPTION_NO_DELAY_KEY_WRITE);
|
|
create_info->table_options|= db_create_options;
|
|
|
|
if (table->tmp_table)
|
|
create_info->options|=HA_LEX_CREATE_TMP_TABLE;
|
|
|
|
/*
|
|
Handling of symlinked tables:
|
|
If no rename:
|
|
Create new data file and index file on the same disk as the
|
|
old data and index files.
|
|
Copy data.
|
|
Rename new data file over old data file and new index file over
|
|
old index file.
|
|
Symlinks are not changed.
|
|
|
|
If rename:
|
|
Create new data file and index file on the same disk as the
|
|
old data and index files. Create also symlinks to point at
|
|
the new tables.
|
|
Copy data.
|
|
At end, rename temporary tables and symlinks to temporary table
|
|
to final table name.
|
|
Remove old table and old symlinks
|
|
|
|
If rename is made to another database:
|
|
Create new tables in new database.
|
|
Copy data.
|
|
Remove old table and symlinks.
|
|
*/
|
|
|
|
if (!strcmp(db, new_db)) // Ignore symlink if db changed
|
|
{
|
|
if (create_info->index_file_name)
|
|
{
|
|
/* Fix index_file_name to have 'tmp_name' as basename */
|
|
strmov(index_file, tmp_name);
|
|
create_info->index_file_name=fn_same(index_file,
|
|
create_info->index_file_name,
|
|
1);
|
|
}
|
|
if (create_info->data_file_name)
|
|
{
|
|
/* Fix data_file_name to have 'tmp_name' as basename */
|
|
strmov(data_file, tmp_name);
|
|
create_info->data_file_name=fn_same(data_file,
|
|
create_info->data_file_name,
|
|
1);
|
|
}
|
|
}
|
|
else
|
|
create_info->data_file_name=create_info->index_file_name=0;
|
|
|
|
if ((error=mysql_create_table(thd, new_db, tmp_name,
|
|
create_info,
|
|
create_list,key_list,1,1,0))) // no logging
|
|
DBUG_RETURN(error);
|
|
|
|
if (table->tmp_table)
|
|
new_table=open_table(thd,new_db,tmp_name,tmp_name,0);
|
|
else
|
|
{
|
|
char path[FN_REFLEN];
|
|
(void) sprintf(path,"%s/%s/%s",mysql_data_home,new_db,tmp_name);
|
|
fn_format(path,path,"","",4);
|
|
new_table=open_temporary_table(thd, path, new_db, tmp_name,0);
|
|
}
|
|
if (!new_table)
|
|
{
|
|
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
goto err;
|
|
}
|
|
|
|
save_time_stamp=new_table->time_stamp;
|
|
if (use_timestamp)
|
|
new_table->time_stamp=0;
|
|
new_table->next_number_field=new_table->found_next_number_field;
|
|
thd->count_cuted_fields=1; // calc cuted fields
|
|
thd->cuted_fields=0L;
|
|
thd->proc_info="copy to tmp table";
|
|
next_insert_id=thd->next_insert_id; // Remember for loggin
|
|
copied=deleted=0;
|
|
if (!new_table->is_view)
|
|
error=copy_data_between_tables(table,new_table,create_list,
|
|
handle_duplicates,
|
|
order, &copied, &deleted);
|
|
thd->last_insert_id=next_insert_id; // Needed for correct log
|
|
thd->count_cuted_fields=0; // Don`t calc cuted fields
|
|
new_table->time_stamp=save_time_stamp;
|
|
|
|
if (table->tmp_table)
|
|
{
|
|
/* We changed a temporary table */
|
|
if (error)
|
|
{
|
|
close_temporary_table(thd,new_db,tmp_name);
|
|
my_free((gptr) new_table,MYF(0));
|
|
goto err;
|
|
}
|
|
/* Close lock if this is a transactional table */
|
|
if (thd->lock)
|
|
{
|
|
mysql_unlock_tables(thd, thd->lock);
|
|
thd->lock=0;
|
|
}
|
|
/* Remove link to old table and rename the new one */
|
|
close_temporary_table(thd,table->table_cache_key,table_name);
|
|
if (rename_temporary_table(thd, new_table, new_db, new_name))
|
|
{ // Fatal error
|
|
close_temporary_table(thd,new_db,tmp_name);
|
|
my_free((gptr) new_table,MYF(0));
|
|
goto err;
|
|
}
|
|
mysql_update_log.write(thd, thd->query,thd->query_length);
|
|
if (mysql_bin_log.is_open())
|
|
{
|
|
Query_log_event qinfo(thd, thd->query, thd->query_length);
|
|
mysql_bin_log.write(&qinfo);
|
|
}
|
|
goto end_temporary;
|
|
}
|
|
|
|
intern_close_table(new_table); /* close temporary table */
|
|
my_free((gptr) new_table,MYF(0));
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
if (error)
|
|
{
|
|
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
goto err;
|
|
}
|
|
/*
|
|
Data is copied. Now we rename the old table to a temp name,
|
|
rename the new one to the old name, remove all entries from the old table
|
|
from the cash, free all locks, close the old table and remove it.
|
|
*/
|
|
|
|
thd->proc_info="rename result table";
|
|
sprintf(old_name,"%s2-%lx-%lx", tmp_file_prefix, current_pid,
|
|
thd->thread_id);
|
|
if (new_name != table_name || new_db != db)
|
|
{
|
|
if (!access(new_name_buff,F_OK))
|
|
{
|
|
error=1;
|
|
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),new_name_buff);
|
|
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
|
|
if (table->file->has_transactions())
|
|
#endif
|
|
{
|
|
/*
|
|
Win32 and InnoDB can't drop a table that is in use, so we must
|
|
close the original table at before doing the rename
|
|
*/
|
|
table_name=thd->strdup(table_name); // must be saved
|
|
if (close_cached_table(thd,table))
|
|
{ // Aborted
|
|
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
goto err;
|
|
}
|
|
table=0; // Marker that table is closed
|
|
}
|
|
#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
|
|
else
|
|
table->file->extra(HA_EXTRA_FORCE_REOPEN); // Don't use this file anymore
|
|
#endif
|
|
|
|
|
|
error=0;
|
|
if (mysql_rename_table(old_db_type,db,table_name,db,old_name))
|
|
{
|
|
error=1;
|
|
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
}
|
|
else if (mysql_rename_table(new_db_type,new_db,tmp_name,new_db,
|
|
new_name))
|
|
{ // Try to get everything back
|
|
error=1;
|
|
VOID(quick_rm_table(new_db_type,new_db,new_name));
|
|
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
VOID(mysql_rename_table(old_db_type,db,old_name,db,table_name));
|
|
}
|
|
if (error)
|
|
{
|
|
/*
|
|
This shouldn't happen. We solve this the safe way by
|
|
closing the locked table.
|
|
*/
|
|
close_cached_table(thd,table);
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
goto err;
|
|
}
|
|
if (thd->lock || new_name != table_name) // True if WIN32
|
|
{
|
|
/*
|
|
Not table locking or alter table with rename
|
|
free locks and remove old table
|
|
*/
|
|
close_cached_table(thd,table);
|
|
VOID(quick_rm_table(old_db_type,db,old_name));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
Using LOCK TABLES without rename.
|
|
This code is never executed on WIN32!
|
|
Remove old renamed table, reopen table and get new locks
|
|
*/
|
|
if (table)
|
|
{
|
|
VOID(table->file->extra(HA_EXTRA_FORCE_REOPEN)); // Use new file
|
|
remove_table_from_cache(thd,db,table_name); // Mark all in-use copies old
|
|
mysql_lock_abort(thd,table); // end threads waiting on lock
|
|
}
|
|
VOID(quick_rm_table(old_db_type,db,old_name));
|
|
if (close_data_tables(thd,db,table_name) ||
|
|
reopen_tables(thd,1,0))
|
|
{ // This shouldn't happen
|
|
close_cached_table(thd,table); // Remove lock for table
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
/* The ALTER TABLE is always in it's own transaction */
|
|
error = ha_commit_stmt(thd);
|
|
if (ha_commit(thd))
|
|
error=1;
|
|
if (error)
|
|
{
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
VOID(pthread_cond_broadcast(&COND_refresh));
|
|
goto err;
|
|
}
|
|
#ifdef HAVE_BERKELEY_DB
|
|
extern bool berkeley_flush_logs(void);
|
|
if (old_db_type == DB_TYPE_BERKELEY_DB && berkeley_flush_logs())
|
|
goto err;
|
|
#endif
|
|
thd->proc_info="end";
|
|
mysql_update_log.write(thd, thd->query,thd->query_length);
|
|
if (mysql_bin_log.is_open())
|
|
{
|
|
Query_log_event qinfo(thd, thd->query, thd->query_length);
|
|
mysql_bin_log.write(&qinfo);
|
|
}
|
|
VOID(pthread_cond_broadcast(&COND_refresh));
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
table_list->table=0; // For query cache
|
|
query_cache_invalidate3(thd, table_list, 0);
|
|
|
|
end_temporary:
|
|
sprintf(tmp_name,ER(ER_INSERT_INFO),(ulong) (copied+deleted),
|
|
(ulong) deleted, thd->cuted_fields);
|
|
send_ok(thd,copied+deleted,0L,tmp_name);
|
|
thd->some_tables_deleted=0;
|
|
DBUG_RETURN(0);
|
|
|
|
err:
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
|
|
static int
|
|
copy_data_between_tables(TABLE *from,TABLE *to,
|
|
List<create_field> &create,
|
|
enum enum_duplicates handle_duplicates,
|
|
ORDER *order,
|
|
ha_rows *copied,
|
|
ha_rows *deleted)
|
|
{
|
|
int error;
|
|
Copy_field *copy,*copy_end;
|
|
ulong found_count,delete_count;
|
|
THD *thd= current_thd;
|
|
uint length;
|
|
SORT_FIELD *sortorder;
|
|
READ_RECORD info;
|
|
Field *next_field;
|
|
TABLE_LIST tables;
|
|
List<Item> fields;
|
|
List<Item> all_fields;
|
|
ha_rows examined_rows;
|
|
DBUG_ENTER("copy_data_between_tables");
|
|
|
|
if (!(copy= new Copy_field[to->fields]))
|
|
DBUG_RETURN(-1); /* purecov: inspected */
|
|
|
|
to->file->external_lock(thd,F_WRLCK);
|
|
to->file->extra(HA_EXTRA_WRITE_CACHE);
|
|
from->file->info(HA_STATUS_VARIABLE);
|
|
to->file->deactivate_non_unique_index(from->file->records);
|
|
|
|
List_iterator<create_field> it(create);
|
|
create_field *def;
|
|
copy_end=copy;
|
|
for (Field **ptr=to->field ; *ptr ; ptr++)
|
|
{
|
|
def=it++;
|
|
if (def->field)
|
|
(copy_end++)->set(*ptr,def->field,0);
|
|
}
|
|
|
|
found_count=delete_count=0;
|
|
|
|
if (order)
|
|
{
|
|
from->io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
|
|
MYF(MY_FAE | MY_ZEROFILL));
|
|
bzero((char*) &tables,sizeof(tables));
|
|
tables.table = from;
|
|
tables.name = tables.real_name= from->real_name;
|
|
tables.db = from->table_cache_key;
|
|
error=1;
|
|
|
|
if (setup_order(thd, &tables, fields, all_fields, order) ||
|
|
!(sortorder=make_unireg_sortorder(order, &length)) ||
|
|
(from->found_records = filesort(from, sortorder, length,
|
|
(SQL_SELECT *) 0, 0L, HA_POS_ERROR,
|
|
&examined_rows))
|
|
== HA_POS_ERROR)
|
|
goto err;
|
|
};
|
|
|
|
/* Turn off recovery logging since rollback of an
|
|
alter table is to delete the new table so there
|
|
is no need to log the changes to it. */
|
|
error = ha_recovery_logging(thd,FALSE);
|
|
if (error)
|
|
{
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
|
|
init_read_record(&info, thd, from, (SQL_SELECT *) 0, 1,1);
|
|
if (handle_duplicates == DUP_IGNORE ||
|
|
handle_duplicates == DUP_REPLACE)
|
|
to->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
|
|
next_field=to->next_number_field;
|
|
while (!(error=info.read_record(&info)))
|
|
{
|
|
if (thd->killed)
|
|
{
|
|
my_error(ER_SERVER_SHUTDOWN,MYF(0));
|
|
error= 1;
|
|
break;
|
|
}
|
|
if (next_field)
|
|
next_field->reset();
|
|
for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++)
|
|
copy_ptr->do_copy(copy_ptr);
|
|
if ((error=to->file->write_row((byte*) to->record[0])))
|
|
{
|
|
if ((handle_duplicates != DUP_IGNORE &&
|
|
handle_duplicates != DUP_REPLACE) ||
|
|
(error != HA_ERR_FOUND_DUPP_KEY &&
|
|
error != HA_ERR_FOUND_DUPP_UNIQUE))
|
|
{
|
|
to->file->print_error(error,MYF(0));
|
|
break;
|
|
}
|
|
delete_count++;
|
|
}
|
|
else
|
|
found_count++;
|
|
}
|
|
end_read_record(&info);
|
|
free_io_cache(from);
|
|
delete [] copy; // This is never 0
|
|
uint tmp_error;
|
|
if ((tmp_error=to->file->extra(HA_EXTRA_NO_CACHE)))
|
|
{
|
|
to->file->print_error(tmp_error,MYF(0));
|
|
error=1;
|
|
}
|
|
to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
|
if (to->file->activate_all_index(thd))
|
|
error=1;
|
|
|
|
tmp_error = ha_recovery_logging(thd,TRUE);
|
|
/*
|
|
Ensure that the new table is saved properly to disk so that we
|
|
can do a rename
|
|
*/
|
|
if (ha_commit_stmt(thd))
|
|
error=1;
|
|
if (ha_commit(thd))
|
|
error=1;
|
|
if (to->file->external_lock(thd,F_UNLCK))
|
|
error=1;
|
|
err:
|
|
tmp_error = ha_recovery_logging(thd,TRUE);
|
|
free_io_cache(from);
|
|
*copied= found_count;
|
|
*deleted=delete_count;
|
|
DBUG_RETURN(error > 0 ? -1 : 0);
|
|
}
|