You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							203 lines
						
					
					
						
							6.3 KiB
						
					
					
				
			
		
		
	
	
							203 lines
						
					
					
						
							6.3 KiB
						
					
					
				# firebird/kinterbasdb.py
 | 
						|
# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
 | 
						|
# <see AUTHORS file>
 | 
						|
#
 | 
						|
# This module is part of SQLAlchemy and is released under
 | 
						|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
 | 
						|
 | 
						|
"""
 | 
						|
.. dialect:: firebird+kinterbasdb
 | 
						|
    :name: kinterbasdb
 | 
						|
    :dbapi: kinterbasdb
 | 
						|
    :connectstring: firebird+kinterbasdb://user:password@host:port/path/to/db[?key=value&key=value...]
 | 
						|
    :url: https://firebirdsql.org/index.php?op=devel&sub=python
 | 
						|
 | 
						|
Arguments
 | 
						|
----------
 | 
						|
 | 
						|
The Kinterbasdb backend accepts the ``enable_rowcount`` and ``retaining``
 | 
						|
arguments accepted by the :mod:`sqlalchemy.dialects.firebird.fdb` dialect.
 | 
						|
In addition, it also accepts the following:
 | 
						|
 | 
						|
* ``type_conv`` - select the kind of mapping done on the types: by default
 | 
						|
  SQLAlchemy uses 200 with Unicode, datetime and decimal support.  See
 | 
						|
  the linked documents below for further information.
 | 
						|
 | 
						|
* ``concurrency_level`` - set the backend policy with regards to threading
 | 
						|
  issues: by default SQLAlchemy uses policy 1.  See the linked documents
 | 
						|
  below for further information.
 | 
						|
 | 
						|
.. seealso::
 | 
						|
 | 
						|
    https://sourceforge.net/projects/kinterbasdb
 | 
						|
 | 
						|
    https://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_param_conv_dynamic_type_translation
 | 
						|
 | 
						|
    https://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurrency
 | 
						|
 | 
						|
"""  # noqa
 | 
						|
 | 
						|
import decimal
 | 
						|
from re import match
 | 
						|
 | 
						|
from .base import FBDialect
 | 
						|
from .base import FBExecutionContext
 | 
						|
from ... import types as sqltypes
 | 
						|
from ... import util
 | 
						|
 | 
						|
 | 
						|
class _kinterbasdb_numeric(object):
 | 
						|
    def bind_processor(self, dialect):
 | 
						|
        def process(value):
 | 
						|
            if isinstance(value, decimal.Decimal):
 | 
						|
                return str(value)
 | 
						|
            else:
 | 
						|
                return value
 | 
						|
 | 
						|
        return process
 | 
						|
 | 
						|
 | 
						|
class _FBNumeric_kinterbasdb(_kinterbasdb_numeric, sqltypes.Numeric):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class _FBFloat_kinterbasdb(_kinterbasdb_numeric, sqltypes.Float):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class FBExecutionContext_kinterbasdb(FBExecutionContext):
 | 
						|
    @property
 | 
						|
    def rowcount(self):
 | 
						|
        if self.execution_options.get(
 | 
						|
            "enable_rowcount", self.dialect.enable_rowcount
 | 
						|
        ):
 | 
						|
            return self.cursor.rowcount
 | 
						|
        else:
 | 
						|
            return -1
 | 
						|
 | 
						|
 | 
						|
