pax_global_header00006660000000000000000000000064116562523570014526gustar00rootroot0000000000000052 comment=ce3ac9fe0d0deda1748e94c8ba52a4c6ae486c81 mysqldiff-0.43/000075500000000000000000000000001165625235700134505ustar00rootroot00000000000000mysqldiff-0.43/.gear/000075500000000000000000000000001165625235700144445ustar00rootroot00000000000000mysqldiff-0.43/.gear/mysqldiff.spec000064400000000000000000000016651165625235700173260ustar00rootroot00000000000000Name: mysqldiff Version: 0.43 Release: alt1 Summary: Comparing the schema (table structures) of two MySQL databases License: Public domain Group: Development/Databases Url: http://adamspiers.org/computing/mysqldiff/ # https://github.com/aspiers/mysqldiff Source: %name-%version.tar Packager: Vitaly Lipatov BuildArch: noarch # Automatically added by buildreq on Tue Nov 08 2011 # optimized out: MySQL-client perl-Encode perl-Pod-Escapes perl-Pod-Simple perl-YAML-Tiny perl-devel perl-podlators BuildRequires: perl-File-Slurp perl-Module-Build %description MySQL-Diff is suite of Perl modules and accompanying CLI script for comparing the schema (table structures) of two MySQL databases. %prep %setup %build %perl_vendor_build %install %perl_vendor_install %files %_bindir/%name %perl_vendor_privlib/MySQL/ %changelog * Tue Nov 08 2011 Vitaly Lipatov 0.43-alt1 - initial build for ALT Linux Sisyphus mysqldiff-0.43/.gear/rules000064400000000000000000000000421165625235700155150ustar00rootroot00000000000000tar: . spec: .gear/mysqldiff.spec mysqldiff-0.43/.gitignore000064400000000000000000000002461165625235700154420ustar00rootroot00000000000000MySQL-Diff-* mysqldiff-*.tar.gz blib* Makefile Makefile.old Build _build* pm_to_blib* *.tar.gz .lwpcookies cover_db Build.bat *.tmp META.yml MYMETA.yml MYMETA.json mysqldiff-0.43/BUGS000064400000000000000000000012271165625235700141350ustar00rootroot00000000000000Please see: https://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff which supercedes this file. Reported by users but unconfirmed: ================================== * If '-P1 3337' is one of the arguments it doesn't seem to get passed to the arguments for mysqldump (according to the debugging). [Darrell Taylor] Others ====== * You can't specify which database to connect to for creating temporary tables. * Things probably break if you use --password or -p without a parameter. * The remote authentication code is barely tested, and hence probably broken. All easy to fix but I'm so short on time! Patches welcome ... mysqldiff-0.43/Build.PL000075500000000000000000000034621165625235700147540ustar00rootroot00000000000000use strict; use warnings; use Module::Build; #created by eumm-migrate.pl my $build = Module::Build->new( module_name => 'MySQL::Diff', keywords => [ qw/mysql diff compare schema tables structure database/ ], license => 'perl', # Module::Build forces us to use v1.4 of the CPAN Meta Spec: # https://rt.cpan.org/Ticket/Display.html?id=71502 # 'meta-spec' => { # version => '2.0', # url => 'http://search.cpan.org/perldoc?CPAN::Meta::Spec', # }, meta_add => { resources => { license => 'http://dev.perl.org/licenses/' , homepage => 'http://adamspiers.org/computing/mysqldiff/', # Module::Build forces us to use v1.4 of the CPAN Meta Spec: # https://rt.cpan.org/Ticket/Display.html?id=71502 # bugtracker => { # web => 'http://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff', # mailto => 'mysqldiff@adamspiers.org', # }, bugtracker => 'http://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff', # Module::Build forces us to use v1.4 of the CPAN Meta Spec: # https://rt.cpan.org/Ticket/Display.html?id=71502 # repository => { # url => 'git:github.com/aspiers/mysqldiff.git', # web => 'http://github.com/aspiers/mysqldiff', # type => 'git', # }, repository => 'http://github.com/aspiers/mysqldiff', }, }, requires => { 'perl' => '5.006', 'Carp' => 0, 'File::Slurp' => 0, 'IO::File' => 0, }, script_files => [ 'bin/mysqldiff' ], all_from => 'lib/MySQL/Diff.pm', configure_requires => { 'Module::Build' => 0, }, ); $build->create_build_script(); mysqldiff-0.43/ChangeLog.OLD000064400000000000000000000143311165625235700156410ustar00rootroot000000000000002003-05-08 Adam Spiers * README: document bugs * Diff.pm, Makefile.PL, README, Utils.pm: - 0.33 - remove Perl 5.6 requirement (thanks to Tatsuhiko Miyagawa for the suggestion) * t/all.t, test.pl, MANIFEST: renamed test.pl to t/all.t to use Test::Harness * INSTALL: Make stuff more obvious * test.pl: don't hang when mysqld up * test.pl: more obvious on failure * README: Class::MakeMethods 2002-09-10 Adam Spiers * MANIFEST: mysqldiff.pl -> mysqldiff * Diff.pm: version 0.32 * Diff.pm, Table.pm, test.pl: Fix bug with INDEX <-> UNIQUE, spotted by John Smith. Thanks John! Added regression tests. 2002-09-10 Adam Spiers * Diff.pm: version 0.32 * Diff.pm, Table.pm, test.pl: Fix bug with INDEX <-> UNIQUE, spotted by John Smith. Thanks John! Added regression tests. 2002-09-08 Adam Spiers * Table.pm: fix most tests with MySQL 4 2002-07-24 Adam Spiers * Diff.pm: version 0.31 * Database.pm, Diff.pm, test.pl: - add almost complete set of tests - fix obscure bugs relating to changing of keys on auto_increment rows 2002-06-20 Adam Spiers * Diff.pm: version 0.30 * README: update my email * MANIFEST: did a 'make manifest' * Database.pm: allow postgres-style '-- foo' comments 2002-06-09 Adam Spiers * README: Requires 5.6.0 now. Thanks to scottb (at) incursio.com for pointing this out. 2002-05-24 Adam Spiers * mysqldiff: add -B 2002-04-02 Adam Spiers * Makefile.PL: require Perl 5.6 2002-03-11 Adam Spiers * mysqldiff: slightly nicer output * mysqldiff, Database.pm: check we're applying changes to a db 2002-02-27 Adam Spiers * Utils.pm: stop directories being treated as files containing SQL definitions 2002-02-20 Adam Spiers * mysqldiff: disastrous typo * mysqldiff, Database.pm, Diff.pm, Utils.pm: version 0.29: - refactoring - new -A (--apply) option - safety check for "use", "drop database" and "create database" commands in file being canonicalised (thanks to Tripp Lilley for the idea) 2002-02-19 Adam Spiers * Diff.pm: 0.28 * Diff.pm, mysqldiff: add -k option 2002-02-14 Adam Spiers * Table.pm: still learning MakeMethods *sigh* * Diff.pm: debugging 2002-02-13 Adam Spiers * Diff.pm, Makefile.PL, Table.pm: - use Class::MakeMethods - don't break on fulltext (doesn't diff it yet though) 2002-01-09 Adam Spiers * Diff.pm: didn't quite do the bugfix right, thanks AGAIN Warwick! 2002-01-08 Adam Spiers * Table.pm: support KEY foo(bar) format with missing space (thanks again to Warwick Smith) * Table.pm: fix problem with older MySQLs which don't include TYPE=MyISAM in dump (thanks again to Warwick Smith) * Table.pm: no need for brackets around primary key * Diff.pm: no need for brackets around primary key (thanks to Warwick Smith) 2001-11-12 Adam Spiers * mysqldiff, Utils.pm: patch from Geoffrey Talvola to allow specifying alternate ports. 2001-11-07 Adam Spiers * Utils.pm: missed a bit of the socket patch, thanks again Jody! * README: slight inaccuracies * Makefile.PL: omit postamble hack for other people * Diff.pm, mysqldiff, Utils.pm: - bump to 0.27 - move parse_arg and available_dbs to MySQL::Utils - incorporate patch from Jody Biggs to support sockets * .cvsignore: *** empty log message *** * Database.pm: bug fix for empty databases from Noam Solomon * Makefile.PL: best to stick to CPAN conventions * Makefile.PL: ChangeLog hack * .cvsignore, INSTALL, Makefile.PL, MANIFEST, README, test.pl: mysqldiff packaging * Diff.pm: bump up version for new release * Database.pm, Diff.pm, Table.pm, Utils.pm: - add missing `use strict' and fix compile errors - doh! - support table options 2001-10-20 Adam Spiers * Diff.pm: fix primary key diffing * Diff.pm: doh! * mysqldiff, Diff.pm: move MySQLdiff -> MySQL::Diff 2001-09-18 Adam Spiers * Table.pm: bugfix from Paul Mineiro (pmineiro@codegrok.com) 2001-02-19 Adam Spiers * mysqldiff: check that parse_arg succeeded 2001-02-14 Adam Spiers * Database.pm, Table.pm, Utils.pm, mysqldiff: split into pieces so we can reuse it in other utilities 2000-11-02 Adam Spiers * mysqldiff: - version 0.25 - rename 'hack' option to avoid clash - fix --host0 bug 2000-10-16 Christof Damian * mysqldiff: - hack to be more tolerant 2000-09-04 Adam Spiers * mysqldiff: avoid warning 2000-08-31 Adam Spiers * mysqldiff: 0.24 2000-08-31 Christof Damian * mysqldiff: - change column needs the name of old and new field 2000-08-24 Adam Spiers * mysqldiff: - add -t|--table-re option for only comparing tables which match a regexp - 0.23 * mysqldiff: - 0.22 - added -o option * mysqldiff: optional comments describing old defs 2000-08-23 Adam Spiers * mysqldiff: double-doh! there was a reason that close() was late ... * mysqldiff: doh! I was closing the filehandle too late, so mysqldump didn't get everything * mysqldiff: - add debugging - handle UNIQUE indices * mysqldiff: fix bug with db:arg parsing * mysqldiff: keep Christof happy ;-) * mysqldiff: - fix broken usage text - prepare for public release (free bug-testers *grin*) * mysqldiff: - added $VERSION - $changes counter is now @changes - outputs banner if @changes non-empty - now compares indices 2000-08-22 Adam Spiers * mysqldiff: temp db names start with test_ * mysqldiff: - first draft - doesn't compare KEYs yet - authentication code completely untested mysqldiff-0.43/Changes000064400000000000000000000024311165625235700147430ustar00rootroot00000000000000# Changes log for Test::CPAN::Meta 0.43 6th October 2011 - fix missing fields in CPAN meta-data 0.43 6th October 2011 - depend on Perl 5.6 - improve docs and CPAN meta-data 0.41 5th October 2011 - tidy up POD 0.40 5th October 2011 - fix issue with hyphens in database names - made --tolerant ignore COLLATE and AUTO_INCREMENT - fixed 'Duplicate specification' options from Getopt::Long - made CLI options case-sensitive - fixed some coding style inconsistencies - remove .cvsignore - merged changes by Barbie - removed use of unmaintained Class::MakeMethods - repackaged distribution with additional package files - restructured modules to use namespace MySQL::Diff::* - restructured modules to use better OO style inferface - Utils.pm now only contains debug handling - added support for more recent MySQL dumps - added more documentation - added more tests - merged changes by Alexandr Ciornii - depend on Perl 5.5.3 - remove lib/MySQL/.cvsignore - fix .gitignore - upgrade Makefile.PL and Build.PL 0.33 8th May 2003 - see ChangeLog.OLD for previous changes. mysqldiff-0.43/INSTALL000064400000000000000000000036031165625235700145030ustar00rootroot00000000000000First please consult the README to check that you have a new enough version of Perl. (N.B. the rest of this document looks a great deal more complicated than it actually is, mainly because I'm trying to encourage people to do the Right Things by using CPANPLUS instead of CPAN, and Module::Build instead of ExtUtils::MakeMaker.) "Automatic" installation via CPANPLUS.pm or CPAN.pm ========================================================================= Installation from either of the recommended installers can be performed at the command line, with either of the two following commands: $ perl -MCPANPLUS -e 'install MySQL::Diff' $ perl -MCPAN -e 'install MySQL::Diff' Although CPAN.pm is the default installer for many, with the release of Perl 5.10, CPANPLUS.pm is now also available in core. However, if you use an earlier version of Perl, you can install CPANPLUS from the CPAN with the following command: $ perl -MCPAN -e 'install CPANPLUS' "Manual" installation ========================================================================= First ensure you have File::Slurp installed. Then there are two options: 1) Install via Module::Build (recommended) -------------------------------------------- Ensure that Module::Build is installed, e.g. $ perl -MCPAN -e 'install Module::Build' or $ perl -MCPANPLUS -e 'install Module::Build' Then run these commands: perl Build.PL perl Build perl Build test perl Build install 2) Install via ExtUtils::MakeMaker (deprecated but simpler) ------------------------------------------------------------- You can install MySQL::Diff in the traditional way by running these commands: perl Makefile.PL make make test make install And finally ... ========================================================================= Note that the test suite will not run properly unless you have a MySQL server which it can connect to. mysqldiff-0.43/MANIFEST000064400000000000000000000004041165625235700145770ustar00rootroot00000000000000BUGS Build.PL bin/mysqldiff Changes ChangeLog.OLD INSTALL lib/MySQL/Diff.pm lib/MySQL/Diff/Database.pm lib/MySQL/Diff/Table.pm lib/MySQL/Diff/Utils.pm MANIFEST Makefile.PL META.yml README t/01use.t t/90podtest.t t/91podcover.t t/94metatest.t t/all.t META.json mysqldiff-0.43/META.json000064400000000000000000000031031165625235700150660ustar00rootroot00000000000000{ "abstract" : "Generates a database upgrade instruction set", "author" : [ "Adam Spiers " ], "dynamic_config" : 1, "generated_by" : "Module::Build version 0.38, CPAN::Meta::Converter version 2.112621", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "MySQL-Diff", "prereqs" : { "configure" : { "requires" : { "Module::Build" : 0 } }, "runtime" : { "requires" : { "Carp" : 0, "File::Slurp" : 0, "IO::File" : 0, "perl" : "5.006" } } }, "provides" : { "MySQL::Diff" : { "file" : "lib/MySQL/Diff.pm", "version" : "0.43" }, "MySQL::Diff::Database" : { "file" : "lib/MySQL/Diff/Database.pm", "version" : "0.43" }, "MySQL::Diff::Table" : { "file" : "lib/MySQL/Diff/Table.pm", "version" : "0.43" }, "MySQL::Diff::Utils" : { "file" : "lib/MySQL/Diff/Utils.pm", "version" : "0.43" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "http://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff" }, "homepage" : "http://adamspiers.org/computing/mysqldiff/", "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "http://github.com/aspiers/mysqldiff" } }, "version" : "0.43" } mysqldiff-0.43/META.yml000064400000000000000000000017521165625235700147260ustar00rootroot00000000000000--- abstract: 'Generates a database upgrade instruction set' author: - 'Adam Spiers ' build_requires: {} configure_requires: Module::Build: 0 dynamic_config: 1 generated_by: 'Module::Build version 0.38, CPAN::Meta::Converter version 2.112621' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: MySQL-Diff provides: MySQL::Diff: file: lib/MySQL/Diff.pm version: 0.43 MySQL::Diff::Database: file: lib/MySQL/Diff/Database.pm version: 0.43 MySQL::Diff::Table: file: lib/MySQL/Diff/Table.pm version: 0.43 MySQL::Diff::Utils: file: lib/MySQL/Diff/Utils.pm version: 0.43 requires: Carp: 0 File::Slurp: 0 IO::File: 0 perl: 5.006 resources: bugtracker: http://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff homepage: http://adamspiers.org/computing/mysqldiff/ license: http://dev.perl.org/licenses/ repository: http://github.com/aspiers/mysqldiff version: 0.43 mysqldiff-0.43/Makefile.PL000075500000000000000000000043451165625235700154330ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; require 5.005003; use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile1( NAME => 'MySQL::Diff', #KEYWORDS => [ qw/mysql diff compare schema tables structure database/ ], LICENSE => 'perl', META_ADD => { resources => { license => [ 'http://dev.perl.org/licenses/' ], homepage => 'http://adamspiers.org/computing/mysqldiff/', bugtracker => { web => 'http://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff', mailto => 'mysqldiff@adamspiers.org', }, repository => { url => 'git:github.com/aspiers/mysqldiff.git', web => 'http://github.com/aspiers/mysqldiff', type => 'git', }, #repository => 'http://github.com/aspiers/mysqldiff', }, }, MIN_PERL_VERSION => '5.006', PREREQ_PM => { 'Carp' => 0, 'File::Slurp' => 0, 'IO::File' => 0, }, EXE_FILES => [ 'bin/mysqldiff' ], VERSION_FROM => 'lib/MySQL/Diff.pm', # finds $VERSION ); sub WriteMakefile1 { #Written by Alexandr Ciornii, version 0.21. Added by eumm-upgrade. my %params = @_; my $eumm_version = $ExtUtils::MakeMaker::VERSION; $eumm_version = eval $eumm_version; die "EXTRA_META is deprecated" if exists $params{EXTRA_META}; die "License not specified" if not exists $params{LICENSE}; if ($params{BUILD_REQUIRES} and $eumm_version < 6.5503) { #EUMM 6.5502 has problems with BUILD_REQUIRES $params{PREREQ_PM}={ %{$params{PREREQ_PM} || {}} , %{$params{BUILD_REQUIRES}} }; delete $params{BUILD_REQUIRES}; } delete $params{CONFIGURE_REQUIRES} if $eumm_version < 6.52; delete $params{MIN_PERL_VERSION} if $eumm_version < 6.48; delete $params{META_MERGE} if $eumm_version < 6.46; delete $params{META_ADD} if $eumm_version < 6.46; delete $params{LICENSE} if $eumm_version < 6.31; delete $params{AUTHOR} if $] < 5.005; delete $params{ABSTRACT_FROM} if $] < 5.005; delete $params{BINARY_LOCATION} if $] < 5.005; WriteMakefile(%params); } mysqldiff-0.43/README000064400000000000000000000024021165625235700143260ustar00rootroot00000000000000README for MySQL-Diff MySQL-Diff ============ MySQL-Diff is suite of Perl modules and accompanying CLI script for comparing the schema (table structures) of two MySQL databases. Prerequisites ------------- This suite probably requires Perl 5.005 or higher. Availability ------------ The latest version of MySQL-Diff is available from http://adamspiers.org/computing/mysqldiff/ and eventually (a possibly slightly older version) from the Comprehensive Perl Archive Network (CPAN). Visit to find a CPAN site near you. Installation ------------ See INSTALL file. Documentation ------------- Homepage: http://adamspiers.org/computing/mysqldiff/ Documentation at CPAN: http://search.cpan.org/dist/MySQL-Diff/ Support ------- Questions, patches, and suggestions for MySQL-Diff should just be sent to me at . Bug reports should be submitted at the website below. Known bugs ---------- See https://rt.cpan.org/Public/Dist/Display.html?Name=MySQL-Diff Copyright --------- (c) 2000-2011 Adam Spiers , all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. mysqldiff-0.43/bin/000075500000000000000000000000001165625235700142205ustar00rootroot00000000000000mysqldiff-0.43/bin/mysqldiff000075500000000000000000000155451165625235700161560ustar00rootroot00000000000000#!/usr/bin/perl -w =head1 NAME mysqldiff - compare MySQL database schemas =head1 SYNOPSIS mysqldiff [B] B B mysqldiff --help =head1 DESCRIPTION F is a Perl script front-end to the L module L which compares the data structures (i.e. schema / table definitions) of two L databases, and returns the differences as a sequence of MySQL commands suitable for piping into F which will transform the structure of the first database to be identical to that of the second (I F and F). Database structures can be compared whether they are files containing table definitions or existing databases, local or remote. B The program makes I attempt to compare any of the data which may be stored in the databases. It is purely for comparing the table definitions. I have no plans to implement data comparison; it is a complex problem and I have no need of such functionality anyway. However there is another program L which does this, and is based on an older program called F which seems to have vanished off the 'net. For PostgreSQL there are similar tools such as L and L. =head1 EXAMPLES # compare table definitions in two files mysqldiff db1.mysql db2.mysql # compare table definitions in a file 'db1.mysql' with a database 'db2' mysqldiff db1.mysql db2 # interactively upgrade schema of database 'db1' to be like the # schema described in the file 'db2.mysql' mysqldiff -A db1 db2.mysql # compare table definitions in two databases on a remote machine mysqldiff --host=remote.host.com --user=myaccount db1 db2 # compare table definitions in a local database 'foo' with a # database 'bar' on a remote machine, when a file foo already # exists in the current directory mysqldiff --host2=remote.host.com --password=secret db:foo bar =head1 OPTIONS =for comment FIXME - add option docs here More details to come; for now run C. =head1 INTERNALS For both of the database structures being compared, the following happens: =over 4 =item If the argument is a valid filename, the file is used to create a temporary database which C is run on to obtain the table definitions in canonicalised form. The temporary database is then dropped. (The temporary database is named C because default MySQL permissions allow anyone to create databases beginning with the prefix C.) =item If the argument is a database, C is run directly on it. =item Where authentication is required, the hostname, username, and password given by the corresponding options are used (type C for more information). =item Each set of table definitions is now parsed into tables, and fields and index keys within those tables; these are compared, and the differences outputted in the form of MySQL statements. =back =cut use strict; use 5.006; # due to 'our' and qr// use FindBin qw($RealBin $Script); use lib $RealBin; use Getopt::Long qw(:config no_ignore_case); use IO::File; use MySQL::Diff; my %opts = (); GetOptions(\%opts, "help|?", "debug|d:i", "apply|A", "batch-apply|B", "keep-old-tables|k", "no-old-defs|n", "only-both|o", "table-re|t=s", "host|h=s", "port|P=s", "socket|s=s", "user|u=s", "password|p:s", "host1=s", "port1=s", "socket1=s", "user1=s", "password1:s", "host2=s", "port2=s", "socket2=s", "user2=s", "password2:s", "tolerant|i" ) or usage(); usage() if (@ARGV != 2 or $opts{help}); $opts{debug} ||= 0; my $md = MySQL::Diff->new(%opts); for my $num (1, 2) { my $new_db = $md->register_db($ARGV[$num-1], $num); usage($new_db) unless ref $new_db; } $| = 1; my $diffs = $md->diff(); print $diffs; apply($md, $diffs) if $opts{apply} || $opts{'batch-apply'}; exit 0; ############################################################################## sub usage { print STDERR @_, "\n" if @_; die < Options: -?, --help show this help -A, --apply interactively patch database1 to match database2 -B, --batch-apply non-interactively patch database1 to match database2 -d, --debug[=N] enable debugging [level N, default 1] -o, --only-both only output changes for tables in both databases -k, --keep-old-tables don't output DROP TABLE commands -n, --no-old-defs suppress comments describing old definitions -t, --table-re=REGEXP restrict comparisons to tables matching REGEXP -i, --tolerant ignore DEFAULT, AUTO_INCREMENT, COLLATE, and formatting changes -h, --host=... connect to host -P, --port=... use this port for connection -u, --user=... user for login if not current user -p, --password[=...] password to use when connecting to server -s, --socket=... socket to use when connecting to server for only, where N == 1 or 2, --hostN=... connect to host --portN=... use this port for connection --userN=... user for login if not current user --passwordN[=...] password to use when connecting to server --socketN=... socket to use when connecting to server Databases can be either files or database names. If there is an ambiguity, the file will be preferred; to prevent this prefix the database argument with `db:'. EOF } sub apply { my ($md, $diffs) = @_; if (! $diffs) { print "No differences to apply.\n"; return; } my $db0 = $md->db1->name; if ($md->db1->source_type ne 'db') { die "$db0 is not a database; cannot apply changes.\n"; } unless ($opts{'batch-apply'}) { print "\nApply above changes to $db0 [y/N] ? "; print "\n(CAUTION! Changes contain DROP TABLE commands.) " if $diffs =~ /\bDROP TABLE\b/i; my $reply = ; return unless $reply =~ /^y(es)?$/i; } print "Applying changes ... "; my $args = $md->db1->auth_args; my $pipe = "mysql$args $db0"; my $fh = IO::File->new("|$pipe") or die "Couldn't open pipe to '$pipe': $!\n"; print $fh $diffs; $fh->close or die "Couldn't close pipe: $!\n"; print "done.\n"; } =head1 BUGS, DEVELOPMENT, CONTRIBUTING See L. =head1 COPYRIGHT AND LICENSE Copyright (c) 2000-2011 Adam Spiers. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L, L, L, L =head1 AUTHOR Adam Spiers =cut mysqldiff-0.43/lib/000075500000000000000000000000001165625235700142165ustar00rootroot00000000000000mysqldiff-0.43/lib/MySQL/000075500000000000000000000000001165625235700151635ustar00rootroot00000000000000mysqldiff-0.43/lib/MySQL/Diff.pm000064400000000000000000000323121165625235700163720ustar00rootroot00000000000000package MySQL::Diff; =head1 NAME MySQL::Diff - Generates a database upgrade instruction set =head1 SYNOPSIS use MySQL::Diff; my $md = MySQL::Diff->new( %options ); my $db1 = $md->register_db($ARGV[0], 1); my $db2 = $md->register_db($ARGV[1], 2); my $diffs = $md->diff(); =head1 DESCRIPTION Generates the SQL instructions required to upgrade the first database to match the second. =cut use warnings; use strict; our $VERSION = '0.43'; # ------------------------------------------------------------------------------ # Libraries use MySQL::Diff::Database; use MySQL::Diff::Utils qw(debug debug_level debug_file); use Data::Dumper; # ------------------------------------------------------------------------------ =head1 METHODS =head2 Constructor =over 4 =item new( %options ) Instantiate the objects, providing the command line options for database access and process requirements. =back =cut sub new { my $class = shift; my %hash = @_; my $self = {}; bless $self, ref $class || $class; $self->{opts} = \%hash; if($hash{debug}) { debug_level($hash{debug}) ; delete $hash{debug}; } if($hash{debug_file}) { debug_file($hash{debug_file}) ; delete $hash{debug_file}; } debug(3,"\nconstructing new MySQL::Diff"); return $self; } =head2 Public Methods Fuller documentation will appear here in time :) =over 4 =item * register_db($name,$inx) Reference the database, and setup a connection. The name can be an already existing 'MySQL::Diff::Database' database object. The index can be '1' or '2', and refers both to the order of the diff, and to the host, port, username and password arguments that have been supplied. =cut sub register_db { my ($self, $name, $inx) = @_; return unless $inx == 1 || $inx == 2; my $db = ref $name eq 'MySQL::Diff::Database' ? $name : $self->_load_database($name,$inx); $self->{databases}[$inx-1] = $db; return $db; } =item * db1() =item * db2() Return the first and second databases registered via C. =cut sub db1 { shift->{databases}->[0] } sub db2 { shift->{databases}->[1] } =item * diff() Performs the diff, returning a string containing the commands needed to change the schema of the first database into that of the second. =back =cut sub diff { my $self = shift; my $table_re = $self->{opts}{'table-re'}; my @changes; debug(1, "\ncomparing databases"); for my $table1 ($self->db1->tables()) { my $name = $table1->name(); debug(4, "table 1 $name = ".Dumper($table1)); if ($table_re && $name !~ $table_re) { debug(2,"table '$name' didn't match /$table_re/; ignoring"); next; } debug(2,"looking at tables called '$name'"); if (my $table2 = $self->db2->table_by_name($name)) { debug(3,"comparing tables called '$name'"); push @changes, $self->_diff_tables($table1, $table2); } else { debug(3,"table '$name' dropped"); push @changes, "DROP TABLE $name;\n\n" unless $self->{opts}{'only-both'} || $self->{opts}{'keep-old-tables'}; } } for my $table2 ($self->db2->tables()) { my $name = $table2->name(); debug(4, "table 2 $name = ".Dumper($table2)); if ($table_re && $name !~ $table_re) { debug(2,"table '$name' matched $self->{opts}{'table-re'}; ignoring"); next; } if (! $self->db1->table_by_name($name)) { debug(3,"table '$name' added"); debug(4,"table '$name' added '".$table2->def()."'"); push @changes, $table2->def() . "\n" unless $self->{opts}{'only-both'}; } } debug(4,join '', @changes); my $out = ''; if (@changes) { $out .= $self->_diff_banner(); $out .= join '', @changes; } return $out; } # ------------------------------------------------------------------------------ # Private Methods sub _diff_banner { my ($self) = @_; my $summary1 = $self->db1->summary(); my $summary2 = $self->db2->summary(); my $opt_text = join ', ', map { $self->{opts}{$_} eq '1' ? $_ : "$_=$self->{opts}{$_}" } keys %{$self->{opts}}; $opt_text = "## Options: $opt_text\n" if $opt_text; my $now = scalar localtime(); return <_diff_fields(@_), $self->_diff_indices(@_), $self->_diff_primary_key(@_), $self->_diff_options(@_) ); $changes[-1] =~ s/\n*$/\n/ if (@changes); return @changes; } sub _diff_fields { my ($self, $table1, $table2) = @_; my $name1 = $table1->name(); my $fields1 = $table1->fields; my $fields2 = $table2->fields; return () unless $fields1 || $fields2; my @changes; if($fields1) { for my $field (keys %$fields1) { debug(3,"table1 had field '$field'"); my $f1 = $fields1->{$field}; my $f2 = $fields2->{$field}; if ($fields2 && $f2) { if ($self->{opts}{tolerant}) { for ($f1, $f2) { s/ COLLATE [\w_]+//gi; } } if ($f1 ne $f2) { if (not $self->{opts}{tolerant} or (($f1 !~ m/$f2\(\d+,\d+\)/) and ($f1 ne "$f2 DEFAULT '' NOT NULL") and ($f1 ne "$f2 NOT NULL") )) { debug(3,"field '$field' changed"); my $change = "ALTER TABLE $name1 CHANGE COLUMN $field $field $f2;"; $change .= " # was $f1" unless $self->{opts}{'no-old-defs'}; $change .= "\n"; push @changes, $change; } } } else { debug(3,"field '$field' removed"); my $change = "ALTER TABLE $name1 DROP COLUMN $field;"; $change .= " # was $fields1->{$field}" unless $self->{opts}{'no-old-defs'}; $change .= "\n"; push @changes, $change; } } } if($fields2) { for my $field (keys %$fields2) { unless($fields1 && $fields1->{$field}) { debug(3,"field '$field' added"); push @changes, "ALTER TABLE $name1 ADD COLUMN $field $fields2->{$field};\n"; } } } return @changes; } sub _diff_indices { my ($self, $table1, $table2) = @_; my $name1 = $table1->name(); my $indices1 = $table1->indices(); my $indices2 = $table2->indices(); return () unless $indices1 || $indices2; my @changes; if($indices1) { for my $index (keys %$indices1) { debug(3,"table1 had index '$index'"); my $old_type = $table1->is_unique($index) ? 'UNIQUE' : $table1->is_fulltext($index) ? 'FULLTEXT INDEX' : 'INDEX'; if ($indices2 && $indices2->{$index}) { if( ($indices1->{$index} ne $indices2->{$index}) or ($table1->is_unique($index) xor $table2->is_unique($index)) or ($table1->is_fulltext($index) xor $table2->is_fulltext($index)) ) { debug(3,"index '$index' changed"); my $new_type = $table2->is_unique($index) ? 'UNIQUE' : $table2->is_fulltext($index) ? 'FULLTEXT INDEX' : 'INDEX'; my $changes = "ALTER TABLE $name1 DROP INDEX $index;"; $changes .= " # was $old_type ($indices1->{$index})" unless $self->{opts}{'no-old-defs'}; $changes .= "\nALTER TABLE $name1 ADD $new_type $index ($indices2->{$index});\n"; push @changes, $changes; } } else { debug(3,"index '$index' removed"); my $auto = _check_for_auto_col($table2, $indices1->{$index}, 1) || ''; my $changes = $auto ? _index_auto_col($table1, $indices1->{$index}) : ''; $changes .= "ALTER TABLE $name1 DROP INDEX $index;"; $changes .= " # was $old_type ($indices1->{$index})" unless $self->{opts}{'no-old-defs'}; $changes .= "\n"; push @changes, $changes; } } } if($indices2) { for my $index (keys %$indices2) { next if($indices1 && $indices1->{$index}); debug(3,"index '$index' added"); my $new_type = $table2->is_unique($index) ? 'UNIQUE' : 'INDEX'; push @changes, "ALTER TABLE $name1 ADD $new_type $index ($indices2->{$index});\n"; } } return @changes; } sub _diff_primary_key { my ($self, $table1, $table2) = @_; my $name1 = $table1->name(); my $primary1 = $table1->primary_key(); my $primary2 = $table2->primary_key(); return () unless $primary1 || $primary2; my @changes; if ($primary1 && ! $primary2) { debug(3,"primary key '$primary1' dropped"); my $changes = _index_auto_col($table2, $primary1); $changes .= "ALTER TABLE $name1 DROP PRIMARY KEY;"; $changes .= " # was $primary1" unless $self->{opts}{'no-old-defs'}; return ( "$changes\n" ); } if (! $primary1 && $primary2) { debug(3,"primary key '$primary2' added"); return ("ALTER TABLE $name1 ADD PRIMARY KEY $primary2;\n"); } if ($primary1 ne $primary2) { debug(3,"primary key changed"); my $auto = _check_for_auto_col($table2, $primary1) || ''; my $changes = $auto ? _index_auto_col($table2, $auto) : ''; $changes .= "ALTER TABLE $name1 DROP PRIMARY KEY;"; $changes .= " # was $primary1" unless $self->{opts}{'no-old-defs'}; $changes .= "\nALTER TABLE $name1 ADD PRIMARY KEY $primary2;\n"; $changes .= "ALTER TABLE $name1 DROP INDEX $auto;\n" if($auto); push @changes, $changes; } return @changes; } # If we're about to drop a composite (multi-column) index, we need to # check whether any of the columns in the composite index are # auto_increment; if so, we have to add an index for that # auto_increment column *before* dropping the composite index, since # auto_increment columns must always be indexed. sub _check_for_auto_col { my ($table, $fields, $primary) = @_; $fields =~ s/^\s*\((.*)\)\s*$/$1/g; # strip brackets if any my @fields = split /\s*,\s*/, $fields; for my $field (@fields) { next if($table->field($field) !~ /auto_increment/i); next if($table->isa_index($field)); next if($primary && $table->isa_primary($field)); return $field; } return; } sub _index_auto_col { my ($table, $field) = @_; my $name = $table->name; return "ALTER TABLE $name ADD INDEX ($field); # auto columns must always be indexed\n"; } sub _diff_options { my ($self, $table1, $table2) = @_; my $name = $table1->name(); my $options1 = $table1->options(); my $options2 = $table2->options(); return () unless $options1 || $options2; my @changes; if ($self->{opts}{tolerant}) { for ($options1, $options2) { s/ AUTO_INCREMENT=\d+//gi; s/ COLLATE=[\w_]+//gi; } } if ($options1 ne $options2) { my $change = "ALTER TABLE $name $options2;"; $change .= " # was " . ($options1 || 'blank') unless $self->{opts}{'no-old-defs'}; $change .= "\n"; push @changes, $change; } return @changes; } sub _load_database { my ($self, $arg, $authnum) = @_; debug(2, "parsing arg $authnum: '$arg'\n"); my %auth; for my $auth (qw/dbh host port user password socket/) { $auth{$auth} = $self->{opts}{"$auth$authnum"} || $self->{opts}{$auth}; delete $auth{$auth} unless $auth{$auth}; } if ($arg =~ /^db:(.*)/) { return MySQL::Diff::Database->new(db => $1, auth => \%auth); } if ($self->{opts}{"dbh"} || $self->{opts}{"host$authnum"} || $self->{opts}{"port$authnum"} || $self->{opts}{"user$authnum"} || $self->{opts}{"password$authnum"} || $self->{opts}{"socket$authnum"}) { return MySQL::Diff::Database->new(db => $arg, auth => \%auth); } if (-f $arg) { return MySQL::Diff::Database->new(file => $arg, auth => \%auth); } my %dbs = MySQL::Diff::Database::available_dbs(%auth); debug(2, " available databases: ", (join ', ', keys %dbs), "\n"); if ($dbs{$arg}) { return MySQL::Diff::Database->new(db => $arg, auth => \%auth); } warn "'$arg' is not a valid file or database.\n"; return; } sub _debug_level { my ($self,$level) = @_; debug_level($level); } 1; __END__ =head1 COPYRIGHT AND LICENSE Copyright (c) 2000-2011 Adam Spiers. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L =head1 AUTHOR Adam Spiers =cut mysqldiff-0.43/lib/MySQL/Diff/000075500000000000000000000000001165625235700160335ustar00rootroot00000000000000mysqldiff-0.43/lib/MySQL/Diff/Database.pm000064400000000000000000000154221165625235700201010ustar00rootroot00000000000000package MySQL::Diff::Database; =head1 NAME MySQL::Diff::Database - Database Definition Class =head1 SYNOPSIS use MySQL::Diff::Database; my $db = MySQL::Diff::Database->new(%options); my $source = $db->source_type(); my $summary = $db->summary(); my $name = $db->name(); my @tables = $db->tables(); my $table_def = $db->table_by_name($table); my @dbs = MySQL::Diff::Database::available_dbs(); =head1 DESCRIPTION Parses a database definition into component parts. =cut use warnings; use strict; our $VERSION = '0.43'; # ------------------------------------------------------------------------------ # Libraries use Carp qw(:DEFAULT); use File::Slurp; use IO::File; use MySQL::Diff::Utils qw(debug); use MySQL::Diff::Table; # ------------------------------------------------------------------------------ =head1 METHODS =head2 Constructor =over 4 =item new( %options ) Instantiate the objects, providing the command line options for database access and process requirements. =back =cut sub new { my $class = shift; my %p = @_; my $self = {}; bless $self, ref $class || $class; debug(3,"\nconstructing new MySQL::Diff::Database"); my $string = _auth_args_string(%{$p{auth}}); debug(3,"auth args: $string"); $self->{_source}{auth} = $string; $self->{_source}{dbh} = $p{dbh} if($p{dbh}); if ($p{file}) { $self->_canonicalise_file($p{file}); } elsif ($p{db}) { $self->_read_db($p{db}); } else { confess "MySQL::Diff::Database::new called without db or file params"; } $self->_parse_defs(); return $self; } =head2 Public Methods =over 4 =item * source_type() Returns 'file' if the data source is a text file, and 'db' if connected directly to a database. =cut sub source_type { my $self = shift; return 'file' if $self->{_source}{file}; return 'db' if $self->{_source}{db}; } =item * summary() Provides a summary of the database. =cut sub summary { my $self = shift; if ($self->{_source}{file}) { return "file: " . $self->{_source}{file}; } elsif ($self->{_source}{db}) { my $args = $self->{_source}{auth}; $args =~ tr/-//d; $args =~ s/\bpassword=\S+//; $args =~ s/^\s*(.*?)\s*$/$1/; my $summary = " db: " . $self->{_source}{db}; $summary .= " ($args)" if $args; return $summary; } else { return 'unknown'; } } =item * name() Returns the name of the database. =cut sub name { my $self = shift; return $self->{_source}{file} || $self->{_source}{db}; } =item * tables() Returns a list of tables for the current database. =cut sub tables { my $self = shift; return @{$self->{_tables}}; } =item * table_by_name( $name ) Returns the table definition (see L) for the given table. =cut sub table_by_name { my ($self,$name) = @_; return $self->{_by_name}{$name}; } =back =head1 FUNCTIONS =head2 Public Functions =over 4 =item * available_dbs() Returns a list of the available databases. Note that is used as a function call, not a method call. =cut sub available_dbs { my %auth = @_; my $args = _auth_args_string(%auth); # evil but we don't use DBI because I don't want to implement -p properly # not that this works with -p anyway ... my $fh = IO::File->new("mysqlshow$args |") or die "Couldn't execute 'mysqlshow$args': $!\n"; my @dbs; while (<$fh>) { next unless /^\| ([\w-]+)/; push @dbs, $1; } $fh->close() or die "mysqlshow$args failed: $!"; return map { $_ => 1 } @dbs; } =back =cut # ------------------------------------------------------------------------------ # Private Methods sub _canonicalise_file { my ($self, $file) = @_; $self->{_source}{file} = $file; debug(2,"fetching table defs from file $file"); # FIXME: option to avoid create-and-dump bit # create a temporary database using defs from file ... # hopefully the temp db is unique! my $temp_db = sprintf "test_mysqldiff-temp-%d_%d_%d", time(), $$, rand(); debug(3,"creating temporary database $temp_db"); my $defs = read_file($file); die "$file contains dangerous command '$1'; aborting.\n" if $defs =~ /;\s*(use|((drop|create)\s+database))\b/i; my $args = $self->{_source}{auth}; my $fh = IO::File->new("| mysql $args") or die "Couldn't execute 'mysql$args': $!\n"; print $fh "\nCREATE DATABASE \`$temp_db\`;\nUSE \`$temp_db\`;\n"; print $fh $defs; $fh->close; # ... and then retrieve defs from mysqldump. Hence we've used # MySQL to massage the defs file into canonical form. $self->_get_defs($temp_db); debug(3,"dropping temporary database $temp_db"); $fh = IO::File->new("| mysql $args") or die "Couldn't execute 'mysql$args': $!\n"; print $fh "DROP DATABASE \`$temp_db\`;\n"; $fh->close; } sub _read_db { my ($self, $db) = @_; $self->{_source}{db} = $db; debug(3, "fetching table defs from db $db"); $self->_get_defs($db); } sub _get_defs { my ($self, $db) = @_; my $args = $self->{_source}{auth}; my $fh = IO::File->new("mysqldump -d $args $db 2>&1 |") or die "Couldn't read ${db}'s table defs via mysqldump: $!\n"; debug(3, "running mysqldump -d $args $db"); my $defs = $self->{_defs} = [ <$fh> ]; $fh->close; if (grep /mysqldump: Got error: .*: Unknown database/, @$defs) { die <{_tables}; debug(2, "parsing table defs"); my $defs = join '', grep ! /^\s*(\#|--|SET|\/\*)/, @{$self->{_defs}}; $defs =~ s/`//sg; my @tables = split /(?=^\s*(?:create|alter|drop)\s+table\s+)/im, $defs; $self->{_tables} = []; for my $table (@tables) { debug(4, " table def [$table]"); if($table =~ /create\s+table/i) { my $obj = MySQL::Diff::Table->new(source => $self->{_source}, def => $table); push @{$self->{_tables}}, $obj; $self->{_by_name}{$obj->name()} = $obj; } } } sub _auth_args_string { my %auth = @_; my $args = ''; for my $arg (qw/host port user password socket/) { $args .= " --$arg=$auth{$arg}" if $auth{$arg}; } return $args; } 1; __END__ =head1 COPYRIGHT AND LICENSE Copyright (c) 2000-2011 Adam Spiers. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L =head1 AUTHOR Adam Spiers =cut mysqldiff-0.43/lib/MySQL/Diff/Table.pm000064400000000000000000000156251165625235700174310ustar00rootroot00000000000000package MySQL::Diff::Table; =head1 NAME MySQL::Diff::Table - Table Definition Class =head1 SYNOPSIS use MySQL::Diff::Table my $db = MySQL::Diff::Database->new(%options); my $def = $db->def(); my $name = $db->name(); my $field = $db->field(); my $fields = $db->fields(); # %$fields my $primary_key = $db->primary_key(); my $indices = $db->indices(); # %$indices my $options = $db->options(); my $isfield = $db->isa_field($field); my $isprimary = $db->isa_primary($field); my $isindex = $db->isa_index($field); my $isunique = $db->is_unique($field); my $isfulltext = $db->is_fulltext($field); =head1 DESCRIPTION Parses a table definition into component parts. =cut use warnings; use strict; our $VERSION = '0.43'; # ------------------------------------------------------------------------------ # Libraries use Carp qw(:DEFAULT); use MySQL::Diff::Utils qw(debug); # ------------------------------------------------------------------------------ =head1 METHODS =head2 Constructor =over 4 =item new( %options ) Instantiate the objects, providing the command line options for database access and process requirements. =cut sub new { my $class = shift; my %hash = @_; my $self = {}; bless $self, ref $class || $class; $self->{$_} = $hash{$_} for(keys %hash); debug(3,"\nconstructing new MySQL::Diff::Table"); croak "MySQL::Diff::Table::new called without def params" unless $self->{def}; $self->_parse; return $self; } =back =head2 Public Methods Fuller documentation will appear here in time :) =over 4 =item * def Returns the table definition as a string. =item * name Returns the name of the current table. =item * field Returns the current field definition of the given field. =item * fields Returns an array reference to a list of fields. =item * primary_key Returns a hash reference to fields used as primary key fields. =item * indices Returns a hash reference to fields used as index fields. =item * options Returns the additional options added to the table definition. =item * isa_field Returns 1 if given field is used in the current table definition, otherwise returns 0. =item * isa_primary Returns 1 if given field is defined as a primary key, otherwise returns 0. =item * isa_index Returns 1 if given field is used as an index field, otherwise returns 0. =item * is_unique Returns 1 if given field is used as unique index field, otherwise returns 0. =item * is_fulltext Returns 1 if given field is used as fulltext index field, otherwise returns 0. =back =cut sub def { my $self = shift; return $self->{def}; } sub name { my $self = shift; return $self->{name}; } sub field { my $self = shift; return $self->{fields}{$_[0]}; } sub fields { my $self = shift; return $self->{fields}; } sub primary_key { my $self = shift; return $self->{primary_key}; } sub indices { my $self = shift; return $self->{indices}; } sub options { my $self = shift; return $self->{options}; } sub isa_field { my $self = shift; return $_[0] && $self->{fields}{$_[0]} ? 1 : 0; } sub isa_primary { my $self = shift; return $_[0] && $self->{primary}{$_[0]} ? 1 : 0; } sub isa_index { my $self = shift; return $_[0] && $self->{indices}{$_[0]} ? 1 : 0; } sub is_unique { my $self = shift; return $_[0] && $self->{unique}{$_[0]} ? 1 : 0; } sub is_fulltext { my $self = shift; return $_[0] && $self->{fulltext}{$_[0]} ? 1 : 0; } # ------------------------------------------------------------------------------ # Private Methods sub _parse { my $self = shift; $self->{def} =~ s/`([^`]+)`/$1/gs; # later versions quote names $self->{def} =~ s/\n+/\n/; $self->{lines} = [ grep ! /^\s*$/, split /(?=^)/m, $self->{def} ]; my @lines = @{$self->{lines}}; debug(4,"parsing table def '$self->{def}'"); my $name; if ($lines[0] =~ /^\s*create\s+table\s+(\S+)\s+\(\s*$/i) { $self->{name} = $1; debug(3,"got table name '$self->{name}'"); shift @lines; } else { croak "couldn't figure out table name"; } while (@lines) { $_ = shift @lines; s/^\s*(.*?),?\s*$/$1/; # trim whitespace and trailing commas debug(4,"line: [$_]"); if (/^PRIMARY\s+KEY\s+(.+)$/) { my $primary = $1; croak "two primary keys in table '$self->{name}': '$primary', '$self->{primary_key}'\n" if $self->{primary_key}; debug(4,"got primary key $primary"); $self->{primary_key} = $primary; $primary =~ s/\((.*?)\)/$1/; $self->{primary}{$_} = 1 for(split(/,/, $primary)); next; } if (/^(KEY|UNIQUE(?: KEY)?)\s+(\S+?)(?:\s+USING\s+(?:BTREE|HASH|RTREE))?\s*\((.*)\)$/) { my ($type, $key, $val) = ($1, $2, $3); croak "index '$key' duplicated in table '$name'\n" if $self->{indices}{$key}; $self->{indices}{$key} = $val; $self->{unique}{$key} = 1 if($type =~ /unique/i); debug(4, "got ", defined $self->{unique}{$key} ? 'unique ' : '', "index key '$key': ($val)"); next; } if (/^(FULLTEXT(?:\s+KEY|INDEX)?)\s+(\S+?)\s*\((.*)\)$/) { my ($type, $key, $val) = ($1, $2, $3); croak "FULLTEXT index '$key' duplicated in table '$name'\n" if $self->{fulltext}{$key}; $self->{indices}{$key} = $val; $self->{fulltext}{$key} = 1; debug(4,"got FULLTEXT index '$key': ($val)"); next; } if (/^\)\s*(.*?);$/) { # end of table definition $self->{options} = $1; debug(4,"got table options '$self->{options}'"); last; } if (/^(\S+)\s*(.*)/) { my ($field, $fdef) = ($1, $2); croak "definition for field '$field' duplicated in table '$name'\n" if $self->{fields}{$field}; $self->{fields}{$field} = $fdef; debug(4,"got field def '$field': $fdef"); next; } croak "unparsable line in definition for table '$self->{name}':\n$_"; } warn "table '$self->{name}' didn't have terminator\n" unless defined $self->{options}; @lines = grep ! m{^/\*!40\d{3} .*? \*/;}, @lines; @lines = grep ! m{^(SET |DROP TABLE)}, @lines; warn "table '$self->{name}' had trailing garbage:\n", join '', @lines if @lines; } 1; __END__ =head1 COPYRIGHT AND LICENSE Copyright (c) 2000-2011 Adam Spiers. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L =head1 AUTHOR Adam Spiers =cut mysqldiff-0.43/lib/MySQL/Diff/Utils.pm000064400000000000000000000045341165625235700174770ustar00rootroot00000000000000package MySQL::Diff::Utils; =head1 NAME MySQL::Diff::Utils - Supporting functions for MySQL:Diff =head1 SYNOPSIS use MySQL::Diff::Utils qw(debug_level debug); =head1 DESCRIPTION Currently contains the debug message handling routines. =cut use warnings; use strict; our $VERSION = '0.43'; # ------------------------------------------------------------------------------ # Libraries use IO::File; # ------------------------------------------------------------------------------ # Export Components use base qw(Exporter); our @EXPORT_OK = qw(debug_file debug_level debug); # ------------------------------------------------------------------------------ =head1 FUNCTIONS =head2 Public Functions Fuller documentation will appear here in time :) =over 4 =item * debug_file( $file ) Accessor to set/get the current debug log file. =item * debug_level( $level ) Accessor to set/get the current debug level for messages. Current levels range from 1 to 4, with 1 being very brief processing messages, 2 providing high level process flow messages, 3 providing low level process flow messages and 4 providing data dumps, etc where appropriate. =item * debug Writes to debug log file (if specified) and STDERR the given message, provided is equal to or lower than the current debug level. =back =cut { my $debug_file; my $debug_level = 0; sub debug_file { my ($new_debug_file) = @_; $debug_file = $new_debug_file if defined $new_debug_file; return $debug_file; } sub debug_level { my ($new_debug_level) = @_; $debug_level = $new_debug_level if defined $new_debug_level; return $debug_level; } sub debug { my $level = shift; return unless($debug_level >= $level && @_); if($debug_file) { if(my $fh = IO::File->new($debug_file, 'a+')) { print $fh @_,"\n"; $fh->close; return; } } print STDERR @_,"\n"; } } 1; __END__ =head1 COPYRIGHT AND LICENSE Copyright (c) 2000-2011 Adam Spiers. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L =head1 AUTHOR Adam Spiers =cut mysqldiff-0.43/t/000075500000000000000000000000001165625235700137135ustar00rootroot00000000000000mysqldiff-0.43/t/01use.t000064400000000000000000000003071165625235700150350ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use Test::More tests => 4; BEGIN { use_ok( 'MySQL::Diff' ); use_ok( 'MySQL::Diff::Database' ); use_ok( 'MySQL::Diff::Table' ); use_ok( 'MySQL::Diff::Utils' ); } mysqldiff-0.43/t/90podtest.t000064400000000000000000000004141165625235700157320ustar00rootroot00000000000000use Test::More; # Skip if doing a regular install plan skip_all => "Author tests not required for installation" unless ( $ENV{AUTOMATED_TESTING} ); eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); mysqldiff-0.43/t/91podcover.t000064400000000000000000000004531165625235700160750ustar00rootroot00000000000000use Test::More; # Skip if doing a regular install plan skip_all => "Author tests not required for installation" unless ( $ENV{AUTOMATED_TESTING} ); eval "use Test::Pod::Coverage 0.08"; plan skip_all => "Test::Pod::Coverage 0.08 required for testing POD coverage" if $@; all_pod_coverage_ok(); mysqldiff-0.43/t/94metatest.t000064400000000000000000000011661165625235700161070ustar00rootroot00000000000000use Test::More; use MySQL::Diff; # Skip if doing a regular install plan skip_all => "Author tests not required for installation" unless ( $ENV{AUTOMATED_TESTING} ); eval "use Test::CPAN::Meta 0.16"; plan skip_all => "Test::CPAN::Meta 0.16 required for testing META.yml" if $@; plan no_plan; my $yaml = meta_spec_ok(undef,undef,@_); is($yaml->{version},$MySQL::Diff::VERSION, 'META.yml distribution version matches'); if($yaml->{provides}) { for my $mod (keys %{$yaml->{provides}}) { is($yaml->{provides}{$mod}{version},$MySQL::Diff::VERSION, "META.yml entry [$mod] version matches"); } } mysqldiff-0.43/t/all.t000075500000000000000000000241551165625235700146620ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use Test::More; use MySQL::Diff; use MySQL::Diff::Database; my $TEST_USER = 'test'; my @VALID_ENGINES = qw(MyISAM InnoDB); my $VALID_ENGINES = join '|', @VALID_ENGINES; my %tables = ( foo1 => ' CREATE TABLE foo ( id INT(11) NOT NULL auto_increment, foreign_id INT(11) NOT NULL, PRIMARY KEY (id) ); ', foo2 => ' # here be a comment CREATE TABLE foo ( id INT(11) NOT NULL auto_increment, foreign_id INT(11) NOT NULL, # another random comment field BLOB, PRIMARY KEY (id) ); ', foo3 => ' CREATE TABLE foo ( id INT(11) NOT NULL auto_increment, foreign_id INT(11) NOT NULL, field TINYBLOB, PRIMARY KEY (id) ); ', foo4 => ' CREATE TABLE foo ( id INT(11) NOT NULL auto_increment, foreign_id INT(11) NOT NULL, field TINYBLOB, PRIMARY KEY (id, foreign_id) ); ', bar1 => ' CREATE TABLE bar ( id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, ctime DATETIME, utime DATETIME, name CHAR(16), age INT ); ', bar2 => ' CREATE TABLE bar ( id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, ctime DATETIME, utime DATETIME, # FOO! name CHAR(16), age INT, UNIQUE (name, age) ); ', bar3 => ' CREATE TABLE bar ( id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, ctime DATETIME, utime DATETIME, name CHAR(16), age INT, UNIQUE (id, name, age) ); ', baz1 => ' CREATE TABLE baz ( firstname CHAR(16), surname CHAR(16) ); ', baz2 => ' CREATE TABLE baz ( firstname CHAR(16), surname CHAR(16), UNIQUE (firstname, surname) ); ', baz3 => ' CREATE TABLE baz ( firstname CHAR(16), surname CHAR(16), KEY (firstname, surname) ); ', ); my %tests = ( 'add column' => [ {}, @tables{qw/foo1 foo2/}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo ADD COLUMN field blob; ', ], 'drop column' => [ {}, @tables{qw/foo2 foo1/}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo DROP COLUMN field; # was blob ', ], 'change column' => [ {}, @tables{qw/foo2 foo3/}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo CHANGE COLUMN field field tinyblob; # was blob ' ], 'no-old-defs' => [ { 'no-old-defs' => 1 }, @tables{qw/foo2 foo1/}, '## mysqldiff ## ## Run on ## Options: no-old-defs ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo DROP COLUMN field; ', ], 'add table' => [ { }, $tables{foo1}, $tables{foo2} . $tables{bar1}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo ADD COLUMN field blob; CREATE TABLE bar ( id int(11) NOT NULL auto_increment, ctime datetime default NULL, utime datetime default NULL, name char(16) default NULL, age int(11) default NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; ', ], 'drop table' => [ { }, $tables{foo1} . $tables{bar1}, $tables{foo2}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 DROP TABLE bar; ALTER TABLE foo ADD COLUMN field blob; ', ], 'only-both' => [ { 'only-both' => 1 }, $tables{foo1} . $tables{bar1}, $tables{foo2}, '## mysqldiff ## ## Run on ## Options: only-both ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo ADD COLUMN field blob; ', ], 'keep-old-tables' => [ { 'keep-old-tables' => 1 }, $tables{foo1} . $tables{bar1}, $tables{foo2}, '## mysqldiff ## ## Run on ## Options: keep-old-tables ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo ADD COLUMN field blob; ', ], 'table-re' => [ { 'table-re' => 'ba' }, $tables{foo1} . $tables{bar1} . $tables{baz1}, $tables{foo2} . $tables{bar2} . $tables{baz2}, '## mysqldiff ## ## Run on ## Options: table-re=ba ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar ADD UNIQUE name (name,age); ALTER TABLE baz ADD UNIQUE firstname (firstname,surname); ', ], 'drop primary key with auto weirdness' => [ {}, $tables{foo3}, $tables{foo4}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo ADD INDEX (id); # auto columns must always be indexed ALTER TABLE foo DROP PRIMARY KEY; # was (id) ALTER TABLE foo ADD PRIMARY KEY (id,foreign_id); ALTER TABLE foo DROP INDEX id; ', ], 'drop additional primary key' => [ {}, $tables{foo4}, $tables{foo3}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE foo ADD INDEX (id); # auto columns must always be indexed ALTER TABLE foo DROP PRIMARY KEY; # was (id,foreign_id) ALTER TABLE foo ADD PRIMARY KEY (id); ALTER TABLE foo DROP INDEX id; ', ], 'unique changes' => [ {}, $tables{bar1}, $tables{bar2}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar ADD UNIQUE name (name,age); ', ], 'drop index' => [ {}, $tables{bar2}, $tables{bar1}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar DROP INDEX name; # was UNIQUE (name,age) ', ], 'alter indices' => [ {}, $tables{bar2}, $tables{bar3}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar DROP INDEX name; # was UNIQUE (name,age) ALTER TABLE bar ADD UNIQUE id (id,name,age); ', ], 'alter indices 2' => [ {}, $tables{bar3}, $tables{bar2}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar DROP INDEX id; # was UNIQUE (id,name,age) ALTER TABLE bar ADD UNIQUE name (name,age); ', ], 'add unique index' => [ {}, $tables{bar1}, $tables{bar3}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar ADD UNIQUE id (id,name,age); ', ], 'drop unique index' => [ {}, $tables{bar3}, $tables{bar1}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE bar DROP INDEX id; # was UNIQUE (id,name,age) ', ], 'alter unique index' => [ {}, $tables{baz2}, $tables{baz3}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE baz DROP INDEX firstname; # was UNIQUE (firstname,surname) ALTER TABLE baz ADD INDEX firstname (firstname,surname); ', ], 'alter unique index 2' => [ {}, $tables{baz3}, $tables{baz2}, '## mysqldiff ## ## Run on ## ## --- file: tmp.db1 ## +++ file: tmp.db2 ALTER TABLE baz DROP INDEX firstname; # was INDEX (firstname,surname) ALTER TABLE baz ADD UNIQUE firstname (firstname,surname); ', ], ); my $BAIL = check_setup(); plan skip_all => $BAIL if($BAIL); my $total = scalar(keys %tests) * 5; plan tests => $total; use Data::Dumper; my @tests = (keys %tests); #keys %tests { my %debug = ( debug_file => 'debug.log', debug => 9 ); unlink $debug{debug_file}; for my $test (@tests) { note( "Testing $test\n" ); my ($opts, $db1_defs, $db2_defs, $expected) = @{$tests{$test}}; note("test=".Dumper($tests{$test})); my $diff = MySQL::Diff->new(%$opts, %debug); isa_ok($diff,'MySQL::Diff'); my $db1 = get_db($db1_defs, 1); my $db2 = get_db($db2_defs, 2); my $d1 = $diff->register_db($db1, 1); my $d2 = $diff->register_db($db2, 2); note("d1=" . Dumper($d1)); note("d2=" . Dumper($d2)); isa_ok($d1, 'MySQL::Diff::Database'); isa_ok($d2, 'MySQL::Diff::Database'); my $diffs = $diff->diff(); $diffs =~ s/^## mysqldiff [\d.]+/## mysqldiff /m; $diffs =~ s/^## Run on .*/## Run on /m; $diffs =~ s{/\*!40\d{3} .*? \*/;\n*}{}m; $diffs =~ s/ *$//gm; for ($diffs, $expected) { s/ default\b/ DEFAULT/gi; s/PRIMARY KEY +\(/PRIMARY KEY (/g; s/auto_increment/AUTO_INCREMENT/gi; } my $engine = 'InnoDB'; my $ENGINE_RE = qr/ENGINE=($VALID_ENGINES)/; if ($diffs =~ $ENGINE_RE) { $engine = $1; $expected =~ s/$ENGINE_RE/ENGINE=$engine/g; } note("diffs = " . Dumper($diffs)); note("expected = " . Dumper($expected)); is_deeply($diffs, $expected, ".. expected differences for $test"); # Now test that $diffs correctly patches $db1_defs to $db2_defs. my $patched = get_db($db1_defs . "\n" . $diffs, 1); $diff->register_db($patched, 1); is_deeply($diff->diff(), '', ".. patched differences for $test"); } } sub get_db { my ($defs, $num) = @_; note("defs=$defs"); my $file = "tmp.db$num"; open(TMP, ">$file") or die "open: $!"; print TMP $defs; close(TMP); my $db = MySQL::Diff::Database->new(file => $file, auth => { user => $TEST_USER }); unlink $file; return $db; } sub check_setup { my $failure_string = "Cannot proceed with tests without "; _output_matches("mysql --help", qr/--password/) or return $failure_string . 'a MySQL client'; _output_matches("mysqldump --help", qr/--password/) or return $failure_string . 'mysqldump'; _output_matches("echo status | mysql -u $TEST_USER 2>&1", qr/Connection id:/) or return $failure_string . 'a valid connection'; return ''; } sub _output_matches { my ($cmd, $re) = @_; my ($exit, $out) = _run($cmd); my $issue; if (defined $exit) { if ($exit == 0) { $issue = "Output from '$cmd' didn't match /$re/:\n$out" if $out !~ $re; } else { $issue = "'$cmd' exited with status code $exit"; } } else { $issue = "Failed to execute '$cmd'"; } if ($issue) { warn $issue, "\n"; return 0; } return 1; } sub _run { my ($cmd) = @_; unless (open(CMD, "$cmd|")) { return (undef, "Failed to execute '$cmd': $!\n"); } my $out = join '', ; close(CMD); return ($?, $out); }