Пример #1
0
    def __init__(self, connection_url, charset):

        self._databases = None

        self.connection = DatabaseConnection()
        self.connection.connect(connection_url, charset)

        self.host = self.connection.host
        self.port = self.connection.port
        self.user = self.connection.user
        self.version = self.connection.version
Пример #2
0
    def __init__(self, connection_url):

        self._databases = None

        self.connection = DatabaseConnection()
        self.connection.connect(connection_url)

        self.host = self.connection.host
        self.port = self.connection.port
        self.user = self.connection.user
        self.version = self.connection.version
Пример #3
0
    async def build_database(self, connection_url, charset, table_names=None):
        self.connection = DatabaseConnection()
        await self.connection.connect(connection_url, charset)
        self.version = await self.connection.version()

        self.host = self.connection.host
        self.port = self.connection.port
        self.user = self.connection.user
        self.table_names = table_names

        self.databases = await database_schema_builder(instance=self)
Пример #4
0
class SchemaObject(object):
    """
    Object representation of a single MySQL instance.
    If database name is not specified in ``connection_url``,
    all databases on the MySQL instance will be loaded.

    ``connection_url`` - the database url as per `RFC1738 <http://www.ietf.org/rfc/rfc1738.txt>`_

      >>> schema  = schemaobject.SchemaObject('mysql://*****:*****@localhost:3306/sakila', 'utf8')
      >>> schema.host
      'localhost'
      >>> schema.port
      3306
      >>> schema.user
      'username'
      >>> schema.version
      '5.1.30'
      >>> schema.selected.name
      'sakila'

    """
    def __init__(self, connection_url, charset):

        self._databases = None

        self.connection = DatabaseConnection()
        self.connection.connect(connection_url, charset)

        self.host = self.connection.host
        self.port = self.connection.port
        self.user = self.connection.user
        self.version = self.connection.version

    @property
    def selected(self):
        """
        Returns the DatabaseSchema object associated with the database name in the connection url

          >>> schema.selected.name
          'sakila'
        """
        if self.connection.db:
            return self.databases[self.connection.db]
        else:
            return None

    @property
    def databases(self):
        """
        Lazily loaded dictionary of the databases within this MySQL instance.

        See DatabaseSchema for usage::

          #if database name is specified in the connection url
          >>> len(schema.databases)
          1
          >>> schema.databases.keys()
          ['sakila']

        """
        if self._databases == None:
            self._databases = DatabaseSchemaBuilder(instance=self)
        return self._databases
Пример #5
0
class SchemaObject(object):
    """
    Object representation of a single MySQL instance.
    If database name is not specified in ``connection_url``,
    all databases on the MySQL instance will be loaded.

    ``connection_url`` - the database url as per `RFC1738 <http://www.ietf.org/rfc/rfc1738.txt>`_

      >>> schema  = schemaobject.SchemaObject('mysql://*****:*****@localhost:3306/sakila')
      >>> schema.host
      'localhost'
      >>> schema.port
      3306
      >>> schema.user
      'username'
      >>> schema.version
      '5.1.30'
      >>> schema.selected.name
      'sakila'

    """

    def __init__(self, connection_url):

        self._databases = None

        self.connection = DatabaseConnection()
        self.connection.connect(connection_url)

        self.host = self.connection.host
        self.port = self.connection.port
        self.user = self.connection.user
        self.version = self.connection.version

    @property
    def selected(self):
        """
        Returns the DatabaseSchema object associated with the database name in the connection url

          >>> schema.selected.name
          'sakila'
        """
        if self.connection.db:
            return self.databases[self.connection.db]
        else:
            return None

    @property
    def databases(self):
        """
        Lazily loaded dictionary of the databases within this MySQL instance.

        See DatabaseSchema for usage::

          #if database name is specified in the connection url
          >>> len(schema.databases)
          1
          >>> schema.databases.keys()
          ['sakila']

        """
        if self._databases == None:
            self._databases = DatabaseSchemaBuilder(instance=self)
        return self._databases
Пример #6
0
def app(sourcedb='',
        targetdb='',
        version_filename=False,
        output_directory=None,
        log_directory=None,
        no_date=False,
        tag=None,
        charset=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)
    if len(logging.getLogger('').handlers) <= 1:
        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

    if source_info['db'] == '*' and target_info['db'] == '*':
        from schemaobject.connection import DatabaseConnection

        sourcedb_none = sourcedb[:-1]
        targetdb_none = targetdb[:-1]
        connection = DatabaseConnection()
        connection.connect(sourcedb_none, charset='utf8')
        sql_schema = """
        SELECT SCHEMA_NAME FROM information_schema.SCHEMATA
        WHERE SCHEMA_NAME NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
        """
        schemas = connection.execute(sql_schema)
        for schema_info in schemas:
            db = schema_info['SCHEMA_NAME']
            sourcedb = sourcedb_none + db
            targetdb = targetdb_none + db
            try:
                app(sourcedb=sourcedb,
                    targetdb=targetdb,
                    version_filename=version_filename,
                    output_directory=output_directory,
                    log_directory=log_directory,
                    no_date=no_date,
                    tag=tag,
                    charset=charset,
                    sync_auto_inc=sync_auto_inc,
                    sync_comments=sync_comments)
            except schemaobject.connection.DatabaseError, e:
                logging.error("MySQL Error %d: %s (Ignore)" %
                              (e.args[0], e.args[1]))
        return 1