class FBDialect_kinterbasdb(FBDialect):
 | 
						|
    driver = "kinterbasdb"
 | 
						|
    supports_statement_cache = True
 | 
						|
    supports_sane_rowcount = False
 | 
						|
    supports_sane_multi_rowcount = False
 | 
						|
    execution_ctx_cls = FBExecutionContext_kinterbasdb
 | 
						|
 | 
						|
    supports_native_decimal = True
 | 
						|
 | 
						|
    colspecs = util.update_copy(
 | 
						|
        FBDialect.colspecs,
 | 
						|
        {
 | 
						|
            sqltypes.Numeric: _FBNumeric_kinterbasdb,
 | 
						|
            sqltypes.Float: _FBFloat_kinterbasdb,
 | 
						|
        },
 | 
						|
    )
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        type_conv=200,
 | 
						|
        concurrency_level=1,
 | 
						|
        enable_rowcount=True,
 | 
						|
        retaining=False,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        super(FBDialect_kinterbasdb, self).__init__(**kwargs)
 | 
						|
        self.enable_rowcount = enable_rowcount
 | 
						|
        self.type_conv = type_conv
 | 
						|
        self.concurrency_level = concurrency_level
 | 
						|
        self.retaining = retaining
 | 
						|
        if enable_rowcount:
 | 
						|
            self.supports_sane_rowcount = True
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def dbapi(cls):
 | 
						|
        return __import__("kinterbasdb")
 | 
						|
 | 
						|
    def do_execute(self, cursor, statement, parameters, context=None):
 | 
						|
        # kinterbase does not accept a None, but wants an empty list
 | 
						|
        # when there are no arguments.
 | 
						|
        cursor.execute(statement, parameters or [])
 | 
						|
 | 
						|
    def do_rollback(self, dbapi_connection):
 | 
						|
        dbapi_connection.rollback(self.retaining)
 | 
						|
 | 
						|
    def do_commit(self, dbapi_connection):
 | 
						|
        dbapi_connection.commit(self.retaining)
 | 
						|
 | 
						|
    def create_connect_args(self, url):
 | 
						|
        opts = url.translate_connect_args(username="user")
 | 
						|
        if opts.get("port"):
 | 
						|
            opts["host"] = "%s/%s" % (opts["host"], opts["port"])
 | 
						|
            del opts["port"]
 | 
						|
        opts.update(url.query)
 | 
						|
 | 
						|
        util.coerce_kw_type(opts, "type_conv", int)
 | 
						|
 | 
						|
        type_conv = opts.pop("type_conv", self.type_conv)
 | 
						|
        concurrency_level = opts.pop(
 | 
						|
            "concurrency_level", self.concurrency_level
 | 
						|
        )
 | 
						|
 | 
						|
        if self.dbapi is not None:
 | 
						|
            initialized = getattr(self.dbapi, "initialized", None)
 | 
						|
            if initialized is None:
 | 
						|
                # CVS rev 1.96 changed the name of the attribute:
 | 
						|
                # https://kinterbasdb.cvs.sourceforge.net/viewvc/kinterbasdb/
 | 
						|
                # Kinterbasdb-3.0/__init__.py?r1=1.95&r2=1.96
 | 
						|
                initialized = getattr(self.dbapi, "_initialized", False)
 | 
						|
            if not initialized:
 | 
						|
                self.dbapi.init(
 | 
						|
                    type_conv=type_conv, concurrency_level=concurrency_level
 | 
						|
                )
 | 
						|
        return ([], opts)
 | 
						|
 | 
						|
    def _get_server_version_info(self, connection):
 | 
						|
        """Get the version of the Firebird server used by a connection.
 | 
						|
 | 
						|
        Returns a tuple of (`major`, `minor`, `build`), three integers
 | 
						|
        representing the version of the attached server.
 | 
						|
        """
 | 
						|
 | 
						|
        # This is the simpler approach (the other uses the services api),
 | 
						|
        # that for backward compatibility reasons returns a string like
 | 
						|
        #   LI-V6.3.3.12981 Firebird 2.0
 | 
						|
        # where the first version is a fake one resembling the old
 | 
						|
        # Interbase signature.
 | 
						|
 | 
						|
        fbconn = connection.connection
 | 
						|
        version = fbconn.server_version
 | 
						|
 | 
						|
        return self._parse_version_info(version)
 | 
						|
 | 
						|
    def _parse_version_info(self, version):
 | 
						|
        m = match(
 | 
						|
            r"\w+-V(\d+)\.(\d+)\.(\d+)\.(\d+)( \w+ (\d+)\.(\d+))?", version
 | 
						|
        )
 | 
						|
        if not m:
 | 
						|
            raise AssertionError(
 | 
						|
                "Could not determine version from string '%s'" % version
 | 
						|
            )
 | 
						|
 | 
						|
        if m.group(5) != None:
 | 
						|
            return tuple([int(x) for x in m.group(6, 7, 4)] + ["firebird"])
 | 
						|
        else:
 | 
						|
            return tuple([int(x) for x in m.group(1, 2, 3)] + ["interbase"])
 | 
						|
 | 
						|
    def is_disconnect(self, e, connection, cursor):
 | 
						|
        if isinstance(
 | 
						|
            e, (self.dbapi.OperationalError, self.dbapi.ProgrammingError)
 | 
						|
        ):
 | 
						|
            msg = str(e)
 | 
						|
            return (
 | 
						|
                "Error writing data to the connection" in msg
 | 
						|
                or "Unable to complete network request to host" in msg
 | 
						|
                or "Invalid connection state" in msg
 | 
						|
                or "Invalid cursor state" in msg
 | 
						|
                or "connection shutdown" in msg
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            return False
 | 
						|
 | 
						|
 | 
						|
dialect = FBDialect_kinterbasdb
 |