Skip to content

Commit

Permalink
embedded python mode support
Browse files Browse the repository at this point in the history
  • Loading branch information
daimor committed Feb 3, 2023
1 parent 653bda3 commit 0857676
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 9 deletions.
10 changes: 5 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = sqlalchemy-iris
version = 0.4.2
version = 0.5.0
description = InterSystems IRIS for SQLAlchemy
long_description = file: README.md
url = https://github.com/caretdev/sqlalchemy-iris
Expand All @@ -14,6 +14,7 @@ classifiers =
License :: OSI Approved :: MIT License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Topic :: Database :: Front-Ends
Expand All @@ -24,16 +25,15 @@ project_urls =
Tracker = https://github.com/caretdev/sqlalchemy-iris/issues

[options]
packages =
sqlalchemy_iris
python_requires = >=3.9
python_requires = >=3.8
packages = find:

[tool:pytest]
addopts= --tb native -v -r fxX --maxfail=25 -p no:warnings
python_files=test/*test_*.py

[db]
default=iris://_SYSTEM:SYS@localhost:1972/USER
irisemb=iris+emb:///
sqlite=sqlite:///:memory:

[sqla_testing]
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
entry_points={
"sqlalchemy.dialects": [
"iris = sqlalchemy_iris.iris:IRISDialect_iris",
"iris.emb = sqlalchemy_iris.embedded:IRISDialect_emb",
]
},
)
1 change: 1 addition & 0 deletions sqlalchemy_iris/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
base.dialect = dialect = iris.dialect

_registry.register("iris.iris", "sqlalchemy_iris.iris", "IRISDialect_iris")
_registry.register("iris.emb", "sqlalchemy_iris.embedded", "IRISDialect_emb")

__all__ = [
dialect,
Expand Down
80 changes: 77 additions & 3 deletions sqlalchemy_iris/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,14 @@ def visit_mod_binary(self, binary, operator, **kw):
+ self.process(binary.right, **kw)
)

def visit_regexp_match_op_binary(self, binary, operator, **kw):
# InterSystems use own format for %MATCHES, it does not support Regular Expressions
raise exc.CompileError("InterSystems IRIS does not support REGEXP")

def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
# InterSystems use own format for %MATCHES, it does not support Regular Expressions
raise exc.CompileError("InterSystems IRIS does not support REGEXP")


class IRISDDLCompiler(sql.compiler.DDLCompiler):
"""IRIS syntactic idiosyncrasies"""
Expand Down Expand Up @@ -642,6 +650,27 @@ def __init__(self, dialect):
super(IRISIdentifierPreparer, self).__init__(
dialect, omit_schema=False)

# def _escape_identifier(self, value):
# value = value.replace(self.escape_quote, self.escape_to_quote)
# return value.replace(".", "_")

def format_column(
self,
column,
use_table=False,
name=None,
table_name=None,
use_schema=False,
anon_map=None,
):
if name is None:
name = column.name

# if '.' in name:
# name = name.replace('.', '_')

return super().format_column(column, use_table, name, table_name, use_schema, anon_map)


class IRISExecutionContext(default.DefaultExecutionContext):

Expand Down Expand Up @@ -678,6 +707,8 @@ class IRISDialect(default.DefaultDialect):

name = 'iris'

embedded = False

default_schema_name = "SQLUser"

default_paramstyle = "format"
Expand All @@ -694,6 +725,8 @@ class IRISDialect(default.DefaultDialect):
supports_native_boolean = True
non_native_boolean_check_constraint = False

supports_multivalues_insert = True

supports_sequences = False

postfetch_lastrowid = True
Expand Down Expand Up @@ -741,7 +774,7 @@ def _get_option(self, connection, option):
def _set_option(self, connection, option, value):
cursor = connection.cursor()
# cursor = connection.cursor()
cursor.execute('SELECT %SYSTEM_SQL.Util_SetOption(?, ?)', option, value)
cursor.execute('SELECT %SYSTEM_SQL.Util_SetOption(?, ?)', [option, value])
row = cursor.fetchone()
if row:
return row[0]
Expand Down Expand Up @@ -805,17 +838,55 @@ def create_connect_args(self, url):

opts['autoCommit'] = False

opts['embedded'] = self.embedded

return ([], opts)

_debug_queries = False
# _debug_queries = True

def _debug(self, query, params, many=False):
from decimal import Decimal
if not self._debug_queries:
return
if many:
for p in params:
self._debug(query, p)
return
for p in params:
if isinstance(p, Decimal):
v = str(p)
elif p is None:
v = 'NULL'
else:
v = '%r' % (p, )
query = query.replace('?', v, 1)
print('--')
print(query + ';')
print('--')

def _debug_pre(self, query, params, many=False):
print('-- do_execute' + 'many' if many else '')
if not self._debug_queries:
return
for line in query.split('\n'):
print('-- ', line)
if many:
print(params)
else:
for p in params:
print('-- @param = %r' % (p, ))

def do_execute(self, cursor, query, params, context=None):
self._debug(query, params)
cursor.execute(query, params)

def do_executemany(self, cursor, query, params, context=None):
self._debug(query, params, True)
cursor.executemany(query, params)

def do_begin(self, connection):
pass
# connection.cursor().execute("START TRANSACTION")

def do_rollback(self, connection):
connection.rollback()
Expand Down Expand Up @@ -1171,7 +1242,10 @@ def get_columns(self, connection, table_name, schema=None, **kw):
):
if charlen == -1:
charlen = None
kwargs["length"] = int(charlen)
try:
kwargs["length"] = int(charlen)
except ValueError:
kwargs["length"] = 0
if collation:
kwargs["collation"] = collation
if coltype is None:
Expand Down
18 changes: 18 additions & 0 deletions sqlalchemy_iris/embedded.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from .base import IRISDialect


class IRISDialect_emb(IRISDialect):
driver = "emb"

embedded = True

supports_statement_cache = True

def _get_option(self, connection, option):
return connection.iris.cls('%SYSTEM.SQL.Util').GetOption(option)

def _set_option(self, connection, option, value):
return connection.iris.cls('%SYSTEM.SQL.Util').SetOption(option)


dialect = IRISDialect_emb
43 changes: 42 additions & 1 deletion sqlalchemy_iris/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def binary_literals(self):
e.g. it could be ``BLOB`` or similar.
"""

return exclusions.closed()
return exclusions.open()

@property
def foreign_key_constraint_option_reflection_ondelete(self):
Expand Down Expand Up @@ -173,3 +173,44 @@ def memory_process_intensive(self):
"""
return exclusions.closed()

@property
def ctes(self):
"""Target database supports CTEs"""

return exclusions.open()

@property
def ctes_with_update_delete(self):
"""target database supports CTES that ride on top of a normal UPDATE
or DELETE statement which refers to the CTE in a correlated subquery.
"""

return exclusions.open()

@property
def ctes_on_dml(self):
"""target database supports CTES which consist of INSERT, UPDATE
or DELETE *within* the CTE, e.g. WITH x AS (UPDATE....)"""

return exclusions.open()

@property
def autocommit(self):
"""target dialect supports 'AUTOCOMMIT' as an isolation_level"""
return exclusions.open()

def get_isolation_levels(self, config):
levels = set(config.db.dialect._isolation_lookup)

default = "READ COMMITTED"
levels.add("AUTOCOMMIT")

return {"default": default, "supported": levels}

@property
def regexp_match(self):
"""backend supports the regexp_match operator."""
# InterSystems use own format for %MATCHES and %PATTERN, it does not support Regular Expressions
return exclusions.closed()
Empty file removed test/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions test/conftest.py → tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import sys
sys.path.insert(1, '/home/irisowner/sqlalchemy')
sys.path.insert(1, '/home/irisowner/intersystems-irispython')

from sqlalchemy.dialects import registry
import pytest

registry.register("iris.iris", "sqlalchemy_iris.iris", "IRISDialect_iris")
registry.register("iris.emb", "sqlalchemy_iris.embedded", "IRISDialect_emb")

pytest.register_assert_rewrite("sqlalchemy.testing.assertions")

Expand Down
13 changes: 13 additions & 0 deletions test/test_suite.py → tests/test_suite.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from sqlalchemy.testing.suite import FetchLimitOffsetTest as _FetchLimitOffsetTest
from sqlalchemy.testing.suite import CompoundSelectTest as _CompoundSelectTest
from sqlalchemy.testing.suite import CTETest as _CTETest
from sqlalchemy.testing.suite import DifficultParametersTest as _DifficultParametersTest
from sqlalchemy.testing import fixtures
from sqlalchemy import testing
from sqlalchemy import Table, Column, Integer, String, select
Expand All @@ -14,6 +16,17 @@ def test_limit_offset_aliased_selectable_in_unions(self):
return


class CTETest(_CTETest):
@pytest.mark.skip()
def test_select_recursive_round_trip(self):
pass


@pytest.mark.skip()
class DifficultParametersTest(_DifficultParametersTest):
pass


class FetchLimitOffsetTest(_FetchLimitOffsetTest):

def test_simple_offset_no_order(self, connection):
Expand Down

0 comments on commit 0857676

Please sign in to comment.