Пример #7
0
def app(sourcedb='',
        targetdb='',
        version_filename=False,
        output_directory=None,
        log_directory=None,
        no_date=False,
        tag=None,
        charset=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)
    if len(logging.getLogger('').handlers) <= 1:
        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

    if source_info['db'] == '*' and target_info['db'] == '*':
        from schemaobject.connection import DatabaseConnection

        sourcedb_none = sourcedb[:-1]
        targetdb_none = targetdb[:-1]
        connection = DatabaseConnection()
        connection.connect(sourcedb_none, charset='utf8')
        sql_schema = """
        SELECT SCHEMA_NAME FROM information_schema.SCHEMATA
        WHERE SCHEMA_NAME NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
        """
        schemas = connection.execute(sql_schema)
        for schema_info in schemas:
            db = schema_info['SCHEMA_NAME']
            sourcedb = sourcedb_none + db
            targetdb = targetdb_none + db
            try:
                app(sourcedb=sourcedb,
                    targetdb=targetdb,
                    version_filename=version_filename,
                    output_directory=output_directory,
                    log_directory=log_directory,
                    no_date=no_date,
                    tag=tag,
                    charset=charset,
                    sync_auto_inc=sync_auto_inc,
                    sync_comments=sync_comments)
            except schemaobject.connection.DatabaseError as e:
                logging.error("MySQL Error %d: %s (Ignore)" %
                              (e.args[0], e.args[1]))
        return 1

    source_obj = schemaobject.SchemaObject(sourcedb, charset)
    target_obj = schemaobject.SchemaObject(targetdb, charset)

    if utils.compare_version(source_obj.version, '5.0.0') < 0:
        logging.error("%s requires MySQL version 5.0+ (source is v%s)" %
                      (APPLICATION_NAME, source_obj.version))
        return 1

    if utils.compare_version(target_obj.version, '5.0.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),
        lambda d: utils.REGEX_SEMICOLON_EXPLODE_TO_NEWLINE.sub(";\n", 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,
                                           no_date=no_date)

    ctx['type'] = "Patch Script"
    p_buffer = 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"
    r_buffer = 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:
                p_buffer.write(target_obj.selected.select() + '\n')
                r_buffer.write(target_obj.selected.select() + '\n')
                p_buffer.write(target_obj.selected.fk_checks(0) + '\n')
                r_buffer.write(target_obj.selected.fk_checks(0) + '\n')
                db_selected = True

            p_buffer.write(patch + '\n')
            r_buffer.write(revert + '\n')

    if db_selected:
        p_buffer.write(target_obj.selected.fk_checks(1) + '\n')
        r_buffer.write(target_obj.selected.fk_checks(1) + '\n')

    for patch, revert in syncdb.sync_views(source_obj.selected,
                                           target_obj.selected):
        if patch and revert:
            if not db_selected:
                p_buffer.write(target_obj.selected.select() + '\n')
                r_buffer.write(target_obj.selected.select() + '\n')
                db_selected = True

            p_buffer.write(patch + '\n')
            r_buffer.write(revert + '\n')

    for patch, revert in syncdb.sync_triggers(source_obj.selected,
                                              target_obj.selected):
        if patch and revert:
            if not db_selected:
                p_buffer.write(target_obj.selected.select() + '\n')
                r_buffer.write(target_obj.selected.select() + '\n')
                db_selected = True

            p_buffer.write(patch + '\n')
            r_buffer.write(revert + '\n')

    for patch, revert in syncdb.sync_procedures(source_obj.selected,
                                                target_obj.selected):
        if patch and revert:

            if not db_selected:
                p_buffer.write(target_obj.selected.select() + '\n')
                r_buffer.write(target_obj.selected.select() + '\n')
                p_buffer.write(target_obj.selected.fk_checks(0) + '\n')
                r_buffer.write(target_obj.selected.fk_checks(0) + '\n')
                db_selected = True

            p_buffer.write(patch + '\n')
            r_buffer.write(revert + '\n')

    if not p_buffer.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:
            p_buffer.save()
            r_buffer.save()
            logging.info("Migration scripts created for mysql://%s/%s\n"
                         "Patch Script: %s\nRevert Script: %s" %
                         (target_obj.host, target_obj.selected.name,
                          p_buffer.name, r_buffer.name))
        except OSError as e:
            p_buffer.delete()
            r_buffer.delete()
            logging.error("Failed writing migration scripts. %s" % e)
            return 1

    return 0