Permalink
Browse files

inital release

  • Loading branch information...
0 parents commit 771e0dda5b7b15d01639790637b74b90d79d4fa6 mitch committed Nov 10, 2009
@@ -0,0 +1,7 @@
+build/
+dist/
+docs/
+*egg-info
+*.pyc
+.* # Ignore all dotfiles...
+!.gitignore # except for .gitignore
@@ -0,0 +1,2 @@
+== 0.9.0 / 2009-11-10
+ * Initial Public Release
13 LICENSE
@@ -0,0 +1,13 @@
+Copyright 2009 Mitch Matuson
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
83 README
@@ -0,0 +1,83 @@
+Schema Sync v0.9.0
++++++++++++++++++++
+a MySQL schema synchronization utility
+
+SYNOPSIS
+========
+schemasync [options] <source> <target>
+
+# source/target format: mysql://user:pass@host:port/database
+# output format: <database>[_<tag>].DDMMYYYY.(patch|revert)[_<version>].sql
+
+
+DESCRIPTION
+===========
+Schema Sync will generate the SQL necessary to migrate the schema of a source database to a target database (patch script), as well as a the SQL necessary to undo the changes after you apply them (revert script).
+
+* Schema Sync does not alter your database. It only generates the .sql files containing the differences. You must apply the changes.
+* Schema Sync does not yet recognize Tables or Columns that have been renamed. A rename will result in the old table or column being dropped and the new one added.
+* All ADD|MODIFY COLUMN statements have the AFTER (or FIRST) SQL syntax even if no move is required.
+* COMMENTS and AUTO_INCREMENT values are not by synced by default. See help (-h) for details.
+
+
+OPTIONS
+=================
+-h, --help show this help message and exit
+-V, --version show version and exit.
+-r, --revision increment the migration script version number
+ if a file with the same name already exists.
+-a, --sync-auto-inc sync the AUTO_INCREMENT value for each table.
+-c, --sync-comments sync the COMMENT field for all tables AND columns
+--tag=TAG tag the migration scripts as <database>_<tag>.
+ Valid characters include [A-Za-z0-9-_]
+--output-directory=OUTPUT_DIRECTORY
+ directory to write the migration scrips.
+ The default is current working directory.
+ Must use absolute path if provided.
+--log-directory=LOG_DIRECTORY
+ set the directory to write the log to.
+ Must use absolute path if provided.
+ Default is output directory.
+ Log filename is schemasync.log
+
+
+Download and Install
+====================
+
+Prerequisites
+-------------
+* To run Schema Sync, you need to have:
+ - Python 2.4, 2.5, or 2.6
+ - MySQL <http://www.mysql.com/>, version 5.0 or higher
+ - MySQLdb <http://sourceforge.net/projects/mysql-python>, version 1.2.1p2 or higher
+ - SchemaObject <http://matuson.com/code/schemaobject> 0.5.1 or higher (auto installed via easy_install schemasync)
+* To run the test suite, you need to install a copy of the Sakila Database <http://dev.mysql.com/doc/sakila/en/sakila.html>, version 0.8
+
+Installation
+------------
+Download http://matuson.com/code/schemasync-0.9.tar.gz
+tar xvzf schemasync-0.9.tar.gz
+cd schemasync-0.9
+sudo python setup.py install
+
+Installing via easy_install
+---------------------------
+ sudo apt-get install python-mysqldb
+ sudo easy_install schemaobject==0.5.1 schemasync
+
+Installing the latest development version
+-----------------------------------------
+ git clone git://github.com/mmatuson/SchemaSync.git
+ cd schemasync
+ sudo python setup.py install
+
+
+Status & License
+================
+Schema Sync is under active development and released under the Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>.
+
+You can obtain a copy of the latest source code from the Git repository <http://github.com/mmatuson/SchemaSync>, or fork it on Github <http://www.github.com>.
+
+You can report bugs via the Schema Sync Issues page <http://github.com/mmatuson/SchemaSync/issues>
+
+Comments, questions, and feature requests can be sent to code at matuson com
No changes.
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+
+__author__ = "Mitch Matuson"
+__copyright__ = "Copyright 2009 Mitch Matuson"
+__version__ = "0.9"
+__license__ = "Apache 2.0"
+
+import re
+import sys
+import os
+import logging
+import datetime
+import optparse
+import syncdb
+import utils
+import warnings
+
+# supress MySQLdb DeprecationWarning in Python 2.6
+warnings.simplefilter("ignore", DeprecationWarning)
+
+try:
+ import MySQLdb
+except ImportError:
+ print "Error: Missing Required Dependency MySQLdb."
+ sys.exit(1)
+
+try:
+ import schemaobject
+except ImportError:
+ print "Error: Missing Required Dependency SchemaObject"
+ sys.exit(1)
+
+
+APPLICATION_VERSION = __version__
+APPLICATION_NAME = "Schema Sync"
+LOG_FILENAME = "schemasync.log"
+DATE_FORMAT = "%Y%m%d"
+TPL_DATE_FORMAT = "%a, %b %d, %Y"
+PATCH_TPL = """--
+-- Schema Sync %(app_version)s %(type)s
+-- Created: %(created)s
+-- Server Version: %(server_version)s
+-- Apply To: %(target_host)s/%(target_database)s
+--
+
+%(data)s"""
+
+
+def parse_cmd_line(fn):
+ """Parse the command line options and pass them to the application"""
+
+ def processor(*args, **kwargs):
+ usage = """
+ %prog [options] <source> <target>
+ source/target format: mysql://user:pass@host:port/database"""
+ description = """
+ A MySQL Schema Synchronization Utility
+ """
+ parser = optparse.OptionParser(usage=usage,
+ description=description)
+
+ parser.add_option("-V", "--version",
+ action="store_true",
+ dest="show_version",
+ default=False,
+ help=("show version and exit."))
+
+ parser.add_option("-r", "--revision",
+ action="store_true",
+ dest="version_filename",
+ default=False,
+ help=("increment the migration script version number "
+ "if a file with the same name already exists."))
+
+ parser.add_option("-a", "--sync-auto-inc",
+ dest="sync_auto_inc",
+ action="store_true",
+ default=False,
+ help="sync the AUTO_INCREMENT value for each table.")
+
+ parser.add_option("-c", "--sync-comments",
+ dest="sync_comments",
+ action="store_true",
+ default=False,
+ help=("sync the COMMENT field for all "
+ "tables AND columns"))
+
+ parser.add_option("--tag",
+ dest="tag",
+ help=("tag the migration scripts as <database>_<tag>."
+ " Valid characters include [A-Za-z0-9-_]"))
+
+ parser.add_option("--output-directory",
+ dest="output_directory",
+ default=os.getcwd(),
+ help=("directory to write the migration scrips. "
+ "The default is current working directory. "
+ "Must use absolute path if provided."))
+
+ parser.add_option("--log-directory",
+ dest="log_directory",
+ help=("set the directory to write the log to. "
+ "Must use absolute path if provided. "
+ "Default is output directory. "
+ "Log filename is schemasync.log"))
+
+ options, args = parser.parse_args(sys.argv[1:])
+
+
+ if options.show_version:
+ print APPLICATION_NAME, __version__
+ return 0
+
+ if (not args) or (len(args) != 2):
+ parser.print_help()
+ return 0
+
+ return fn(*args, **dict(version_filename=options.version_filename,
+ output_directory=options.output_directory,
+ log_directory=options.log_directory,
+ tag=options.tag,
+ sync_auto_inc=options.sync_auto_inc,
+ sync_comments=options.sync_comments))
+ return processor
+
+
+def app(sourcedb='', targetdb='', version_filename=False,
+ output_directory=None, log_directory=None,
+ tag=None, sync_auto_inc=False, sync_comments=False):
+ """Main Application"""
+
+ options = locals()
+
+ if not os.path.isabs(output_directory):
+ print "Error: Output directory must be an absolute path. Quiting."
+ return 1
+
+ if not os.path.isdir(output_directory):
+ print "Error: Output directory does not exist. Quiting."
+ return 1
+
+ if not log_directory or not os.path.isdir(log_directory):
+ if log_directory:
+ print "Log directory does not exist, writing log to %s" % output_directory
+ log_directory = output_directory
+
+ logging.basicConfig(filename=os.path.join(log_directory, LOG_FILENAME),
+ level=logging.INFO,
+ format= '[%(levelname)s %(asctime)s] %(message)s')
+
+ console = logging.StreamHandler()
+ console.setLevel(logging.DEBUG)
+ logging.getLogger('').addHandler(console)
+
+ if not sourcedb:
+ logging.error("Source database URL not provided. Exiting.")
+ return 1
+
+ source_info = schemaobject.connection.parse_database_url(sourcedb)
+ if not source_info:
+ logging.error("Invalid source database URL format. Exiting.")
+ return 1
+
+ if not source_info['protocol'] == 'mysql':
+ logging.error("Source database must be MySQL. Exiting.")
+ return 1
+
+ if 'db' not in source_info:
+ logging.error("Source database name not provided. Exiting.")
+ return 1
+
+ if not targetdb:
+ logging.error("Target database URL not provided. Exiting.")
+ return 1
+
+ target_info = schemaobject.connection.parse_database_url(targetdb)
+ if not target_info:
+ logging.error("Invalid target database URL format. Exiting.")
+ return 1
+
+ if not target_info['protocol'] == 'mysql':
+ logging.error("Target database must be MySQL. Exiting.")
+ return 1
+
+ if 'db' not in target_info:
+ logging.error("Target database name not provided. Exiting.")
+ return 1
+
+ source_obj = schemaobject.SchemaObject(sourcedb)
+ target_obj = schemaobject.SchemaObject(targetdb)
+
+ if source_obj.version < '5.0.0':
+ logging.error("%s requires MySQL version 5.0+ (source is v%s)"
+ % (APPLICATION_NAME, source_obj.version))
+ return 1
+
+ if target_obj.version < '5.0.0':
+ logging.error("%s requires MySQL version 5.0+ (target is v%s)"
+ % (APPLICATION_NAME, target_obj.version))
+ return 1
+
+ # data transformation filters
+ filters = (lambda d: utils.REGEX_MULTI_SPACE.sub(' ', d),
+ lambda d: utils.REGEX_DISTANT_SEMICOLIN.sub(';', d))
+
+ # Information about this run, used in the patch/revert templates
+ ctx = dict(app_version=APPLICATION_VERSION,
+ server_version=target_obj.version,
+ target_host=target_obj.host,
+ target_database=target_obj.selected.name,
+ created=datetime.datetime.now().strftime(TPL_DATE_FORMAT))
+
+ p_fname, r_fname = utils.create_pnames(target_obj.selected.name,
+ tag=tag,
+ date_format=DATE_FORMAT)
+
+ ctx['type'] = "Patch Script"
+ pBuffer = utils.PatchBuffer(name=os.path.join(output_directory, p_fname),
+ filters=filters, tpl=PATCH_TPL, ctx=ctx.copy(),
+ version_filename=version_filename)
+
+ ctx['type'] = "Revert Script"
+ rBuffer = utils.PatchBuffer(name=os.path.join(output_directory, r_fname),
+ filters=filters, tpl=PATCH_TPL, ctx=ctx.copy(),
+ version_filename=version_filename)
+
+ db_selected = False
+ for patch, revert in syncdb.sync_schema(source_obj.selected,
+ target_obj.selected, options):
+ if patch and revert:
+
+ if not db_selected:
+ pBuffer.write(target_obj.selected.select() + '\n')
+ rBuffer.write(target_obj.selected.select() + '\n')
+ db_selected = True
+
+ pBuffer.write(patch + '\n')
+ rBuffer.write(revert + '\n')
+
+ if not pBuffer.modified:
+ logging.info(("No migration scripts written."
+ " mysql://%s/%s and mysql://%s/%s were in sync.") %
+ (source_obj.host, source_obj.selected.name,
+ target_obj.host, target_obj.selected.name))
+ else:
+ try:
+ pBuffer.save()
+ rBuffer.save()
+ logging.info("Migration scripts created for mysql://%s/%s\n"
+ "Patch Script: %s\nRevert Script: %s"
+ % (target_obj.host, target_obj.selected.name,
+ pBuffer.name, rBuffer.name))
+ except OSError, e:
+ pBuffer.delete()
+ rBuffer.delete()
+ logging.error("Failed writing migration scripts. %s" % e)
+ return 1
+
+ return 0
+
+
+def main():
+ try:
+ sys.exit(parse_cmd_line(app)())
+ except schemaobject.connection.DatabaseError, e:
+ logging.error("MySQL Error %d: %s" % (e.args[0], e.args[1]))
+ sys.exit(1)
+ except KeyboardInterrupt:
+ print "Sync Interrupted, Exiting."
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
Oops, something went wrong.

0 comments on commit 771e0dd

Please sign in to comment.