def test_delete_rows(self): # I could also just create the database and add rows but I feel like doing this tables = self._get_tables() rows = { 'logs': ({ 'id': 2, 'message': 'sup', 'traceback': 'forever' }, ) } database = database_reader(mysqldb(db_structure(tables, rows))) database.read_rows('logs') rows_from = { 'logs': ({ 'id': 1, 'message': 'hey', 'traceback': 'never' }, { 'id': 2, 'message': 'sup', 'traceback': 'forever' }) } database_from = database_reader( mysqldb(db_structure(tables, rows_from))) database_from.read_rows('logs') operations = [ str(op) for op in row_mygration(database, database_from).operations ] self.assertEquals(1, len(operations)) self.assertEquals("DELETE FROM `logs` WHERE id=1;", operations[0])
def test_add_rows(self): # I could also just create the database and add rows but I feel like doing this tables = self._get_tables() rows = { 'logs': ({ 'id': 1, 'message': 'hey', 'traceback': 'never' }, { 'id': 2, 'message': 'sup', 'traceback': 'forever' }) } database = database_reader(mysqldb(db_structure(tables, rows))) database.read_rows('logs') operations = [str(op) for op in row_mygration(database).operations] self.assertEquals(2, len(operations)) self.assertEquals( "INSERT INTO `logs` (`id`, `message`, `traceback`) VALUES ('1', 'hey', 'never');", operations[0]) self.assertEquals( "INSERT INTO `logs` (`id`, `message`, `traceback`) VALUES ('2', 'sup', 'forever');", operations[1])
def test_read_rows(self): tables = self._get_tables() rows = { 'logs': ({ 'id': 1, 'message': 'hey', 'traceback': 'never' }, { 'id': 2, 'message': 'sup', 'traceback': 'forever' }) } database = database_reader(mysqldb(db_structure(tables, rows))) # quick double check self.assertTrue('logs' in database.tables) self.assertTrue('more_logs' in database.tables) database.read_rows('logs') self.assertTrue(database.tables['logs'].tracking_rows) rows = database.tables['logs'].rows self.assertEquals(1, rows[1]['id']) self.assertEquals('hey', rows[1]['message']) self.assertEquals('never', rows[1]['traceback']) self.assertEquals(2, rows[2]['id']) self.assertEquals('sup', rows[2]['message']) self.assertEquals('forever', rows[2]['traceback'])
def test_all(self): # I could also just create the database and add rows but I feel like doing this tables = self._get_tables() rows = { 'logs': ({ 'id': 2, 'message': 'sup', 'traceback': 'whatever' }, { 'id': 3, 'message': 'okay', 'traceback': 'always' }) } database = database_reader(mysqldb(db_structure(tables, rows))) database.read_rows('logs') rows_from = { 'logs': ({ 'id': 1, 'message': 'hey', 'traceback': 'never' }, { 'id': 2, 'message': 'sup', 'traceback': 'forever' }) } database_from = database_reader( mysqldb(db_structure(tables, rows_from))) database_from.read_rows('logs') operations = [ str(op) for op in row_mygration(database, database_from).operations ] # don't be picky about the order self.assertEquals(3, len(operations)) self.assertTrue( "INSERT INTO `logs` (`id`, `message`, `traceback`) VALUES ('3', 'okay', 'always');" in operations) self.assertTrue("DELETE FROM `logs` WHERE id=1;" in operations) self.assertTrue( "UPDATE `logs` SET `message`='sup', `traceback`='whatever' WHERE id=2;" in operations)
def build_commands(self): files_database = database_parser(self.config['files_directory']) # any errors or warnings? quit_early = False if files_database.errors and not self.options['force']: print('Errors found in *.sql files', file=sys.stderr) quit_early = True for error in files_database.errors: print(error, file=sys.stderr) # or 1215 errors? if files_database.errors_1215 and not self.options['force']: print('1215 errors encountered', sys.stderr) quit_early = True for error in files_database.errors_1215: print(error, file=sys.stderr) if quit_early: return [] # use the credentials to load up a database connection live_database = database_reader(mysqldb(self.credentials)) # we have to tell the live database to load records # for any tables we are tracking records for. # We use the "files" database as the "truth" because # just about any database can have records, but # that doesn't mean we want to track them. We only # track them if the file has rows. for table in files_database.tables.values(): if not table.tracking_rows: continue if not table.name in live_database.tables: continue live_database.read_rows(table) mygrate = mygration(files_database, live_database, False) ops = [] if mygrate.operations: ops.extend(mygrate.operations) rows = row_mygration(files_database, live_database) if rows.operations: ops.extend(rows.operations) if not ops: return [] return [ disable_checks(), *ops, enable_checks(), ]
def test_simple(self): tables = self._get_tables() mock_db = db_structure(tables, {}) database = database_reader(mysqldb(mock_db)) # our parser should have a table! self.assertTrue('logs' in database.tables) self.assertTrue('more_logs' in database.tables)
def test_modify_rows(self): """ Types can change depending on whether or not rows come out of files or the database. As a result, equality comparison has to ignore type differences """ # I could also just create the database and add rows but I feel like doing this tables = self._get_tables() rows = { 'logs': ({ 'id': 1, 'account_id': 1, 'message': 'hey' }, { 'id': 2, 'account_id': 1, 'message': 'sup' }) } database = database_reader(mysqldb(db_structure(tables, rows))) database.read_rows('logs') rows_from = { 'logs': ({ 'id': 1, 'account_id': '1', 'message': 'hey' }, { 'id': 2, 'account_id': '1', 'message': 'sup' }) } database_from = database_reader( mysqldb(db_structure(tables, rows_from))) database_from.read_rows('logs') operations = [ str(op) for op in row_mygration(database, database_from).operations ] self.assertEquals(0, len(operations))
def test_diff_with_null( self ): """ NULL should be allowed and should result in a MySQL NULL in the database The system was turning NULL into a literal 'NULL'. Internally, NULL is handled as a None """ tables = { 'logs': """ CREATE TABLE `logs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `message` TEXT NOT NULL, `traceback` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;""" } rows = { 'logs': ( { 'id': 1, 'message': 'from null to value', 'traceback': None }, { 'id': 2, 'message': 'from value to null', 'traceback': 'forever' }, { 'id': 3, 'message': 'from null to null', 'traceback': None }, ) } db_db = database_reader( mysqldb( db_structure( tables, rows ) ) ) db_db.read_rows( 'logs' ) # and one from a file table1 = """CREATE TABLE `logs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `message` TEXT NOT NULL, `traceback` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO logs (id,message,traceback) VALUES (1,'from null to value', 'HEY'); INSERT INTO logs (id,message,traceback) VALUES (2,'from value to null', NULL); INSERT INTO logs (id,message,traceback) VALUES (3,'from null to null', NULL); INSERT INTO logs (id,message,traceback) VALUES (4,'Insert Null',NULL); """ files_db = file_reader( [ table1 ] ) mygrate = row_mygration( files_db, db_db ) ops = [ str( op ) for op in mygrate ] self.assertEquals( 3, len( ops ) ) self.assertTrue( "INSERT INTO `logs` (`id`, `message`, `traceback`) VALUES ('4', 'Insert Null', NULL);" in ops ) self.assertTrue( "UPDATE `logs` SET `message`='from null to value', `traceback`='HEY' WHERE id=1;" in ops ) self.assertTrue( "UPDATE `logs` SET `message`='from value to null', `traceback`=NULL WHERE id=2;" in ops )
def test_diffs_with_quotes(self): """ Things that need backslashes can cause trouble """ # stick close to our use case: get the comparison table from the "database" tables = { 'logs': """ CREATE TABLE `logs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `message` TEXT NOT NULL, `traceback` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;""" } rows = { 'logs': ({ 'id': 1, 'message': 'test\\sup', 'traceback': 'never' }, { 'id': 2, 'message': 'sup\\test', 'traceback': 'forever' }) } db_db = database_reader(mysqldb(db_structure(tables, rows))) db_db.read_rows('logs') # and one from a file table1 = """CREATE TABLE `logs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `message` TEXT NOT NULL, `traceback` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO logs (id,message,traceback) VALUES (1,'test\\sup', 'never'); INSERT INTO logs (id,message,traceback) VALUES (2,'bob\\test', 'forever'); """ files_db = file_reader([table1]) mygrate = row_mygration(files_db, db_db) ops = [str(op) for op in mygrate] self.assertEquals(1, len(ops)) self.assertTrue("`message`='bob\\\\test'" in ops[0])
def execute(self): files_database = database_parser(self.config['files_directory']) # use the credentials to load up a database connection live_database = database_reader(mysqldb(self.credentials)) # we aren't outputting operations. Instead we just need to know what tables # have changed (either structure or records). The easiest (and slightly hack) # way to do that is to simply run an actual mygration and grab out the names # of the tables that have changed. Cheating, I know self.modified_tables = {} mygrate = mygration(live_database, files_database, False) if mygrate.operations: for op in mygrate.operations: self.modified_tables[op.table_name] = True # we have to tell the live database to load records # for any tables we are tracking records for, according # to the files database. for table in files_database.tables.values(): if not table.tracking_rows: continue if not table.name in live_database.tables: continue live_database.read_rows(table) rows = row_mygration(live_database, files_database) if rows.operations: for op in rows.operations: self.modified_tables[op.table_name] = True for table_name in self.modified_tables.keys(): print('\033[1;33m\033[41m%s\033[0m' % table_name) table = live_database.tables[table_name] print(table.nice()) if table.tracking_rows: print('\n') for row in table.rows.values(): print(row_insert(table.name, row))
def execute( self ): files_database = database_parser( self.config['files_directory'] ) # any errors or warnings? if files_database.errors: print( 'Errors found in *.sql files' ) for error in files_database.errors: print( error ) return False # use the credentials to load up a database connection live_database = database_reader( mysqldb( self.credentials ) ) # we have to tell the live database to load records # for any tables we are tracking records for. # We use the "files" database as the "truth" because # just about any database can have records, but # that doesn't mean we want to track them. We only # track them if the file has rows. for table in files_database.tables.values(): if not table.tracking_rows: continue if not table.name in live_database.tables: continue live_database.read_rows( table ) mygrate = mygration( files_database, live_database ) if mygrate.errors_1215: print( '1215 Errors encountered' ) for error in mygrate.errors_1215: print( error ) else: for op in mygrate.operations: live_database.apply_to_source( op )