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 = DatabaseReader( PyMySQL(connection=DbStructure(tables, rows))) database.read_rows('logs') operations = [str(op) for op in RowMygration(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_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 = DatabaseReader(PyMySQL(connection=DbStructure(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 = DatabaseReader(PyMySQL(connection=DbStructure(tables, rows_from))) database_from.read_rows('logs') operations = [str(op) for op in RowMygration(database, database_from).operations] self.assertEquals(0, len(operations))
def build_commands(self): files_database = DatabaseParser(self.get_sql_files()) # any errors or warnings? quit_early = False if files_database.errors and not self.options.get('force'): print('Errors found in *.sql files', file=sys.stderr) quit_early = True for error in files_database.errors: print(error, file=sys.stderr) if quit_early: return [] # use the credentials to load up a database connection live_database = DatabaseReader(self.get_driver()) # 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 = RowMygration(files_database, live_database) if rows.operations: ops.extend(rows.operations) if not ops: return [] return [ DisableChecks(), *ops, EnableChecks(), ]
def execute(self, print_results=True): files_database = DatabaseParser(self.get_sql_files()) # use the credentials to load up a database connection live_database = DatabaseReader(self.get_driver()) # 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 = RowMygration(live_database, files_database) if rows.operations: for op in rows.operations: self.modified_tables[op.table_name] = True operations = [] for table_name in self.modified_tables.keys(): if print_results: print('\033[1;33m\033[41m%s\033[0m' % table_name) table = live_database.tables[table_name] operations.append(table.nice()) if print_results: print(table.nice()) if table.tracking_rows: if print_results: print('\n') for row in table.rows.values(): operation = row_insert(table.name, row) if print_results: print(operation) operations.append(operation) return (operations, True)
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 = DatabaseReader(PyMySQL(connection=DbStructure(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 = FileReader([table1]) mygrate = RowMygration(files_db, db_db) ops = [str(op) for op in mygrate] self.assertEquals(1, len(ops)) self.assertEquals( "UPDATE `logs` SET `message`='bob\\\\test', `traceback`='forever' WHERE id='2';", ops[0])
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 = DatabaseReader( PyMySQL(connection=DbStructure(tables, rows))) database.read_rows('logs') rows_from = { 'logs': ({ 'id': 1, 'message': 'hey', 'traceback': 'never' }, { 'id': 2, 'message': 'sup', 'traceback': 'forever' }) } database_from = DatabaseReader( PyMySQL(connection=DbStructure(tables, rows_from))) database_from.read_rows('logs') operations = [ str(op) for op in RowMygration(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 test_modify_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 = DatabaseReader( PyMySQL(connection=DbStructure(tables, rows))) database.read_rows('logs') rows_from = { 'logs': ({ 'id': 1, 'message': 'hey', 'traceback': 'bah' }, { 'id': 2, 'message': 'sup', 'traceback': 'forever' }) } database_from = DatabaseReader( PyMySQL(connection=DbStructure(tables, rows_from))) database_from.read_rows('logs') operations = [ str(op) for op in RowMygration(database, database_from).operations ] self.assertEquals(1, len(operations)) self.assertEquals( "UPDATE `logs` SET `message`='hey', `traceback`='never' WHERE id='1';", operations[0])
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 = DatabaseReader(PyMySQL(connection=DbStructure(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 = FileReader([table1]) mygrate = RowMygration(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)