Merges specific **tables** or all tables of **conflicted** database into the master database.

        Parameters
        ----------
        conflicted: str
            The path of the SQL database to be merged into the master.
        tables: list (optional)
            The list of tables to merge. If None, all tables are merged.
        diff_only: bool
                If True, only prints the differences of each table and doesn't actually merge anything.

def merge(self, conflicted, tables=[], diff_only=True):
        """
        Merges specific **tables** or all tables of **conflicted** database into the master database.

        Parameters
        ----------
        conflicted: str
            The path of the SQL database to be merged into the master.
        tables: list (optional)
            The list of tables to merge. If None, all tables are merged.
        diff_only: bool
                If True, only prints the differences of each table and doesn't actually merge anything.

        """
        if os.path.isfile(conflicted):
            # Load and attach master and conflicted databases
            con, master, reassign = Database(conflicted), self.list("PRAGMA database_list").fetchall()[0][2], {}
            con.modify("ATTACH DATABASE '{}' AS m".format(master), verbose=False)
            self.modify("ATTACH DATABASE '{}' AS c".format(conflicted), verbose=False)
            con.modify("ATTACH DATABASE '{}' AS c".format(conflicted), verbose=False)
            self.modify("ATTACH DATABASE '{}' AS m".format(master), verbose=False)

            # Drop any backup tables from failed merges
            for table in tables: self.modify("DROP TABLE IF EXISTS Backup_{0}".format(table), verbose=False)

            # Gather user data to add to CHANGELOG table
            import socket, datetime
            if not diff_only: user = get_input('Please enter your name : ')
            machine_name = socket.gethostname()
            date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
            modified_tables = []

            # Merge table by table, starting with SOURCES
            if not isinstance(tables, type(list())):
                tables = [tables]

            tables = tables or ['sources'] + [t for t in zip(*self.list(
                "SELECT * FROM sqlite_master WHERE name NOT LIKE '%Backup%' AND name!='sqlite_sequence' AND type='table'{}".format(
                    " AND name IN ({})".format("'" + "','".join(tables) + "'") if tables else '')))[1] if
                                              t != 'sources']
            for table in tables:
                # Get column names and data types from master table and column names from conflicted table
                metadata = self.query("PRAGMA table_info({})".format(table), fmt='table')
                columns, types, constraints = [np.array(metadata[n]) for n in ['name', 'type', 'notnull']]
                # columns, types, constraints = self.query("PRAGMA table_info({})".format(table), unpack=True)[1:4]
                conflicted_cols = con.query("PRAGMA table_info({})".format(table), unpack=True)[1]

                if any([i not in columns for i in conflicted_cols]):
                    # Abort table merge if conflicted has new columns not present in master. New columns must be added to the master database first via db.edit_columns().
                    print(
                    "\nMerge of {0} table aborted since conflicted copy has columns {1} not present in master.\nAdd new columns to master with astrodb.table() method and try again.\n".format(
                        table.upper(), [i for i in conflicted_cols if i not in columns]))

                else:
                    # Add new columns from master table to conflicted table if necessary
                    if any([i not in conflicted_cols for i in columns]):
                        con.modify("DROP TABLE IF EXISTS Conflicted_{0}".format(table))
                        con.modify("ALTER TABLE {0} RENAME TO Conflicted_{0}".format(table))
                        # TODO: Update to allow multiple primary and foreign keys
                        con.modify("CREATE TABLE {0} ({1})".format(table, ', '.join( \
                                ['{} {} {}{}'.format(c, t, r, ' UNIQUE PRIMARY KEY' if c == 'id' else '') \
                                 for c, t, r in zip(columns, types, constraints * ['NOT NULL'])])))
                        con.modify("INSERT INTO {0} ({1}) SELECT {1} FROM Conflicted_{0}".format(table, ','.join(
                            conflicted_cols)))
                        con.modify("DROP TABLE Conflicted_{0}".format(table))

                    # Pull unique records from conflicted table
                    data = map(list, con.list(
                        "SELECT * FROM (SELECT 1 AS db, {0} FROM m.{2} UNION ALL SELECT 2 AS db, {0} FROM c.{2}) GROUP BY {1} HAVING COUNT(*)=1 AND db=2".format(
                            ','.join(columns), ','.join(columns[1:]), table)).fetchall())

                    if data:

                        # Just print the table differences
                        if diff_only:
                            pprint(zip(*data)[1:], names=columns, title='New {} records'.format(table.upper()))

                        # Add new records to the master and then clean up tables
                        else:
                            # Make temporary table copy so changes can be undone at any time
                            self.modify("DROP TABLE IF EXISTS Backup_{0}".format(table), verbose=False)
                            self.modify("ALTER TABLE {0} RENAME TO Backup_{0}".format(table), verbose=False)
                            self.modify("CREATE TABLE {0} ({1})".format(table, ', '.join( \
                                    ['{} {} {}{}'.format(c, t, r, ' UNIQUE PRIMARY KEY' if c == 'id' else '') \
                                     for c, t, r in zip(columns, types, constraints * ['NOT NULL'])])), verbose=False)
                            self.modify(
                                "INSERT INTO {0} ({1}) SELECT {1} FROM Backup_{0}".format(table, ','.join(columns)),
                                verbose=False)

                            # Create a dictionary of any reassigned ids from merged SOURCES tables and replace applicable source_ids in other tables.
                            print("\nMerging {} tables.\n".format(table.upper()))
                            try:
                                count = self.query("SELECT MAX(id) FROM {}".format(table), fetch='one')[0] + 1
                            except TypeError:
                                count = 1
                            for n, i in enumerate([d[1:] for d in data]):
                                if table == 'sources':
                                    reassign[i[0]] = count
                                elif 'source_id' in columns and i[1] in reassign.keys():
                                    i[1] = reassign[i[1]]
                                else:
                                    pass
                                i[0] = count
                                data[n] = i
                                count += 1

                            # Insert unique records into master
                            for d in data: self.modify(
                                "INSERT INTO {} VALUES({})".format(table, ','.join(['?' for c in columns])), d,
                                verbose=False)
                            pprint(zip(*data), names=columns,
                                   title="{} records added to {} table at '{}':".format(len(data), table, master))

                            # Run clean_up on the table to check for conflicts
                            abort = self.clean_up(table)

                            # Undo all changes to table if merge is aborted. Otherwise, push table changes to master.
                            if abort:
                                self.modify("DROP TABLE {0}".format(table), verbose=False)
                                self.modify("ALTER TABLE Backup_{0} RENAME TO {0}".format(table), verbose=False)
                            else:
                                self.modify("DROP TABLE Backup_{0}".format(table), verbose=False)
                                modified_tables.append(table.upper())

                    else:
                        print("\n{} tables identical.".format(table.upper()))

            # Add data to CHANGELOG table
            if not diff_only:
                user_description = get_input('\nPlease describe the changes made in this merge: ')
                self.list("INSERT INTO changelog VALUES(?, ?, ?, ?, ?, ?, ?)", \
                          (None, date, str(user), machine_name, ', '.join(modified_tables), user_description,
                           os.path.basename(conflicted)))

            # Finish up and detach
            if diff_only:
                print("\nDiff complete. No changes made to either database. Set `diff_only=False' to apply merge.")
            else:
                print("\nMerge complete!")

            con.modify("DETACH DATABASE c", verbose=False)
            self.modify("DETACH DATABASE c", verbose=False)
            con.modify("DETACH DATABASE m", verbose=False)
            self.modify("DETACH DATABASE m", verbose=False)
        else:
            print("File '{}' not found!".format(conflicted))