Commit e5dcd8be authored by monty@donna.mysql.com's avatar monty@donna.mysql.com

Portability fixes

parent 19a9f8f9
...@@ -500,6 +500,7 @@ Examples of common queries ...@@ -500,6 +500,7 @@ Examples of common queries
* example-Maximum-row:: The row holding the maximum of a certain column * example-Maximum-row:: The row holding the maximum of a certain column
* example-Maximum-column-group:: Maximum of column per group * example-Maximum-column-group:: Maximum of column per group
* example-Maximum-column-group-row:: The rows holding the group-wise maximum of a certain field * example-Maximum-column-group-row:: The rows holding the group-wise maximum of a certain field
* example-user-variables::
* example-Foreign keys:: Using foreign keys * example-Foreign keys:: Using foreign keys
Creating and using a database Creating and using a database
...@@ -948,7 +949,7 @@ Changes in release 3.19.x ...@@ -948,7 +949,7 @@ Changes in release 3.19.x
MySQL and the future (The TODO) MySQL and the future (The TODO)
* TODO MySQL 4.0:: * TODO MySQL 4.0:: Things that should be in 4.0
* TODO future:: Things that must done in the very near future * TODO future:: Things that must done in the very near future
* TODO sometime:: Things that have to be done sometime * TODO sometime:: Things that have to be done sometime
* TODO unplanned:: Some things we don't have any plans to do * TODO unplanned:: Some things we don't have any plans to do
...@@ -22657,6 +22658,7 @@ SELECT * FROM shop ...@@ -22657,6 +22658,7 @@ SELECT * FROM shop
* example-Maximum-row:: The row holding the maximum of a certain column * example-Maximum-row:: The row holding the maximum of a certain column
* example-Maximum-column-group:: Maximum of column per group * example-Maximum-column-group:: Maximum of column per group
* example-Maximum-column-group-row:: The rows holding the group-wise maximum of a certain field * example-Maximum-column-group-row:: The rows holding the group-wise maximum of a certain field
* example-user-variables::
* example-Foreign keys:: Using foreign keys * example-Foreign keys:: Using foreign keys
@end menu @end menu
...@@ -22736,7 +22738,7 @@ GROUP BY article ...@@ -22736,7 +22738,7 @@ GROUP BY article
+---------+-------+ +---------+-------+
@end example @end example
@node example-Maximum-column-group-row, example-Foreign keys, example-Maximum-column-group, Examples @node example-Maximum-column-group-row, example-user-variables, example-Maximum-column-group, Examples
@subsection The rows holding the group-wise maximum of a certain field @subsection The rows holding the group-wise maximum of a certain field
``For each article, find the dealer(s) with the most expensive price.'' ``For each article, find the dealer(s) with the most expensive price.''
...@@ -22807,9 +22809,31 @@ GROUP BY article; ...@@ -22807,9 +22809,31 @@ GROUP BY article;
The last example can of course be made a bit more efficient by doing the The last example can of course be made a bit more efficient by doing the
splitting of the concatenated column in the client. splitting of the concatenated column in the client.
@node example-user-variables, example-Foreign keys, example-Maximum-column-group-row, Examples
@subsection Using user variables
You can use @strong{MySQL} user variables to remember results without
having to store them in a temporary variables in the client.
@xref{Variables}.
For example to find the articles with the highest and lowest price you
can do:
@example
select @@min_price:=min(price),@@max_price:=max(price) from shop;
select * from shop where price=@@min_price or price=@@max_price;
+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
| 0003 | D | 1.25 |
| 0004 | D | 19.95 |
+---------+--------+-------+
@end example
@cindex foreign keys @cindex foreign keys
@cindex keys, foreign @cindex keys, foreign
@node example-Foreign keys, , example-Maximum-column-group-row, Examples @node example-Foreign keys, , example-user-variables, Examples
@subsection Using foreign keys @subsection Using foreign keys
You don't need foreign keys to join 2 tables. You don't need foreign keys to join 2 tables.
...@@ -37609,6 +37633,8 @@ With source code. By Matthias Fichtner. ...@@ -37609,6 +37633,8 @@ With source code. By Matthias Fichtner.
A library to use @strong{MySQL} with Delphi} A library to use @strong{MySQL} with Delphi}
@item @uref{http://www.geocities.com/CapeCanaveral/2064/mysql.html, Delphi TDataset-component} @item @uref{http://www.geocities.com/CapeCanaveral/2064/mysql.html, Delphi TDataset-component}
@item
@item @uref{http://www.mysql.com/Downloads/Contrib/Win32/SBMySQL50Share.exe, Delphi 5 Shareware MySQL Dataset Components}
@end itemize @end itemize
@item @uref{http://www.mysql.com/Downloads/Contrib/mysql-ruby-2.2.0.tar.gz, mysql-ruby-2.2.0.tar.gz} @item @uref{http://www.mysql.com/Downloads/Contrib/mysql-ruby-2.2.0.tar.gz, mysql-ruby-2.2.0.tar.gz}
...@@ -37752,9 +37778,13 @@ An open source client for exploring databases and executing SQL. Supports ...@@ -37752,9 +37778,13 @@ An open source client for exploring databases and executing SQL. Supports
A query tool for @strong{MySQL} and PostgreSQL. A query tool for @strong{MySQL} and PostgreSQL.
@item @uref{http://dbman.linux.cz/,dbMan} @item @uref{http://dbman.linux.cz/,dbMan}
A query tool written in Perl. Uses DBI and Tk. A query tool written in Perl. Uses DBI and Tk.
@item @uref{http://www.mysql.com/Downloads/Contrib/mascon1.exe, mascon1.exe} @item @uref{http://www.mysql.com/Downloads/Win32/Msc18.exe, Mascon 2000.1.8}
You can get the newest one from @item @uref{http://www.mysql.com/Downloads/Win32/FrMsc18.exe, Free Mascon 2000.1.8}
@uref{http://www.scibit.com/Products/Software/Utils/Mascon.asp,Mascon.asp}. Mascon is a powerful Win32 GUI for the administering MySQL server
databases. Mascon's features include visual table design, connections to
multiple servers, data and blob editing of tables, security setting, SQL
colour coding, dump functionality and much more.
@uref{http://www.scibit.com/Products/Software/Utils/Mascon.asp,Mascon home page}.
@item @uref{http://www.virtualbeer.net/dbui/,DBUI} @item @uref{http://www.virtualbeer.net/dbui/,DBUI}
DBUI is a Gtk graphical database editor. DBUI is a Gtk graphical database editor.
@end itemize @end itemize
...@@ -38503,6 +38533,9 @@ though, so Version 3.23 is not released as a stable version yet. ...@@ -38503,6 +38533,9 @@ though, so Version 3.23 is not released as a stable version yet.
@item @item
Fixed the @code{--skip-networking} works properly on NT. Fixed the @code{--skip-networking} works properly on NT.
@item @item
Fixed long outstanding bug in the @code{ISAM} tables when a row with a length
of more than 65K was shortened by a single byte.
@item
Fixed bug in @code{MyISAM} when running multiple updating processes on Fixed bug in @code{MyISAM} when running multiple updating processes on
the same table. the same table.
@item @item
...@@ -115,9 +115,8 @@ static bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0, ...@@ -115,9 +115,8 @@ static bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0,
no_rehash=0,skip_updates=0,safe_updates=0,one_database=0, no_rehash=0,skip_updates=0,safe_updates=0,one_database=0,
opt_compress=0, opt_compress=0,
vertical=0,skip_line_numbers=0,skip_column_names=0,opt_html=0, vertical=0,skip_line_numbers=0,skip_column_names=0,opt_html=0,
opt_nopager=1, opt_outfile=0, opt_nopager=1, opt_outfile=0, no_named_cmds=1;
no_named_cmds=1; static uint verbose=0,opt_silent=0,opt_mysql_port=0,opt_connect_timeout=0;
static uint verbose=0,opt_silent=0,opt_mysql_port=0;
static my_string opt_mysql_unix_port=0; static my_string opt_mysql_unix_port=0;
static int connect_flag=CLIENT_INTERACTIVE; static int connect_flag=CLIENT_INTERACTIVE;
static char *current_host,*current_db,*current_user=0,*opt_password=0, static char *current_host,*current_db,*current_user=0,*opt_password=0,
...@@ -363,8 +362,8 @@ sig_handler mysql_end(int sig) ...@@ -363,8 +362,8 @@ sig_handler mysql_end(int sig)
exit(status.exit_status); exit(status.exit_status);
} }
enum options {OPT_CHARSETS_DIR=256, OPT_DEFAULT_CHARSET, OPT_PAGER, enum options {OPT_CHARSETS_DIR=256, OPT_DEFAULT_CHARSET, OPT_TIMEOUT,
OPT_NOPAGER, OPT_TEE, OPT_NOTEE} ; OPT_PAGER, OPT_NOPAGER, OPT_TEE, OPT_NOTEE} ;
static struct option long_options[] = static struct option long_options[] =
...@@ -378,7 +377,7 @@ static struct option long_options[] = ...@@ -378,7 +377,7 @@ static struct option long_options[] =
#endif #endif
{"database", required_argument, 0, 'D'}, {"database", required_argument, 0, 'D'},
{"debug-info", no_argument, 0, 'T'}, {"debug-info", no_argument, 0, 'T'},
{"default-character-set", required_argument, 0, OPT_DEFAULT_CHARSET}, {"default-character-set", required_argument,0, OPT_DEFAULT_CHARSET},
{"enable-named-commands", no_argument, 0, 'G'}, {"enable-named-commands", no_argument, 0, 'G'},
{"execute", required_argument, 0, 'e'}, {"execute", required_argument, 0, 'e'},
{"force", no_argument, 0, 'f'}, {"force", no_argument, 0, 'f'},
...@@ -412,6 +411,7 @@ static struct option long_options[] = ...@@ -412,6 +411,7 @@ static struct option long_options[] =
{"socket", required_argument, 0, 'S'}, {"socket", required_argument, 0, 'S'},
#include "sslopt-longopts.h" #include "sslopt-longopts.h"
{"table", no_argument, 0, 't'}, {"table", no_argument, 0, 't'},
{"timeout", required_argument, 0, OPT_TIMEOUT},
#ifndef DONT_ALLOW_USER_CHANGE #ifndef DONT_ALLOW_USER_CHANGE
{"user", required_argument, 0, 'u'}, {"user", required_argument, 0, 'u'},
#endif #endif
...@@ -425,7 +425,7 @@ static struct option long_options[] = ...@@ -425,7 +425,7 @@ static struct option long_options[] =
CHANGEABLE_VAR changeable_vars[] = { CHANGEABLE_VAR changeable_vars[] = {
{ "max_allowed_packet", (long*) &max_allowed_packet,24*1024L*1024L,4096, { "max_allowed_packet", (long*) &max_allowed_packet,16*1024L*1024L,4096,
24*1024L*1024L, MALLOC_OVERHEAD,1024}, 24*1024L*1024L, MALLOC_OVERHEAD,1024},
{ "net_buffer_length",(long*) &net_buffer_length,16384,1024,24*1024*1024L, { "net_buffer_length",(long*) &net_buffer_length,16384,1024,24*1024*1024L,
MALLOC_OVERHEAD,1024}, MALLOC_OVERHEAD,1024},
...@@ -627,9 +627,12 @@ static int get_options(int argc, char **argv) ...@@ -627,9 +627,12 @@ static int get_options(int argc, char **argv)
case 'p': case 'p':
if (optarg) if (optarg)
{ {
char *start=optarg;
my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR)); my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR));
opt_password=my_strdup(optarg,MYF(MY_FAE)); opt_password=my_strdup(optarg,MYF(MY_FAE));
while (*optarg) *optarg++= 'x'; // Destroy argument while (*optarg) *optarg++= 'x'; // Destroy argument
if (*start)
start[1]=0;
} }
else else
tty_password=1; tty_password=1;
...@@ -685,6 +688,9 @@ static int get_options(int argc, char **argv) ...@@ -685,6 +688,9 @@ static int get_options(int argc, char **argv)
opt_mysql_unix_port=my_strdup(MYSQL_NAMEDPIPE,MYF(0)); opt_mysql_unix_port=my_strdup(MYSQL_NAMEDPIPE,MYF(0));
#endif #endif
break; break;
case OPT_TIMEOUT:
opt_connect_timeout=atoi(optarg);
break;
case 'V': usage(1); exit(0); case 'V': usage(1); exit(0);
case 'I': case 'I':
case '?': case '?':
...@@ -2026,6 +2032,9 @@ sql_real_connect(char *host,char *database,char *user,char *password, ...@@ -2026,6 +2032,9 @@ sql_real_connect(char *host,char *database,char *user,char *password,
connected= 0; connected= 0;
} }
mysql_init(&mysql); mysql_init(&mysql);
if (opt_connect_timeout)
mysql_options(&mysql,MYSQL_OPT_CONNECT_TIMEOUT,
(char*) &opt_connect_timeout);
if (opt_compress) if (opt_compress)
mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS); mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS);
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
...@@ -2258,9 +2267,9 @@ void tee_fprintf(FILE *file, const char *fmt, ...) ...@@ -2258,9 +2267,9 @@ void tee_fprintf(FILE *file, const char *fmt, ...)
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
VOID(vfprintf(file, fmt, args)); (void) vfprintf(file, fmt, args);
if (opt_outfile) if (opt_outfile)
VOID(vfprintf(OUTFILE, fmt, args)); (void) vfprintf(OUTFILE, fmt, args);
va_end(args); va_end(args);
} }
......
...@@ -104,7 +104,8 @@ int _nisam_delete_dynamic_record(N_INFO *info) ...@@ -104,7 +104,8 @@ int _nisam_delete_dynamic_record(N_INFO *info)
/* Write record to data-file */ /* Write record to data-file */
static int write_dynamic_record(N_INFO *info, const byte *record, uint reclength) static int write_dynamic_record(N_INFO *info, const byte *record,
uint reclength)
{ {
int flag; int flag;
uint length; uint length;
...@@ -142,8 +143,9 @@ static int _nisam_find_writepos(N_INFO *info, ...@@ -142,8 +143,9 @@ static int _nisam_find_writepos(N_INFO *info,
*filepos=info->s->state.dellink; *filepos=info->s->state.dellink;
block_info.second_read=0; block_info.second_read=0;
info->rec_cache.seek_not_done=1; info->rec_cache.seek_not_done=1;
if (!(_nisam_get_block_info(&block_info,info->dfile,info->s->state.dellink) &
BLOCK_DELETED)) if (!(_nisam_get_block_info(&block_info,info->dfile,
info->s->state.dellink) & BLOCK_DELETED))
{ {
my_errno=HA_ERR_WRONG_IN_RECORD; my_errno=HA_ERR_WRONG_IN_RECORD;
DBUG_RETURN(-1); DBUG_RETURN(-1);
...@@ -213,7 +215,7 @@ int _nisam_write_part_record(N_INFO *info, ...@@ -213,7 +215,7 @@ int _nisam_write_part_record(N_INFO *info,
extra_length++; /* One empty */ extra_length++; /* One empty */
} }
} }
else if (length-long_block < *reclength+5) else if (length-long_block*2 < *reclength+5)
{ /* To short block */ { /* To short block */
if (next_filepos == NI_POS_ERROR) if (next_filepos == NI_POS_ERROR)
next_filepos=info->s->state.dellink != NI_POS_ERROR ? next_filepos=info->s->state.dellink != NI_POS_ERROR ?
......
...@@ -19,13 +19,24 @@ mysqlhotcopy - fast on-line hot-backup utility for local MySQL databases ...@@ -19,13 +19,24 @@ mysqlhotcopy - fast on-line hot-backup utility for local MySQL databases
mysqlhotcopy db_name_1 ... db_name_n /path/to/new_directory mysqlhotcopy db_name_1 ... db_name_n /path/to/new_directory
mysqlhotcopy db_name./regex/
mysqlhotcopy db_name./^\(foo\|bar\)/
mysqlhotcopy db_name./~regex/
mysqlhotcopy db_name_1./regex_1/ db_name_1./regex_2/ ... db_name_n./regex_n/ /path/to/new_directory
mysqlhotcopy --method='scp -Bq -i /usr/home/foo/.ssh/identity' --user=root --password=secretpassword \
db_1./^nice_table/ user@some.system.dom:~/path/to/new_directory
WARNING: THIS IS VERY MUCH A FIRST-CUT ALPHA. Comments/patches welcome. WARNING: THIS IS VERY MUCH A FIRST-CUT ALPHA. Comments/patches welcome.
=cut =cut
# Documentation continued at end of file # Documentation continued at end of file
my $VERSION = "1.7"; my $VERSION = "1.8";
my $OPTIONS = <<"_OPTIONS"; my $OPTIONS = <<"_OPTIONS";
...@@ -39,7 +50,7 @@ Usage: $0 db_name [new_db_name | directory] ...@@ -39,7 +50,7 @@ Usage: $0 db_name [new_db_name | directory]
--allowold don't abort if target already exists (rename it _old) --allowold don't abort if target already exists (rename it _old)
--keepold don't delete previous (now renamed) target when done --keepold don't delete previous (now renamed) target when done
--noindices don't copy index files --indices include index files in copy
--method=# method for copy (only "cp" currently supported) --method=# method for copy (only "cp" currently supported)
-q, --quiet be silent except for errors -q, --quiet be silent except for errors
...@@ -50,6 +61,8 @@ Usage: $0 db_name [new_db_name | directory] ...@@ -50,6 +61,8 @@ Usage: $0 db_name [new_db_name | directory]
--suffix=# suffix for names of copied databases --suffix=# suffix for names of copied databases
--checkpoint=# insert checkpoint entry into specified db.table --checkpoint=# insert checkpoint entry into specified db.table
--flushlog flush logs once all tables are locked --flushlog flush logs once all tables are locked
Try 'perldoc $0 for more complete documentation'
_OPTIONS _OPTIONS
sub usage { sub usage {
...@@ -89,23 +102,26 @@ GetOptions( \%opt, ...@@ -89,23 +102,26 @@ GetOptions( \%opt,
# ========== # ==========
# a list of hash-refs containing: # a list of hash-refs containing:
# #
# 'src' - name of the db to copy # 'src' - name of the db to copy
# 'target' - destination directory of the copy # 't_regex' - regex describing tables in src
# 'tables' - array-ref to list of tables in the db # 'target' - destination directory of the copy
# 'files' - array-ref to list of files to be copied # 'tables' - array-ref to list of tables in the db
# 'files' - array-ref to list of files to be copied
# #
my @db_desc = (); my @db_desc = ();
my $tgt_name = undef; my $tgt_name = undef;
if ( $opt{regexp} || $opt{suffix} || @ARGV > 2 ) { if ( $opt{regexp} || $opt{suffix} || @ARGV > 2 ) {
$tgt_name = pop @ARGV unless ( exists $opt{suffix} ); $tgt_name = pop @ARGV unless ( exists $opt{suffix} );
@db_desc = map { { 'src' => $_ } } @ARGV; @db_desc = map { s{^([^\.]+)\./(.+)/$}{$1}; { 'src' => $_, 't_regex' => ( $2 ? $2 : '.*' ) } } @ARGV;
} }
else { else {
usage("Database name to hotcopy not specified") unless ( @ARGV ); usage("Database name to hotcopy not specified") unless ( @ARGV );
@db_desc = ( { 'src' => $ARGV[0] } ); $ARGV[0] =~ s{^([^\.]+)\./(.+)/$}{$1};
@db_desc = ( { 'src' => $ARGV[0], 't_regex' => ( $2 ? $2 : '.*' ) } );
if ( @ARGV == 2 ) { if ( @ARGV == 2 ) {
$tgt_name = $ARGV[1]; $tgt_name = $ARGV[1];
} }
...@@ -195,15 +211,35 @@ foreach my $rdb ( @db_desc ) { ...@@ -195,15 +211,35 @@ foreach my $rdb ( @db_desc ) {
die "Database '$db' not accessible: $@" if ( $@ ); die "Database '$db' not accessible: $@" if ( $@ );
my @dbh_tables = $dbh->func( '_ListTables' ); my @dbh_tables = $dbh->func( '_ListTables' );
## generate regex for tables/files
my $t_regex = $rdb->{t_regex}; ## assign temporary regex
my $negated = $t_regex =~ tr/~//d; ## remove and count negation operator: we don't allow ~ in table names
$t_regex = qr/$t_regex/; ## make regex string from user regex
## filter (out) tables specified in t_regex
print "Filtering tables with '$t_regex'\n" if $opt{debug};
@dbh_tables = ( $negated
? grep { $_ !~ $t_regex } @dbh_tables
: grep { $_ =~ $t_regex } @dbh_tables );
## get list of files to copy
my $db_dir = "$datadir/$db"; my $db_dir = "$datadir/$db";
opendir(DBDIR, $db_dir ) opendir(DBDIR, $db_dir )
or die "Cannot open dir '$db_dir': $!"; or die "Cannot open dir '$db_dir': $!";
my @db_files = grep { /.+\.\w+$/ } readdir(DBDIR)
or warn "'$db' is an empty database\n";
my %db_files;
map { ( /(.+)\.\w+$/ ? { $db_files{$_} = $1 } : () ) } readdir(DBDIR);
unless( keys %db_files ) {
warn "'$db' is an empty database\n";
}
closedir( DBDIR ); closedir( DBDIR );
## filter (out) files specified in t_regex
my @db_files = sort ( $negated
? grep { $db_files{$_} !~ $t_regex } keys %db_files
: grep { $db_files{$_} =~ $t_regex } keys %db_files );
## remove indices unless we're told to keep them
unless ($opt{indices}) { unless ($opt{indices}) {
@db_files = grep { not /\.(ISM|MYI)$/ } @db_files; @db_files = grep { not /\.(ISM|MYI)$/ } @db_files;
} }
...@@ -238,6 +274,12 @@ if (length $tgt_name ) { ...@@ -238,6 +274,12 @@ if (length $tgt_name ) {
$rdb->{target} = "$tgt_dirname"; $rdb->{target} = "$tgt_dirname";
} }
} }
elsif ($opt{method} =~ /^scp\b/)
{ # we have to trust scp to hit the target
foreach my $rdb ( @db_desc ) {
$rdb->{target} = "$tgt_dirname/$rdb->{src}";
}
}
else else
{ {
die "Last argument ($tgt_dirname) is not a directory\n" die "Last argument ($tgt_dirname) is not a directory\n"
...@@ -249,7 +291,7 @@ if (length $tgt_name ) { ...@@ -249,7 +291,7 @@ if (length $tgt_name ) {
} }
else { else {
die "Error: expected \$opt{suffix} to exist" unless ( exists $opt{suffix} ); die "Error: expected \$opt{suffix} to exist" unless ( exists $opt{suffix} );
foreach my $rdb ( @db_desc ) { foreach my $rdb ( @db_desc ) {
$rdb->{target} = "$datadir/$rdb->{src}$opt{suffix}"; $rdb->{target} = "$datadir/$rdb->{src}$opt{suffix}";
} }
...@@ -278,6 +320,10 @@ foreach my $rdb ( @db_desc ) { ...@@ -278,6 +320,10 @@ foreach my $rdb ( @db_desc ) {
if ( $opt{dryrun} ) { if ( $opt{dryrun} ) {
print "mkdir $tgt_dirpath, 0750\n"; print "mkdir $tgt_dirpath, 0750\n";
} }
elsif ($opt{method} =~ /^scp\b/) {
## assume it's there?
## ...
}
else { else {
mkdir($tgt_dirpath, 0750) mkdir($tgt_dirpath, 0750)
or die "Can't create '$tgt_dirpath': $!\n"; or die "Can't create '$tgt_dirpath': $!\n";
...@@ -320,26 +366,26 @@ else { ...@@ -320,26 +366,26 @@ else {
printf "Flushed tables ($hc_tables) in %d seconds.\n", time-$start unless $opt{quiet}; printf "Flushed tables ($hc_tables) in %d seconds.\n", time-$start unless $opt{quiet};
$dbh->do( "FLUSH LOGS" ) if ( $opt{flushlog} ); $dbh->do( "FLUSH LOGS" ) if ( $opt{flushlog} );
} }
my @failed = (); my @failed = ();
foreach my $rdb ( @db_desc ) { foreach my $rdb ( @db_desc ) {
my @files = map { "$datadir/$rdb->{src}/$_" } @{$rdb->{files}}; my @files = map { "$datadir/$rdb->{src}/$_" } @{$rdb->{files}};
next unless @files; next unless @files;
eval { copy_files($opt{method}, \@files, $rdb->{target} ); }; eval { copy_files($opt{method}, \@files, $rdb->{target} ); };
push @failed, "$rdb->{src} -> $rdb->{target} failed: $@" push @failed, "$rdb->{src} -> $rdb->{target} failed: $@"
if ( $@ ); if ( $@ );
if ( $opt{checkpoint} ) { if ( $opt{checkpoint} ) {
my $msg = ( $@ ) ? "Failed: $@" : "Succeeded"; my $msg = ( $@ ) ? "Failed: $@" : "Succeeded";
eval { eval {
$dbh->do( qq{ insert into $opt{checkpoint} (src, dest, msg) $dbh->do( qq{ insert into $opt{checkpoint} (src, dest, msg)
VALUES ( '$rdb->{src}', '$rdb->{target}', '$msg' ) VALUES ( '$rdb->{src}', '$rdb->{target}', '$msg' )
} ); } );
}; };
if ( $@ ) { if ( $@ ) {
warn "Failed to update checkpoint table: $@\n"; warn "Failed to update checkpoint table: $@\n";
} }
...@@ -410,11 +456,16 @@ sub copy_files { ...@@ -410,11 +456,16 @@ sub copy_files {
my ($method, $files, $target) = @_; my ($method, $files, $target) = @_;
my @cmd; my @cmd;
print "Copying ".@$files." files...\n" unless $opt{quiet}; print "Copying ".@$files." files...\n" unless $opt{quiet};
if ($method =~ /^s?cp\b/) { # cp or scp with optional flags if ($method =~ /^s?cp\b/) { # cp or scp with optional flags
@cmd = ($method); @cmd = ($method);
# add option to preserve mod time etc of copied files # add option to preserve mod time etc of copied files
# not critical, but nice to have # not critical, but nice to have
push @cmd, "-p" if $^O =~ m/^(solaris|linux)$/; push @cmd, "-p" if $^O =~ m/^(solaris|linux|freebsd)$/;
# add recursive option for scp
push @cmd, "-r" if $^O =~ /m^(solaris|linux|freebsd)$/ && $method =~ /^scp\b/;
# add files to copy and the destination directory # add files to copy and the destination directory
push @cmd, @$files, $target; push @cmd, @$files, $target;
} }
...@@ -427,10 +478,13 @@ sub copy_files { ...@@ -427,10 +478,13 @@ sub copy_files {
next; next;
} }
## for some reason system fails but backticks works ok for scp...
print "Executing '@cmd'\n" if $opt{debug}; print "Executing '@cmd'\n" if $opt{debug};
my $cp_status = system @cmd; my $cp_status = system @cmd;
if ($cp_status != 0) { if ($cp_status != 0) {
die "Error: @cmd failed ($cp_status) while copying files.\n"; warn "Burp ('scuse me). Trying backtick execution...\n" if $opt{debug}; #'
## try something else
`@cmd` && die "Error: @cmd failed ($cp_status) while copying files.\n";
} }
} }
...@@ -520,7 +574,22 @@ locked, and before they are copied. ...@@ -520,7 +574,22 @@ locked, and before they are copied.
=item --regexp pattern =item --regexp pattern
Copy all databases with names matching the pattern. Copy all databases with names matching the pattern
=item db_name./pattern/
Copy only tables matching pattern. Shell metacharacters ( (, ), |, !,
etc.) have to be escaped (e.g. \). For example, to select all tables
in database db1 whose names begin with 'foo' or 'bar':
mysqlhotcopy --indices --method=cp db1./^\(foo\|bar\)/
=item db_name./~pattern/
Copy only tables not matching pattern. For example, to copy tables
that do not begin with foo nor bar:
mysqlhotcopy --indices --method=cp db1./~^\(foo\|bar\)/
=item -?, --help =item -?, --help
...@@ -542,13 +611,25 @@ port to use when connecting to local server ...@@ -542,13 +611,25 @@ port to use when connecting to local server
UNIX domain socket to use when connecting to local server UNIX domain socket to use when connecting to local server
=item --noindices =item --indices
don't copy index files include index files in copy
=item --method=# =item --method=#
method for copy (only "cp" currently supported) method for copy (only "cp" currently supported). Alpha support for
"scp" was added in November 2000. Your experience with the scp method
will vary with your ability to understand how scp works. 'man scp'
and 'man ssh' are your friends.
The destination directory _must exist_ on the target machine using
the scp method. Liberal use of the --debug option will help you figure
out what's really going on when you do an scp.
Note that using scp will lock your tables for a _long_ time unless
your network connection is _fast_. If this is unacceptable to you,
use the 'cp' method to copy the tables to some temporary area and then
scp or rsync the files at your leisure.
=item -q, --quiet =item -q, --quiet
...@@ -575,12 +656,8 @@ Patches adding bug fixes, documentation and new features are welcome. ...@@ -575,12 +656,8 @@ Patches adding bug fixes, documentation and new features are welcome.
=head1 TO DO =head1 TO DO
Allow a list of tables (or regex) to be given on the command line to Extend the individual table copy to allow multiple subsets of tables
enable a logically-related subset of the tables to be hot-copied to be specified on the command line:
rather than force the whole db to be copied in one go.
Extend the above to allow multiple subsets of tables to be specified
on the command line:
mysqlhotcopy db newdb t1 t2 /^foo_/ : t3 /^bar_/ : + mysqlhotcopy db newdb t1 t2 /^foo_/ : t3 /^bar_/ : +
...@@ -609,5 +686,6 @@ Tim Bunce ...@@ -609,5 +686,6 @@ Tim Bunce
Martin Waite - added checkpoint, flushlog, regexp and dryrun options Martin Waite - added checkpoint, flushlog, regexp and dryrun options
Ralph Corderoy - Added synonyms for commands Ralph Corderoy - added synonyms for commands
=cut
Scott Wiersdorf - added table regex and scp support
...@@ -365,6 +365,25 @@ else ...@@ -365,6 +365,25 @@ else
print " for order_by_big ($small_loop_count:$rows): " . print " for order_by_big ($small_loop_count:$rows): " .
timestr(timediff($end_time, $loop_time),"all") . "\n"; timestr(timediff($end_time, $loop_time),"all") . "\n";
$loop_time=new Benchmark;
$estimated=$rows=0;
for ($i=1 ; $i <= $range_loop_count ; $i++)
{
$start=$opt_loop_count/$range_loop_count*$i;
$end=$start+$i;
$rows+=fetch_all_rows($dbh,"select dummy1 from bench1 where id>=$start and id <= $end order by id3",1);
$end_time=new Benchmark;
last if ($estimated=predict_query_time($loop_time,$end_time,\$i,$i,
$range_loop_count));
}
if ($estimated)
{ print "Estimated time"; }
else
{ print "Time"; }
print " for order_by_range ($range_loop_count:$rows): " .
timestr(timediff($end_time, $loop_time),"all") . "\n";
$loop_time=new Benchmark; $loop_time=new Benchmark;
$estimated=$rows=0; $estimated=$rows=0;
for ($i=1 ; $i <= $range_loop_count ; $i++) for ($i=1 ; $i <= $range_loop_count ; $i++)
......
...@@ -171,7 +171,7 @@ void MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, ...@@ -171,7 +171,7 @@ void MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg,
sprintf(buff, "%s, Version: %s, started with:\nTcp port: %d Unix socket: %s\n", my_progname,server_version,mysql_port,mysql_unix_port); sprintf(buff, "%s, Version: %s, started with:\nTcp port: %d Unix socket: %s\n", my_progname,server_version,mysql_port,mysql_unix_port);
#endif #endif
end=strmov(strend(buff),"Time Id Command Argument\n"); end=strmov(strend(buff),"Time Id Command Argument\n");
if (my_b_write(&log_file,buff,(uint) (end-buff)) || if (my_b_write(&log_file, (byte*) buff,(uint) (end-buff)) ||
flush_io_cache(&log_file)) flush_io_cache(&log_file))
goto err; goto err;
} }
...@@ -188,7 +188,7 @@ void MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, ...@@ -188,7 +188,7 @@ void MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg,
tm_tmp.tm_hour, tm_tmp.tm_hour,
tm_tmp.tm_min, tm_tmp.tm_min,
tm_tmp.tm_sec); tm_tmp.tm_sec);
if (my_b_write(&log_file,buff,(uint) strlen(buff)) || if (my_b_write(&log_file, (byte*) buff,(uint) strlen(buff)) ||
flush_io_cache(&log_file)) flush_io_cache(&log_file))
goto err; goto err;
} }
...@@ -212,9 +212,9 @@ void MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, ...@@ -212,9 +212,9 @@ void MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg,
s.write(&log_file); s.write(&log_file);
flush_io_cache(&log_file); flush_io_cache(&log_file);
pthread_mutex_lock(&LOCK_index); pthread_mutex_lock(&LOCK_index);
error=(my_write(index_file, log_file_name,strlen(log_file_name), error=(my_write(index_file, (byte*) log_file_name, strlen(log_file_name),
MYF(MY_NABP | MY_WME)) || MYF(MY_NABP | MY_WME)) ||
my_write(index_file, "\n", 1, MYF(MY_NABP | MY_WME))); my_write(index_file, (byte*) "\n", 1, MYF(MY_NABP | MY_WME)));
pthread_mutex_unlock(&LOCK_index); pthread_mutex_unlock(&LOCK_index);
if (error) if (error)
{ {
...@@ -444,8 +444,8 @@ during log purge for write"); ...@@ -444,8 +444,8 @@ during log purge for write");
{ {
char* l; char* l;
get_dynamic(&logs_to_keep, (gptr)&l, i); get_dynamic(&logs_to_keep, (gptr)&l, i);
if (my_write(index_file, l, strlen(l), MYF(MY_WME|MY_NABP)) || if (my_write(index_file, (byte*) l, strlen(l), MYF(MY_WME|MY_NABP)) ||
my_write(index_file, "\n", 1, MYF(MY_WME|MY_NABP))) my_write(index_file, (byte*) "\n", 1, MYF(MY_WME|MY_NABP)))
{ {
error = LOG_INFO_FATAL; error = LOG_INFO_FATAL;
goto err; goto err;
...@@ -571,21 +571,21 @@ void MYSQL_LOG::write(THD *thd,enum enum_server_command command, ...@@ -571,21 +571,21 @@ void MYSQL_LOG::write(THD *thd,enum enum_server_command command,
start->tm_hour, start->tm_hour,
start->tm_min, start->tm_min,
start->tm_sec); start->tm_sec);
if (my_b_write(&log_file,buff,16)) if (my_b_write(&log_file, (byte*) buff,16))
error=errno; error=errno;
} }
else if (my_b_write(&log_file,"\t\t",2) < 0) else if (my_b_write(&log_file, (byte*) "\t\t",2) < 0)
error=errno; error=errno;
sprintf(buff,"%7ld %-10.10s", id,command_name[(uint) command]); sprintf(buff,"%7ld %-10.10s", id,command_name[(uint) command]);
if (my_b_write(&log_file,buff,strlen(buff))) if (my_b_write(&log_file, (byte*) buff,strlen(buff)))
error=errno; error=errno;
if (format) if (format)
{ {
if (my_b_write(&log_file," ",1) || if (my_b_write(&log_file, (byte*) " ",1) ||
my_b_vprintf(&log_file,format,args) == (uint) -1) my_b_vprintf(&log_file,format,args) == (uint) -1)
error=errno; error=errno;
} }
if (my_b_write(&log_file,"\n",1) || if (my_b_write(&log_file, (byte*) "\n",1) ||
flush_io_cache(&log_file)) flush_io_cache(&log_file))
error=errno; error=errno;
if (error && ! write_error) if (error && ! write_error)
...@@ -711,7 +711,6 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length, ...@@ -711,7 +711,6 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length,
last_time=current_time; last_time=current_time;
struct tm tm_tmp; struct tm tm_tmp;
struct tm *start; struct tm *start;
char buff[32];
localtime_r(&current_time,&tm_tmp); localtime_r(&current_time,&tm_tmp);
start=&tm_tmp; start=&tm_tmp;
/* Note that my_b_write() assumes it knows the length for this */ /* Note that my_b_write() assumes it knows the length for this */
...@@ -722,7 +721,7 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length, ...@@ -722,7 +721,7 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length,
start->tm_hour, start->tm_hour,
start->tm_min, start->tm_min,
start->tm_sec); start->tm_sec);
if (my_b_write(&log_file,buff,24)) if (my_b_write(&log_file, (byte*) buff,24))
error=errno; error=errno;
} }
if (my_b_printf(&log_file, "# User@Host: %s [%s] @ %s [%s]\n", if (my_b_printf(&log_file, "# User@Host: %s [%s] @ %s [%s]\n",
...@@ -778,8 +777,8 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length, ...@@ -778,8 +777,8 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length,
*end++=';'; *end++=';';
*end++='\n'; *end++='\n';
*end=0; *end=0;
if (my_b_write(&log_file,"SET ",4) || if (my_b_write(&log_file, (byte*) "SET ",4) ||
my_b_write(&log_file,buff+1,(uint) (end-buff)-1)) my_b_write(&log_file, (byte*) buff+1,(uint) (end-buff)-1))
error=errno; error=errno;
} }
if (!query) if (!query)
...@@ -787,8 +786,8 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length, ...@@ -787,8 +786,8 @@ void MYSQL_LOG::write(THD *thd,const char *query, uint query_length,
query="#adminstrator command"; query="#adminstrator command";
query_length=21; query_length=21;
} }
if (my_b_write(&log_file,(byte*) query,query_length) || if (my_b_write(&log_file, (byte*) query,query_length) ||
my_b_write(&log_file,";\n",2) || my_b_write(&log_file, (byte*) ";\n",2) ||
flush_io_cache(&log_file)) flush_io_cache(&log_file))
error=errno; error=errno;
if (error && ! write_error) if (error && ! write_error)
......
...@@ -363,7 +363,7 @@ public: ...@@ -363,7 +363,7 @@ public:
Stop_log_event(IO_CACHE* file, time_t when_arg, uint32 server_id): Stop_log_event(IO_CACHE* file, time_t when_arg, uint32 server_id):
Log_event(when_arg,0,0,server_id) Log_event(when_arg,0,0,server_id)
{ {
char skip[4]; byte skip[4];
my_b_read(file, skip, sizeof(skip)); // skip the event length my_b_read(file, skip, sizeof(skip)); // skip the event length
} }
Stop_log_event(const char* buf):Log_event(buf) Stop_log_event(const char* buf):Log_event(buf)
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include <errno.h> #include <errno.h>
static void my_aiowait(my_aio_result *result); static void my_aiowait(my_aio_result *result);
#endif #endif
#include <assert.h>
extern "C" { extern "C" {
...@@ -233,24 +234,28 @@ my_bool reinit_io_cache(IO_CACHE *info, enum cache_type type, ...@@ -233,24 +234,28 @@ my_bool reinit_io_cache(IO_CACHE *info, enum cache_type type,
/* Read buffered. Returns 1 if can't read requested characters */ /*
/* Returns 0 if record read */ Read buffered. Returns 1 if can't read requested characters
This function is only called from the my_b_read() macro
when there isn't enough characters in the buffer to
satisfy the request.
Returns 0 we succeeded in reading all data
*/
int _my_b_read(register IO_CACHE *info, byte *Buffer, uint Count) int _my_b_read(register IO_CACHE *info, byte *Buffer, uint Count)
{ {
uint length,diff_length,left_length; uint length,diff_length,left_length;
my_off_t max_length, pos_in_file; my_off_t max_length, pos_in_file;
if((left_length=(uint) (info->rc_end-info->rc_pos))) if ((left_length=(uint) (info->rc_end-info->rc_pos)))
{ {
if(Count < left_length) dbug_assert(Count >= left_length); /* User is not using my_b_read() */
left_length = Count; memcpy(Buffer,info->rc_pos, (size_t) (left_length));
memcpy(Buffer,info->rc_pos,
(size_t) (left_length));
Buffer+=left_length; Buffer+=left_length;
Count-=left_length; Count-=left_length;
} }
pos_in_file=info->pos_in_file+ left_length; /* pos_in_file always point on where info->buffer was read */
pos_in_file=info->pos_in_file+(uint) (info->rc_end - info->buffer);
if (info->seek_not_done) if (info->seek_not_done)
{ /* File touched, do seek */ { /* File touched, do seek */
VOID(my_seek(info->file,pos_in_file,MY_SEEK_SET,MYF(0))); VOID(my_seek(info->file,pos_in_file,MY_SEEK_SET,MYF(0)));
......
...@@ -330,12 +330,12 @@ static void dump_local_log_entries(const char* logname) ...@@ -330,12 +330,12 @@ static void dump_local_log_entries(const char* logname)
if (position) if (position)
{ {
/* skip 'position' characters from stdout */ /* skip 'position' characters from stdout */
char buff[IO_SIZE]; byte buff[IO_SIZE];
my_off_t length,tmp; my_off_t length,tmp;
for (length=position ; length > 0 ; length-=tmp) for (length=position ; length > 0 ; length-=tmp)
{ {
tmp=min(length,sizeof(buff)); tmp=min(length,sizeof(buff));
if (my_b_read(file,buff,tmp)) if (my_b_read(file,buff, (uint) tmp))
exit(1); exit(1);
} }
} }
......
...@@ -569,7 +569,6 @@ static sig_handler print_signal_warning(int sig) ...@@ -569,7 +569,6 @@ static sig_handler print_signal_warning(int sig)
void unireg_end(int signal_number __attribute__((unused))) void unireg_end(int signal_number __attribute__((unused)))
{ {
(void) my_delete(pidfile_name,MYF(0)); // This may not always exist
clean_up(); clean_up();
pthread_exit(0); // Exit is in main thread pthread_exit(0); // Exit is in main thread
} }
...@@ -580,7 +579,6 @@ void unireg_abort(int exit_code) ...@@ -580,7 +579,6 @@ void unireg_abort(int exit_code)
if (exit_code) if (exit_code)
sql_print_error("Aborting\n"); sql_print_error("Aborting\n");
clean_up(); /* purecov: inspected */ clean_up(); /* purecov: inspected */
(void) my_delete(pidfile_name,MYF(0)); // This may not always exist
exit(exit_code); /* purecov: inspected */ exit(exit_code); /* purecov: inspected */
} }
...@@ -610,6 +608,7 @@ void clean_up(void) ...@@ -610,6 +608,7 @@ void clean_up(void)
free_defaults(defaults_argv); free_defaults(defaults_argv);
my_free(mysql_tmpdir,MYF(0)); my_free(mysql_tmpdir,MYF(0));
x_free(opt_bin_logname); x_free(opt_bin_logname);
(void) my_delete(pidfile_name,MYF(0)); // This may not always exist
my_end(opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0); my_end(opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0);
/* Tell main we are ready */ /* Tell main we are ready */
...@@ -1520,7 +1519,7 @@ int main(int argc, char **argv) ...@@ -1520,7 +1519,7 @@ int main(int argc, char **argv)
sql_print_error("Can't init databases"); sql_print_error("Can't init databases");
exit(1); exit(1);
} }
#ifdef HAVE_MLOCKALL #if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT)
if (locked_in_memory && !geteuid()) if (locked_in_memory && !geteuid())
{ {
ha_key_cache(); ha_key_cache();
......
...@@ -42,7 +42,7 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db, ...@@ -42,7 +42,7 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
static inline char* rewrite_db(char* db); static inline char* rewrite_db(char* db);
static void free_table_ent(TABLE_RULE_ENT* e) static void free_table_ent(TABLE_RULE_ENT* e)
{ {
my_free((byte*)e, MYF(0)); my_free((gptr) e, MYF(0));
} }
static byte* get_table_key(TABLE_RULE_ENT* e, uint* len, static byte* get_table_key(TABLE_RULE_ENT* e, uint* len,
...@@ -74,12 +74,12 @@ int tables_ok(THD* thd, TABLE_LIST* tables) ...@@ -74,12 +74,12 @@ int tables_ok(THD* thd, TABLE_LIST* tables)
uint len = strmov(p, tables->real_name) - hash_key ; uint len = strmov(p, tables->real_name) - hash_key ;
if(do_table_inited) // if there are any do's if(do_table_inited) // if there are any do's
{ {
if(hash_search(&replicate_do_table, hash_key, len)) if(hash_search(&replicate_do_table, (byte*) hash_key, len))
return 1; return 1;
} }
if(ignore_table_inited) // if there are any do's if(ignore_table_inited) // if there are any do's
{ {
if(hash_search(&replicate_ignore_table, hash_key, len)) if(hash_search(&replicate_ignore_table, (byte*) hash_key, len))
return 0; return 0;
} }
} }
......
...@@ -1559,7 +1559,7 @@ mysql_execute_command(void) ...@@ -1559,7 +1559,7 @@ mysql_execute_command(void)
/* Check if auto_commit mode changed */ /* Check if auto_commit mode changed */
if ((org_options ^ lex->options) & OPTION_AUTO_COMMIT) if ((org_options ^ lex->options) & OPTION_AUTO_COMMIT)
{ {
if (!org_options & OPTION_AUTO_COMMIT) if (!(org_options & OPTION_AUTO_COMMIT))
{ {
/* We changed to auto_commit mode */ /* We changed to auto_commit mode */
thd->options&= ~(ulong) (OPTION_BEGIN); thd->options&= ~(ulong) (OPTION_BEGIN);
......
...@@ -48,6 +48,7 @@ int mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists) ...@@ -48,6 +48,7 @@ int mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists)
bool some_tables_deleted=0; bool some_tables_deleted=0;
uint error; uint error;
db_type table_type; db_type table_type;
TABLE_LIST *table;
DBUG_ENTER("mysql_rm_table"); DBUG_ENTER("mysql_rm_table");
/* mark for close and remove all cached entries */ /* mark for close and remove all cached entries */
...@@ -59,22 +60,22 @@ int mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists) ...@@ -59,22 +60,22 @@ int mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists)
pthread_mutex_unlock(&thd->mysys_var->mutex); pthread_mutex_unlock(&thd->mysys_var->mutex);
if(global_read_lock) if(global_read_lock)
{
if(thd->global_read_lock)
{ {
if(thd->global_read_lock) my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE,MYF(0),
{ tables->real_name);
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE,MYF(0), error = 1;
tables->real_name); goto err;
error = 1;
goto err;
}
while (global_read_lock && ! thd->killed)
{
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
}
} }
while (global_read_lock && ! thd->killed)
{
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
}
}
for (TABLE_LIST *table=tables ; table ; table=table->next) for (table=tables ; table ; table=table->next)
{ {
char *db=table->db ? table->db : thd->db; char *db=table->db ? table->db : thd->db;
if (!close_temporary_table(thd, db, table->real_name)) if (!close_temporary_table(thd, db, table->real_name))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment