|
| 1 | +import re |
| 2 | +import pkg_resources |
| 3 | +from ..base import IRISDialect |
| 4 | +from sqlalchemy import text, util |
| 5 | +from ..base import IRISExecutionContext |
| 6 | +from . import dbapi |
| 7 | +from .dbapi import connect, Cursor |
| 8 | +from .cursor import InterSystemsCursorFetchStrategy |
| 9 | +from .dbapi import IntegrityError, OperationalError, DatabaseError |
| 10 | + |
| 11 | + |
| 12 | +def remap_exception(func): |
| 13 | + def wrapper(cursor, *args, **kwargs): |
| 14 | + attempt = 0 |
| 15 | + while attempt < 3: |
| 16 | + attempt += 1 |
| 17 | + try: |
| 18 | + cursor.sqlcode = 0 |
| 19 | + return func(cursor, *args, **kwargs) |
| 20 | + except RuntimeError as ex: |
| 21 | + # [SQLCODE: <-119>:... |
| 22 | + message = ex.args[0] |
| 23 | + if '<LIST ERROR>' in message: |
| 24 | + # just random error happens in the driver, try again |
| 25 | + continue |
| 26 | + sqlcode = re.findall(r"^\[SQLCODE: <(-\d+)>:", message) |
| 27 | + if not sqlcode: |
| 28 | + raise Exception(message) |
| 29 | + sqlcode = int(sqlcode[0]) |
| 30 | + if abs(sqlcode) in [108, 119, 121, 122]: |
| 31 | + raise IntegrityError(message) |
| 32 | + if abs(sqlcode) in [1, 12]: |
| 33 | + raise OperationalError(message) |
| 34 | + raise DatabaseError(message) |
| 35 | + |
| 36 | + return wrapper |
| 37 | + |
| 38 | + |
| 39 | +class InterSystemsExecutionContext(IRISExecutionContext): |
| 40 | + cursor_fetch_strategy = InterSystemsCursorFetchStrategy() |
| 41 | + |
| 42 | + def create_cursor(self): |
| 43 | + cursor = self._dbapi_connection.cursor() |
| 44 | + cursor.sqlcode = 0 |
| 45 | + return cursor |
| 46 | + |
| 47 | + |
| 48 | +class IRISDialect_intersystems(IRISDialect): |
| 49 | + driver = "intersystems" |
| 50 | + |
| 51 | + execution_ctx_cls = InterSystemsExecutionContext |
| 52 | + |
| 53 | + supports_statement_cache = True |
| 54 | + |
| 55 | + supports_cte = False |
| 56 | + |
| 57 | + supports_sane_rowcount = False |
| 58 | + supports_sane_multi_rowcount = False |
| 59 | + |
| 60 | + insert_returning = False |
| 61 | + insert_executemany_returning = False |
| 62 | + |
| 63 | + logfile = None |
| 64 | + |
| 65 | + def __init__(self, logfile: str = None, **kwargs): |
| 66 | + self.logfile = logfile |
| 67 | + IRISDialect.__init__(self, **kwargs) |
| 68 | + |
| 69 | + @classmethod |
| 70 | + def import_dbapi(cls): |
| 71 | + return dbapi |
| 72 | + |
| 73 | + def connect(self, *cargs, **kwarg): |
| 74 | + host = kwarg.get("hostname", "localhost") |
| 75 | + port = kwarg.get("port", 1972) |
| 76 | + namespace = kwarg.get("namespace", "USER") |
| 77 | + username = kwarg.get("username", "_SYSTEM") |
| 78 | + password = kwarg.get("password", "SYS") |
| 79 | + timeout = kwarg.get("timeout", 10) |
| 80 | + sharedmemory = kwarg.get("sharedmemory", False) |
| 81 | + logfile = kwarg.get("logfile", self.logfile) |
| 82 | + sslconfig = kwarg.get("sslconfig", False) |
| 83 | + autoCommit = kwarg.get("autoCommit", False) |
| 84 | + isolationLevel = kwarg.get("isolationLevel", 1) |
| 85 | + return connect( |
| 86 | + host, |
| 87 | + port, |
| 88 | + namespace, |
| 89 | + username, |
| 90 | + password, |
| 91 | + timeout, |
| 92 | + sharedmemory, |
| 93 | + logfile, |
| 94 | + sslconfig, |
| 95 | + autoCommit, |
| 96 | + isolationLevel, |
| 97 | + ) |
| 98 | + |
| 99 | + def create_connect_args(self, url): |
| 100 | + opts = {} |
| 101 | + |
| 102 | + opts["application_name"] = "sqlalchemy" |
| 103 | + opts["host"] = url.host |
| 104 | + opts["port"] = int(url.port) if url.port else 1972 |
| 105 | + opts["namespace"] = url.database if url.database else "USER" |
| 106 | + opts["username"] = url.username if url.username else "" |
| 107 | + opts["password"] = url.password if url.password else "" |
| 108 | + |
| 109 | + opts["autoCommit"] = False |
| 110 | + |
| 111 | + if opts["host"] and "@" in opts["host"]: |
| 112 | + _h = opts["host"].split("@") |
| 113 | + opts["password"] += "@" + _h[0 : len(_h) - 1].join("@") |
| 114 | + opts["host"] = _h[len(_h) - 1] |
| 115 | + |
| 116 | + return ([], opts) |
| 117 | + |
| 118 | + def _get_server_version_info(self, connection): |
| 119 | + # get the wheel version from iris module |
| 120 | + try: |
| 121 | + return tuple( |
| 122 | + map( |
| 123 | + int, |
| 124 | + pkg_resources.get_distribution( |
| 125 | + "intersystems_irispython" |
| 126 | + ).version.split("."), |
| 127 | + ) |
| 128 | + ) |
| 129 | + except: # noqa |
| 130 | + return None |
| 131 | + |
| 132 | + def _get_option(self, connection, option): |
| 133 | + with connection.cursor() as cursor: |
| 134 | + cursor.execute("SELECT %SYSTEM_SQL.Util_GetOption(?)", (option,)) |
| 135 | + row = cursor.fetchone() |
| 136 | + if row: |
| 137 | + return row[0] |
| 138 | + return None |
| 139 | + |
| 140 | + def set_isolation_level(self, connection, level_str): |
| 141 | + if level_str == "AUTOCOMMIT": |
| 142 | + connection.autocommit = True |
| 143 | + else: |
| 144 | + connection.autocommit = False |
| 145 | + if level_str not in ["READ COMMITTED", "READ VERIFIED"]: |
| 146 | + level_str = "READ UNCOMMITTED" |
| 147 | + with connection.cursor() as cursor: |
| 148 | + cursor.execute("SET TRANSACTION ISOLATION LEVEL " + level_str) |
| 149 | + |
| 150 | + @remap_exception |
| 151 | + def do_execute(self, cursor, query, params, context=None): |
| 152 | + if query.endswith(";"): |
| 153 | + query = query[:-1] |
| 154 | + self._debug(query, params) |
| 155 | + cursor.execute(query, params) |
| 156 | + |
| 157 | + @remap_exception |
| 158 | + def do_executemany(self, cursor, query, params, context=None): |
| 159 | + if query.endswith(";"): |
| 160 | + query = query[:-1] |
| 161 | + self._debug(query, params, many=True) |
| 162 | + if params and (len(params[0]) <= 1): |
| 163 | + params = [param[0] if len(param) else None for param in params] |
| 164 | + cursor.executemany(query, params) |
| 165 | + |
| 166 | + |
| 167 | +dialect = IRISDialect_intersystems |
0 commit comments