diff --git a/commands.py b/commands.py index 0734dda..c5c0511 100644 --- a/commands.py +++ b/commands.py @@ -1,452 +1,498 @@ +import sys +import inspect +import os import glob import re +import subprocess + +try: + from play.utils import * +except: + pass + +MODULE = 'migrate' + +COMMANDS = ['migrate','migrate:help','migrate:init','migrate:up','migrate:version','migrate:drop-rebuild','migrate:create','migrate:drop'] + +app = None # Migrate - database migration and creation +def getFormatString(): + return app.readConf('migrate.module.file.format').replace("[[eq]]","=") + def getDbArg(): - grab_next = False - for arg in sys.argv: - if arg.strip() == "--db": - grab_next = True - elif arg.startswith("--db=") or grab_next == True: - spec_db = arg.replace("--db","") - print "~ Processing specified database: " + spec_db - return spec_db + grab_next = False + for arg in sys.argv: + if arg.strip() == "--db": + grab_next = True + elif arg.startswith("--db=") or grab_next == True: + spec_db = arg.replace("--db","") + print "~ Processing specified database: " + spec_db + return spec_db - return None - + return None + # ~~~~~~~~~~~~~~~~~~~~~~ getUpToVersion() is to look up the desired migration version number, to which we'd like to migrate def getUpToVersion(): - grab_next = False - for arg in sys.argv: - if arg.strip() == "--to": - grab_next = True - elif arg.startswith("--to=") or grab_next == True: - try: - to_version = int(arg.replace("--to=","")) - print "~ Migrating to version: %(tv)s" % {'tv': to_version} - return to_version - except TypeError: - print "~ ERROR: unable to parse --to argument: '%(ta)s'" % { 'ta': arg } - return None + grab_next = False + for arg in sys.argv: + if arg.strip() == "--to": + grab_next = True + elif arg.startswith("--to=") or grab_next == True: + try: + to_version = int(arg.replace("--to=","")) + print "~ Migrating to version: %(tv)s" % {'tv': to_version} + return to_version + except TypeError: + print "~ ERROR: unable to parse --to argument: '%(ta)s'" % { 'ta': arg } + return None - return None + return None # ~~~~~~~~~~~~~~~~~~~~~~ getVersion(dbname) is to look up the version number of the database def getVersion(dbname): - [tmp_path,f] = createTempFile('migrate.module/check_version.sql') - f.write("select %(version)s, %(status)s from patchlevel" %{ 'version':"version", 'status': "status" }) - f.close() - - # The format string for running commands through a file - db_format_string = readConf('migrate.module.file.format') - command_strings = getCommandStrings() - command_strings['filename'] = tmp_path - command_strings['dbname'] = dbname - db_cmd = db_format_string % command_strings - - [code, response] = runDBCommand(db_cmd) - if code <> 0: - print "Failure " + response - sys.exit(-1) + [tmp_path,f] = createTempFile('migrate.module/check_version.sql') + f.write("select %(version)s, %(status)s from patchlevel" %{ 'version':"version", 'status': "status" }) + f.close() + + # The format string for running commands through a file + db_format_string = getFormatString() + command_strings = getCommandStrings() + command_strings['filename'] = tmp_path + command_strings['dbname'] = dbname + db_cmd = db_format_string % command_strings + + [code, response] = runDBCommand(db_cmd) + if code <> 0: + print "Failure " + response + sys.exit(-1) - parts = response.split() - return [parts[0]," ".join(parts[1:])] - + parts = response.split() + return [parts[0]," ".join(parts[1:])] + # ~~~~~~~~~~~~~~~~~~~~~~ updateVersionTo(dbname,version) updates the version number in the passed database def updateVersionTo(dbname,version): - [tmp_path,f] = createTempFile('migrate.module/update_version.sql') - f.write("update patchlevel set version = %(version)s, status = '%(status)s'" %{ 'version':version, 'status': "Successful" }) - f.close() - - # The format string for running commands through a file - db_format_string = readConf('migrate.module.file.format') - command_strings = getCommandStrings() - command_strings['filename'] = tmp_path - command_strings['dbname'] = dbname - db_cmd = db_format_string % command_strings - - [code, response] = runDBCommand(db_cmd) - if code <> 0: - print "~ ERROR updating version number: " - print " " + response - sys.exit(-1) - + [tmp_path,f] = createTempFile('migrate.module/update_version.sql') + f.write("update patchlevel set version = %(version)s, status = '%(status)s'" %{ 'version':version, 'status': "Successful" }) + f.close() + + # The format string for running commands through a file + db_format_string = getFormatString() + command_strings = getCommandStrings() + command_strings['filename'] = tmp_path + command_strings['dbname'] = dbname + db_cmd = db_format_string % command_strings + + [code, response] = runDBCommand(db_cmd) + if code <> 0: + print "~ ERROR updating version number: " + print " " + response + sys.exit(-1) + # ~~~~~~~~~~~~~~~~~~~~~~ updateStatusTo(dbname,status) updates the status in the passed database def updateStatusTo(dbname,status): - [tmp_path,f] = createTempFile('migrate.module/update_status.sql') - f.write("update patchlevel set status = '%(status)s'" %{'status': status }) - f.close() - - # The format string for running commands through a file - db_format_string = readConf('migrate.module.file.format') - command_strings = getCommandStrings() - command_strings['filename'] = tmp_path - command_strings['dbname'] = dbname - db_cmd = db_format_string % command_strings - - [code, response] = runDBCommand(db_cmd) - if code <> 0: - print "~ ERROR updating status: " - print "~ " + response - sys.exit(-1) - - + [tmp_path,f] = createTempFile('migrate.module/update_status.sql') + f.write("update patchlevel set status = '%(status)s'" %{'status': status }) + f.close() + + # The format string for running commands through a file + db_format_string = getFormatString() + command_strings = getCommandStrings() + command_strings['filename'] = tmp_path + command_strings['dbname'] = dbname + db_cmd = db_format_string % command_strings + + [code, response] = runDBCommand(db_cmd) + if code <> 0: + print "~ ERROR updating status: " + print "~ " + response + sys.exit(-1) + + # Constructs a temporary file for use in running SQL commands def createTempFile(relative_path): - tmp_path = os.path.normpath(os.path.join(application_path, 'tmp/' + relative_path)) - pathdir = os.path.dirname(tmp_path) - if not os.path.exists(pathdir): - os.makedirs(pathdir) - return [tmp_path, open(tmp_path,'w')] + tmp_path = os.path.normpath(os.path.join(app.path, 'tmp/' + relative_path)) + pathdir = os.path.dirname(tmp_path) + if not os.path.exists(pathdir): + os.makedirs(pathdir) + return [tmp_path, open(tmp_path,'w')] - + # ~~~~~~~~~~~~~~~~~~~~~~ getCommandStrings() retrieves the command parameters for running a DB command from the command line def getCommandStrings(): - db_create_user = readConf('migrate.module.username') - db_create_pwd = readConf('migrate.module.password') - db_port = readConf('migrate.module.port') - db_host = readConf('migrate.module.host') - return {'username': db_create_user, 'password': db_create_pwd, \ - 'host': db_host, 'port': db_port, 'filename': "", 'dbname': "" } + db_create_user = app.readConf('migrate.module.username') + db_create_pwd = app.readConf('migrate.module.password') + db_port = app.readConf('migrate.module.port') + db_host = app.readConf('migrate.module.host') + return {'username': db_create_user, 'password': db_create_pwd, \ + 'host': db_host, 'port': db_port, 'filename': "", 'dbname': "" } -# ~~~~~~~~~~~~~~~~~~~~~ Runs the specified command, returning the returncode and the text (if any) +# ~~~~~~~~~~~~~~~~~~~~~ Runs the specified command, returning the returncode and the text (if any) def runDBCommand(command): - returncode = None - line = "" - try: - create_process = subprocess.Popen(command, env=os.environ, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) - while True: - returncode = create_process.poll() - line += create_process.stdout.readline() - if returncode != None: - break; - - except OSError: - print "Could not execute the database create script: " - print " " + command - returncode = -1 - - return [returncode,line] - + returncode = None + line = "" + try: + create_process = subprocess.Popen(command, env=os.environ, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + while True: + returncode = create_process.poll() + line += create_process.stdout.readline() + if returncode != None: + break; + + except OSError: + print "Could not execute the database create script: " + print " " + command + returncode = -1 + + return [returncode,line] + # ~~~~~~~~~~~~~~~~~~~~~~ Retrieves the list of migration files for the specified database. The files live in the # ~~~~~~~~~~~~~~~~~~~~~~ {playapp}/db/migrate/{dbname} folder and follow a naming convention: {number}.{up|down}.{whatever}.sql def getMigrateFiles(dbname, exclude_before): - search_path = os.path.join(application_path, 'db/migrate/',dbname + '/*up*.sql') + search_path = os.path.join(app.path, 'db/migrate/',dbname + '/*up*.sql') - initial_list = glob.glob(search_path) - return_obj = {} - collisions = [] - # Filter the list to only the specified pattern - pat = re.compile('(\d+)\.(up|down).*\.sql\Z') - maxindex = 0 - for file in initial_list: - match = re.search(pat,file) - index = int(match.group(1)) - if index in return_obj: - collisions.append("" + return_obj[index] + " <==> " + file) - if match != None and index > exclude_before: - return_obj[index] = file - if match != None and index > maxindex: - maxindex = index - - # Check for collisions - if len(collisions) > 0: - print "~" - print "~ ======================================================================================================" - print "~ " - print "~ ERROR: Migrate collisions detected. Please resolve these, then try again" - print "~" - print "~ Collision list:" - for item in collisions: - print "~ " + item - print "~" - print "~" - sys.exit(-1) - - # Check for gaps - missed = [] - for idx in range((exclude_before + 1),maxindex): - if idx not in return_obj: - missed.append(idx) - - if len(missed) > 0: - print "~" - print "~ ======================================================================================================" - print "~ " - print "~ ERROR: Migrate file gaps detected. Please resolve these, then try again" - print "~" - print "~ Files at the following levels are missing:" - for idx in missed: - print "~ %s" % idx - print "~" - print "~" - sys.exit(-1) - - return [maxindex, return_obj] + initial_list = glob.glob(search_path) + return_obj = {} + collisions = [] + # Filter the list to only the specified pattern + pat = re.compile('(\d+)\.(up|down).*\.sql\Z') + maxindex = 0 + for file in initial_list: + match = re.search(pat,file) + index = int(match.group(1)) + if index in return_obj: + collisions.append("" + return_obj[index] + " <==> " + file) + if match != None and index > exclude_before: + return_obj[index] = file + if match != None and index > maxindex: + maxindex = index + + # Check for collisions + if len(collisions) > 0: + print "~" + print "~ ======================================================================================================" + print "~ " + print "~ ERROR: Migrate collisions detected. Please resolve these, then try again" + print "~" + print "~ Collision list:" + for item in collisions: + print "~ " + item + print "~" + print "~" + sys.exit(-1) + + # Check for gaps + missed = [] + for idx in range((exclude_before + 1),maxindex): + if idx not in return_obj: + missed.append(idx) + + if len(missed) > 0: + print "~" + print "~ ======================================================================================================" + print "~ " + print "~ ERROR: Migrate file gaps detected. Please resolve these, then try again" + print "~" + print "~ Files at the following levels are missing:" + for idx in missed: + print "~ %s" % idx + print "~" + print "~" + sys.exit(-1) + + return [maxindex, return_obj] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ extracts the database and its alias from the passed database name. def extractDatabaseAndAlias(db): - db = db.strip() - - # See if there's an alias. - match = re.search('(\w+)\[(\w+)]',db); - if match == None: - db_alias = db; - db_alias_name = 'None'; - else: - db_alias = match.group(2); - db_alias_name = db_alias; - db = match.group(1); - - return [db,db_alias,db_alias_name] - + db = db.strip() + + # See if there's an alias. + match = re.search('(\w+)\[(\w+)]',db); + if match == None: + db_alias = db; + db_alias_name = 'None'; + else: + db_alias = match.group(2); + db_alias_name = db_alias; + db = match.group(1); + + return [db,db_alias,db_alias_name] + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ interpolates the creation file with the passed database name. def interpolateDBFile(db, createpath): - [tmp_path,f] = createTempFile('migrate.module/temp_create_%(db)s.sql' % {'db': db}) - print "~ Creating temp file: %(tf)s" % {'tf':tmp_path} - for line in open(createpath).readlines(): - f.write(line.replace("${db}",db)) + [tmp_path,f] = createTempFile('migrate.module/temp_create_%(db)s.sql' % {'db': db}) + print "~ Creating temp file: %(tf)s" % {'tf':tmp_path} + for line in open(createpath).readlines(): + f.write(line.replace("${db}",db)) - f.close() - - return tmp_path - + f.close() + + return tmp_path + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Runs the creation script def runCreateScript(createpath, createname): - db_format_string = readConf('migrate.module.file.format') - db_commands = getCommandStrings() - db_commands['filename'] = createpath - db_commands['dbname'] = "" + db_format_string = getFormatString() + db_commands = getCommandStrings() + db_commands['filename'] = createpath + db_commands['dbname'] = "" - db_create_cmd = db_format_string %db_commands - - print "~ Running script %(cs)s..." % {'cs': createname} - - [code,response] = runDBCommand(db_create_cmd) - if code <> 0: - print "~ " + str(code) - print "~ ERROR: could not execute the database create script: " - print "~ " + db_create_cmd - print "~ " - print "~ Process response: " + response - print "~ " - print "~ Check your credentials and your script syntax and try again" - print "~ " - sys.exit(-1) + db_create_cmd = db_format_string %db_commands + + print "~ Running script %(cs)s..." % {'cs': createname} + + [code,response] = runDBCommand(db_create_cmd) + if code <> 0: + print "~ " + str(code) + print "~ ERROR: could not execute the database create script: " + print "~ " + db_create_cmd + print "~ " + print "~ Process response: " + response + print "~ " + print "~ Check your credentials and your script syntax and try again" + print "~ " + sys.exit(-1) # ~~~~~~~~~~~~~~~~~~~~~~ Runs the database creation script def create(): - try: - is_generic = False - - if application_path: - createpath = os.path.join(application_path, 'db/migrate/generic_create.sql') - if not os.path.exists(createpath): - createpath = os.path.join(application_path, 'db/migrate/create.sql') - if not os.path.exists(createpath): - print "~ " - print "~ Unable to find create script" - print "~ Please place your database creation script in db/migrate/create.sql or db/migrate/generic_create.sql" - print "~ " - sys.exit(-1) - else: - is_generic = True - else: - print "~ Unable to find create script" - sys.exit(-1) - - if is_generic: - print "~ Using generic create script, replacing parameter ${db} with each database name..." - print "~ " - db_list = readConf('migrate.module.dbs').split(',') - db_arg = getDbArg() - - for db in db_list: - # Extract the database name, trimming any whitespace. - [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) - - # Skip the database if a specified was given - if db_arg != None and db != db_arg: - continue - - print "~ Database: %(db)s" % {'db': db} - - # interpolate the generic file to contain the database name. - interpolated = interpolateDBFile(db, createpath) - # run the interpolated creation script - runCreateScript(interpolated,'generic_created.sql (%(db)s)' % {'db': db}) - else: - # Run the create script - runCreateScript(createpath, 'create.sql') - - except getopt.GetoptError, err: - print "~ %s" % str(err) - print "~ " - sys.exit(-1) - - print "~ " - print "~ Database creation script(s) completed." - print "~ " - - + try: + is_generic = False + + if app.path: + createpath = os.path.join(app.path, 'db/migrate/generic_create.sql') + if not os.path.exists(createpath): + createpath = os.path.join(app.path, 'db/migrate/create.sql') + if not os.path.exists(createpath): + print "~ " + print "~ Unable to find create script" + print "~ Please place your database creation script in db/migrate/create.sql or db/migrate/generic_create.sql" + print "~ " + sys.exit(-1) + else: + is_generic = True + else: + print "~ Unable to find create script" + sys.exit(-1) + + if is_generic: + print "~ Using generic create script, replacing parameter ${db} with each database name..." + print "~ " + db_list = app.readConf('migrate.module.dbs').split(',') + db_arg = getDbArg() + + for db in db_list: + # Extract the database name, trimming any whitespace. + [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) + + # Skip the database if a specified was given + if db_arg != None and db != db_arg: + continue + + print "~ Database: %(db)s" % {'db': db} + + # interpolate the generic file to contain the database name. + interpolated = interpolateDBFile(db, createpath) + # run the interpolated creation script + runCreateScript(interpolated,'generic_created.sql (%(db)s)' % {'db': db}) + else: + # Run the create script + runCreateScript(createpath, 'create.sql') + + except getopt.GetoptError, err: + print "~ %s" % str(err) + print "~ " + sys.exit(-1) + + print "~ " + print "~ Database creation script(s) completed." + print "~ " + + # ~~~~~~~~~~~~~~~~~~~~~~ Performs the up migration task def up(): - # The format string we'll use to run DB commands - db_format_string = readConf('migrate.module.file.format') - - # Find the databases to iterat - db_list = readConf('migrate.module.dbs').split(',') - - db_arg = getDbArg() - - to_version = getUpToVersion() - - print "~ Database migration:" - print "~ " - for db in db_list: - - # Extract the database name, trimming any whitespace. - [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) - - # Skip the database if a specified was given - if db_arg != None and db != db_arg: - continue - - print "~ Database: %(db)s (Alias:%(alias)s)" % {'db':db, 'alias': db_alias_name } - [version,status] = getVersion(db) - print "~ Version: %(version)s" % {'version': version} - print "~ Status: %(status)s" % {'status': status} - [maxindex, files_obj] = getMigrateFiles(db_alias,int(version)) - print "~ Max patch version: " + str(maxindex) - print "~ " - if maxindex <= int(version): - print "~ " + db + " is up to date." - print "~ " - print "~ " - continue - print "~ Migrating..." - command_strings = getCommandStrings() - if to_version == None or to_version > maxindex: - to_version = maxindex - for i in range(int(version) + 1, to_version + 1): - # Skip missed files - if files_obj[i] == None: - print "~ Patch " + str(i) + " is missing...skipped" - continue - - command_strings['filename'] = files_obj[i] - command_strings['dbname'] = db - db_cmd = db_format_string % command_strings - - [code, response] = runDBCommand(db_cmd) - if code <> 0: - print "~ Migration failed on patch " + str(i) + "!" - print "~ ERRROR message: " + response - updateStatusTo(db,response) - - sys.exit(-1) - print "~ " + str(i) + "..." - - updateVersionTo(db,i) - print "~ " - print "~ Migration completed successfully" - print "~ " - print "~ ------------------------------------" - print "~ " - + # The format string we'll use to run DB commands + db_format_string = getFormatString() + + # Find the databases to iterat + db_list = app.readConf('migrate.module.dbs').split(',') + + db_arg = getDbArg() + + to_version = getUpToVersion() + + print "~ Database migration:" + print "~ " + for db in db_list: + + # Extract the database name, trimming any whitespace. + [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) + + # Skip the database if a specified was given + if db_arg != None and db != db_arg: + continue + + print "~ Database: %(db)s (Alias:%(alias)s)" % {'db':db, 'alias': db_alias_name } + [version,status] = getVersion(db) + print "~ Version: %(version)s" % {'version': version} + print "~ Status: %(status)s" % {'status': status} + [maxindex, files_obj] = getMigrateFiles(db_alias,int(version)) + print "~ Max patch version: " + str(maxindex) + print "~ " + if maxindex <= int(version): + print "~ " + db + " is up to date." + print "~ " + print "~ " + continue + print "~ Migrating..." + command_strings = getCommandStrings() + if to_version == None or to_version > maxindex: + to_version = maxindex + for i in range(int(version) + 1, to_version + 1): + # Skip missed files + if files_obj[i] == None: + print "~ Patch " + str(i) + " is missing...skipped" + continue + + command_strings['filename'] = files_obj[i] + command_strings['dbname'] = db + db_cmd = db_format_string % command_strings + + [code, response] = runDBCommand(db_cmd) + if code <> 0: + print "~ Migration failed on patch " + str(i) + "!" + print "~ ERRROR message: " + response + updateStatusTo(db,response) + + sys.exit(-1) + print "~ " + str(i) + "..." + + updateVersionTo(db,i) + print "~ " + print "~ Migration completed successfully" + print "~ " + print "~ ------------------------------------" + print "~ " + # ~~~~~~~~~~~~~~~~~~~~~~ Drops all databases def dropAll(): - db_list = readConf('migrate.module.dbs').split(',') - db_arg = getDbArg() - - print "~ " - print "~ Dropping databases..." - for db in db_list: - - [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) - - # Skip the database if a specified was given - if db_arg != None and db != db_arg: - continue - - print "~ drop %(db)s" % {'db':db} - [tmp_path,f] = createTempFile('migrate.module/drop_db.sql') - f.write("drop database if exists %(db)s;" %{ 'db':db }) - f.close() - - # The format string for running commands through a file - db_format_string = readConf('migrate.module.file.format') - command_strings = getCommandStrings() - command_strings['filename'] = tmp_path - command_strings['dbname'] = "" - db_cmd = db_format_string % command_strings - - [code, response] = runDBCommand(db_cmd) - if code <> 0: - print "Failure " + response - sys.exit(-1) - print "~ " - print "~ Database drop completed" - print "~ " + db_list = app.readConf('migrate.module.dbs').split(',') + db_arg = getDbArg() + + print "~ " + print "~ Dropping databases..." + for db in db_list: + + [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) + + # Skip the database if a specified was given + if db_arg != None and db != db_arg: + continue + + print "~ drop %(db)s" % {'db':db} + [tmp_path,f] = createTempFile('migrate.module/drop_db.sql') + f.write("drop database if exists %(db)s;" %{ 'db':db }) + f.close() + + # The format string for running commands through a file + db_format_string = getFormatString() + command_strings = getCommandStrings() + command_strings['filename'] = tmp_path + command_strings['dbname'] = "" + db_cmd = db_format_string % command_strings + + [code, response] = runDBCommand(db_cmd) + if code <> 0: + print "Failure " + response + sys.exit(-1) + print "~ " + print "~ Database drop completed" + print "~ " -# ~~~~~~~~~~~~~~~~~~~~~~ [migrate:create] Create the initial database -if play_command == 'migrate:create': - create() - sys.exit(0) - -# ~~~~~~~~~~~~~~~~~~~~~~ [migrate:up] Migrate the database from it's current version to another version -if play_command == 'migrate:up': - up() - sys.exit(0) +def execute(**kargs): + global app + play_command = kargs.get("command") + app = kargs.get("app") + + # ~~~~~~~~~~~~~~~~~~~~~~ [migrate:create] Create the initial database + if play_command == 'migrate:create': + create() + sys.exit(0) + + # ~~~~~~~~~~~~~~~~~~~~~~ [migrate:up] Migrate the database from it's current version to another version + if play_command == 'migrate:up': + up() + sys.exit(0) -# ~~~~~~~~~~~~~~~~~~~~~~ [migrate:version] Output the current version(s) of the datbase(s) -if play_command == 'migrate:version': - db_list = readConf('migrate.module.dbs').split(',') - db_arg = getDbArg() + # ~~~~~~~~~~~~~~~~~~~~~~ [migrate:version] Output the current version(s) of the datbase(s) + if play_command == 'migrate:version': + db_list = app.readConf('migrate.module.dbs').split(',') + db_arg = getDbArg() - print "~ Database version check:" - print "~ " - for db in db_list: - [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) - - # Skip the database if a specified was given - if db_arg != None and db != db_arg: - continue - - [version, status] = getVersion(db) - format = "%(dbname)-20s version %(version)s, status: %(status)s" % {'dbname':db, 'version':version, 'status': status} - print "~ " + format - - print "~ " - sys.exit(0) + print "~ Database version check:" + print "~ " + for db in db_list: + [db, db_alias, db_alias_name] = extractDatabaseAndAlias(db) + + # Skip the database if a specified was given + if db_arg != None and db != db_arg: + continue + + [version, status] = getVersion(db) + format = "%(dbname)-20s version %(version)s, status: %(status)s" % {'dbname':db, 'version':version, 'status': status} + print "~ " + format + + print "~ " + sys.exit(0) -# ~~~~~~~~~~~~~~~~~~~~~~ [migrate:init] Build the initial / example files for the migrate module -if play_command == 'migrate:init': - override('db/migrate/create.sql', 'db/migrate/create.sql') - override('db/migrate/db1/1.up.create_user.sql', 'db/migrate/db1/1.up.create_user.sql') - print "~ " - sys.exit(0) + # ~~~~~~~~~~~~~~~~~~~~~~ [migrate:init] Build the initial / example files for the migrate module + if play_command == 'migrate:init': + app.override('db/migrate/generic_create.sql', 'db/migrate/generic_create.sql') + app.override('db/migrate/db1/1.up.create_user.sql', 'db/migrate/db1/1.up.create_user.sql') + print "~ " + sys.exit(0) -# ~~~~~~~~~~~~~~~~~~~~~~ [migrate:init] Build the initial / example files for the migrate module -if play_command == 'migrate:drop-rebuild': - dropAll() - create() - up() - sys.exit(0) - -if play_command.startswith('migrate:'): - print "~ Database migration module " - print "~ " - print "~ Use: migrate:create to create your initial database" - print "~ migrate:up to migrate your database up" - print "~ migrate:version to read the current version of the database" - print "~ migrate:init to set up some initial database migration files" - print "~ migrate:drop-rebuild to drop and then rebuild all databases (use with caution!)" - print "~ " - print "~ Add --db={database name} to any command to only affect that database" - print "~ Add --to={version number} to any command to only migrate to the specified version number" - print "~ " - - sys.exit(0) \ No newline at end of file + # ~~~~~~~~~~~~~~~~~~~~~~ [migrate:drop-rebuild] drop then rebuild databases + if play_command == 'migrate:drop-rebuild': + dropAll() + create() + up() + sys.exit(0) + + # ~~~~~~~~~~~~~~~~~~~~~~ [migrate:drop] drop databases + if play_command == 'migrate:drop': + dropAll() + sys.exit(0) + + if play_command.startswith('migrate:') or play_command == 'migrate': + print "~ Database migration module " + print "~ " + print "~ Use: migrate:create to create your initial database" + print "~ migrate:up to migrate your database up" + print "~ migrate:version to read the current version of the database" + print "~ migrate:init to set up some initial database migration files" + print "~ migrate:drop-rebuild to drop and then rebuild all databases (use with caution!)" + print "~ migrate:help to show this message" + print "~ " + print "~ Add --db={database name} to any command to only affect that database" + print "~ Add --to={version number} to any command to only migrate to the specified version number" + + print "~ " + + sys.exit(0) + +class MockPlayApp: + override = None + readConf = None + path = None + +# 1.0 version code +try: + cmd_10 = play_command + app = MockPlayApp() + app.override = override + app.readConf = readConf + app.path = application_path + execute(command=play_command, app=app) +except NameError: + pass diff --git a/db/migrate/create.sql b/db/migrate/create.sql deleted file mode 100644 index c6c165a..0000000 --- a/db/migrate/create.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE DATABASE db1; - --- A migratable database has to have a 'patchlevel' table, which stores the version and the status of the last update. -CREATE TABLE db1.patchlevel (version int(10) unsigned NOT NULL, status varchar(255) default NULL, PRIMARY KEY (`version`)); -insert into db1.patchlevel (version, status) values (0,'Successful'); \ No newline at end of file diff --git a/db/migrate/db1/1.up.create_user.sql b/db/migrate/db1/1.up.create_user.sql index 2583778..e42c008 100644 --- a/db/migrate/db1/1.up.create_user.sql +++ b/db/migrate/db1/1.up.create_user.sql @@ -1,5 +1,5 @@ -- The user table -CREATE TABLE db1.user ( +CREATE TABLE user ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(255), `password` VARCHAR(255), diff --git a/db/migrate/generic_create.sql b/db/migrate/generic_create.sql new file mode 100644 index 0000000..9f008bb --- /dev/null +++ b/db/migrate/generic_create.sql @@ -0,0 +1,5 @@ +CREATE DATABASE ${db}; + +-- A migratable database has to have a 'patchlevel' table, which stores the version and the status of the last update. +CREATE TABLE ${db}.patchlevel (version int(10) unsigned NOT NULL, status varchar(255) default NULL, PRIMARY KEY (`version`)); +insert into ${db}.patchlevel (version, status) values (0,'Successful'); \ No newline at end of file diff --git a/documentation/manual/home.textile b/documentation/manual/home.textile index 92cc5b0..1f6d8db 100644 --- a/documentation/manual/home.textile +++ b/documentation/manual/home.textile @@ -46,17 +46,21 @@ In order to run database modifications, the module uses command line tools to ex The command line is different for each particular database, so the module uses a format string as a template for the command. The example configuration lines show the template for MySQL and PostgreSQL databases. -bc. # db.module.file.format is used to run commands directly from a file via the command-line +bc. # migrate.module.file.format is used to run commands directly from a file via the command-line # on a database. This format is populated with the appropriate fields in order to perform # creation and migration scripts on the database. # # The MySQL version: -# db.module.file.format=mysql -u%(username)s --password=%(password)s -h %(host)s -P%(port)s --skip-column-names %(dbname)s < %(filename)s +# migrate.module.file.format=mysql -u%(username)s --password=%(password)s -h %(host)s -P%(port)s --skip-column-names %(dbname)s < %(filename)s # The PostgreSQL version: -# db.module.file.format=psql -U %(username)s -h %(host)s -P%(port)s -t -d %(dbname)s -f %(filename)s +# migrate.module.file.format=psql -U %(username)s -h %(host)s -P%(port)s -t -d %(dbname)s -f %(filename)s Make sure that the command (i.e., 'mysql' or 'psql') is available on your system's path or the module will encounter errors. +'''NOTE:''' Play version 1.1 has issues parsing configuration properties with multiple '=' symbols in the line. As a result, the mysql file format will NOT work. I've coded a workaround into the migrate module, which replaces the string '[[eq]]' with an equals symbol. Therefore, in 1.1 the MySQL format string should read: + +bc. migrate.module.file.format=mysql -u%(username)s --password[[eq]]%(password)s -h %(host)s -P%(port)s --skip-column-names %(dbname)s < %(filename)s + h2. Writing patch files Migrate processes two types of files: a single **create** script and multiple **patch** scripts. The create script is named **create.sql** and is only run to generate your database(s) initially. The patch scripts are named according to this pattern: [version number].['up' or 'down'].[description].sql @@ -82,7 +86,7 @@ The **create.sql** file contains the instructions to create all of your database The patch scripts identify a version number for their contents as well as the direction of the patch. The **up** patches are used to bring the database version from its previous version up to the version of the patch (e.g., if the database's current version is 12, patch 13.up.xxx.sql will bring the database to version 13). The **down** patches remove the changes of the corresponding __up__ patch, bringing the database to the previous version. -NOTE: code to run 'down' patches is not implemented in this version of the migrate module! +NOTE: code to run 'down' patches is not implemented in this version of the migrate module! However, this feature has not proven very useful in practice, while migrating up is definitely vital. h2. Database Aliases - Multiple Databases with the Same Schema @@ -115,7 +119,7 @@ If the 'generic_create.sql' file is NOT present, the normal 'create.sql' file wi h2. Running the patch files -To run your database creation script, use the **play migrate:create** command. This just runs the __create.sql__ or interpolated _generic_create.sql_ script (depending on configuration). +To run your database creation script, use the **play migrate:create** command. This just runs the __create.sql__ or interpolated __generic_create.sql__ script (depending on configuration). To bring your database(s) to their most recent patch version(s), use the **play migrate:up** command. @@ -131,6 +135,4 @@ You can see your database(s) current version number(s) and status(es) with the * You can create a skeleton directory structure (with example files) with the **play migrate:init** command. -You can run any of the commands on just one database by adding '--db=[database name]' as an additional argument to the command. - -You can specify a database version to migrate to by adding 'to=[version number]' as an additional argument to the command. \ No newline at end of file +You can just drop all of your databases with the **play migrate:drop** command. \ No newline at end of file