Products.ZMySQLDA.DA — Products.ZMySQLDA 4.0b2.dev0 documentation

# Source code for Products.ZMySQLDA.DA

    ##############################################################################
    #
    # Copyright (c) 2001 Zope Foundation and Contributors.
    #
    # This software is subject to the provisions of the Zope Public License,
    # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    # FOR A PARTICULAR PURPOSE.
    #
    ##############################################################################
    """ The ZODB-based MySQL Database Connection object
    """
    import six
    from six.moves._thread import allocate_lock

    from AccessControl.class_init import InitializeClass
    from AccessControl.Permissions import change_database_methods
    from AccessControl.Permissions import use_database_methods
    from AccessControl.Permissions import view_management_screens
    from AccessControl.SecurityInfo import ClassSecurityInfo
    from AccessControl.SecurityInfo import ModuleSecurityInfo
    from App.special_dtml import HTMLFile
    from Persistence import Persistent
    from Shared.DC.ZRDB.Connection import Connection as ConnectionBase

    from .db import DB
    from .db import DBPool
    from .permissions import add_zmysql_database_connections
    from .utils import TableBrowser
    from .utils import table_icons

    # Connection Pool for connections to MySQL.
    # Maps one mysql client connection to one DA object instance.
    database_connection_pool_lock = allocate_lock()
    database_connection_pool = {}

    # Combining connection pool with the DB pool gets you
    # one DA/connection per connection with 1 DBPool with 1 DB/thread
    # pool_id -> DA -> DBPool -> thread id -> DB
    # dc_pool[pool_id] == DBPool_instance
    # DBPool_instance[thread id] == DB instance

    class Connection(ConnectionBase):
        """ Zope database adapter for MySQL/MariaDB
        """
        meta_type = "Z MySQL Database Connection"
        security = ClassSecurityInfo()
        zmi_icon = 'fas fa-database'

        auto_create_db = True
        use_unicode = False
        _v_connected = ""
        _isAnSQLConnection = 1
        info = None

        security.declareProtected(view_management_screens, 'manage_browse')
        manage_browse = HTMLFile("www/browse", globals())

        security.declareProtected(change_database_methods, 'manage_properties')
        manage_properties = HTMLFile("www/connectionEdit", globals())

        manage_options = ConnectionBase.manage_options + (
            {"label": "Browse", "action": "manage_browse"},)

        def __init__(self, id, title, connection_string, check, use_unicode=None,
                     auto_create_db=None):
            """ Instance setup. Optionally opens the connection.

            :string: id -- The id of the ZMySQLDA Connection

            :string: title -- The title of the ZMySQLDA Connection

            :string: connection_string -- The connection string describes how to
                                          connect to the relational database.
                                          See the documentation for details.

            :bool: check -- Check if the database connection can be opened after
                            instantiation.

            :bool: use_unicode -- If set to ``True``, values from columns of type
                                  ``CHAR``, ``VARCHAR`` and ``TEXT`` are returned
                                  as unicode strings by the database backend.
                                  Combined with the hardcoded ``UTF-8`` character
                                  set of this package the setting allows you to
                                  control the character set of database return
                                  values better. Default: False.

            :bool: auto_create_db -- If the database given in ``connection_string``
                                     does not exist, create it automatically.
                                     Default: False.
            """
            if use_unicode is not None:
                self.use_unicode = bool(use_unicode)
            if auto_create_db is not None:
                self.auto_create_db = bool(auto_create_db)

            return super(Connection, self).__init__(id, title, connection_string,
                                                    check)

        def __setstate__(self, state):
            """ Skip super's __setstate__ as it connects which we don't want
                due to pool_key depending on acquisition.
            """
            Persistent.__setstate__(self, state)

        security.declareProtected(use_database_methods, 'factory')

        def factory(self):
            """ Base API. Returns factory method for DB connections.
            """
            return DB

        def _pool_key(self):
            """ Return key used for DA pool.
            """
            return self.getPhysicalPath()

        def _getConnection(self):
            """ Helper method to retrieve an existing or create a new connection
            """
            try:
                return self._v_database_connection
            except AttributeError:
                self.connect(self.connection_string)
                return self._v_database_connection

        security.declareProtected(use_database_methods, 'connect')

        def connect(self, conn_string):
            """ Base API. Opens connection to mysql. Raises if problems.

            :string: conn_string -- The database connection string
            """
            pool_key = self._pool_key()
            conn = database_connection_pool.get(pool_key)

            if conn is not None and conn.connection == conn_string:
                self._v_database_connection = conn
                self._v_connected = conn.connected_timestamp
            else:
                if conn is not None:
                    conn.closeConnection()

                conn_pool = DBPool(self.factory(), create_db=self.auto_create_db,
                                   use_unicode=self.use_unicode)
                database_connection_pool_lock.acquire()
                try:
                    conn = conn_pool(conn_string)
                    database_connection_pool[pool_key] = conn
                finally:
                    database_connection_pool_lock.release()

                self._v_database_connection = conn
                # XXX If date is used as such, it can be wrong because an
                # existing connection may be reused. But this is suposedly
                # only used as a marker to know if connection was successfull.
                self._v_connected = conn.connected_timestamp

            return self  # ??? why doesn't this return the connection ???

        security.declareProtected(use_database_methods, 'sql_quote__')

        def sql_quote__(self, sql_str, escapes={}):
            """ Base API. Used to massage SQL strings for use in queries.

            :string: sql_str -- The raw SQL string to transform.

            :dict: escapes -- Additional escape transformations.
                              Default: empty ``dict``.
            """
            connection = self._getConnection()

            if self.use_unicode and isinstance(sql_str, six.text_type):
                return connection.unicode_literal(sql_str)
            else:
                return connection.string_literal(sql_str)

        security.declareProtected(change_database_methods, 'manage_edit')

        def manage_edit(self, title, connection_string, check=None,
                        use_unicode=None, auto_create_db=None):
            """ Edit the connection attributes through the Zope ZMI.

            :string: title -- The title of the ZMySQLDA Connection

            :string: connection_string -- The connection string describes how to
                                          connect to the relational database.
                                          See the documentation for details.

            :bool: check -- Check if the database connection can be opened after
                            instantiation. Default: False.

            :bool: use_unicode -- Use unicode internally. Default: False.

            :bool: auto_create_db -- If the database given in ``connection_string``
                                     does not exist, create it automatically.
                                     Default: False.
            """
            if use_unicode is not None:
                self.use_unicode = bool(use_unicode)
            if auto_create_db is not None:
                self.auto_create_db = bool(auto_create_db)

            return super(Connection, self).manage_edit(title, connection_string,
                                                       check=None)

        security.declareProtected(view_management_screens, 'tpValues')

        def tpValues(self):
            """ Support the DTML ``tree`` tag

            Used in the Zope ZMI ``Browse`` tab
            """
            t_list = []
            connection = self._getConnection()

            for t_info in connection.tables(rdb=0):
                try:
                    t_browser = TableBrowser()
                    t_browser.__name__ = t_info["table_name"]
                    t_browser._d = t_info
                    t_browser._c = connection
                    t_browser.icon = table_icons.get(t_info["table_type"], "text")
                    t_list.append(t_browser)
                except Exception:
                    pass

            return t_list

    InitializeClass(Connection)

    mod_security = ModuleSecurityInfo('Products.ZMySQLDA.DA')
    mod_security.declareProtected(add_zmysql_database_connections,
                                  'manage_addZMySQLConnectionForm')
    manage_addZMySQLConnectionForm = HTMLFile("www/connectionAdd", globals())

    mod_security.declareProtected(add_zmysql_database_connections,
                                  'manage_addZMySQLConnection')

    def manage_addZMySQLConnection(self, id, title, connection_string, check=None,
                                   use_unicode=None, auto_create_db=None,
                                   REQUEST=None):
        """Factory function to add a connection object from the Zope ZMI.

        :string: id -- The id of the ZMySQLDA Connection

        :string: title -- The title of the ZMySQLDA Connection

        :string: connection_string -- The connection string describes how to
                                      connect to the relational database.
                                      See the documentation for details.

        :bool: check -- Check if the database connection can be opened after
                        instantiation. Default: False.

        :bool: use_unicode -- If set to ``True``, values from columns of type
                              ``CHAR``, ``VARCHAR`` and ``TEXT`` are returned
                              as unicode strings by the database backend.
                              Combined with the hardcoded ``UTF-8`` character
                              set of this package the setting allows you to
                              control the character set of database return values
                              better. Default: False.

        :bool: auto_create_db -- If the database given in ``connection_string``
                                 does not exist, create it automatically.
                                 Default: False.

        :object: REQUEST -- The currently active Zope request object.
                            Default: None.
        """
        self._setObject(id,
                        Connection(id, title, connection_string, check,
                                   use_unicode=use_unicode,
                                   auto_create_db=auto_create_db))

        if REQUEST is not None:
            return self.manage_main(self, REQUEST)

    mod_security.apply(globals())