Skip to content

Commit 0857676

Browse files
committed
embedded python mode support
1 parent 653bda3 commit 0857676

File tree

9 files changed

+162
-9
lines changed

9 files changed

+162
-9
lines changed

setup.cfg

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = sqlalchemy-iris
3-
version = 0.4.2
3+
version = 0.5.0
44
description = InterSystems IRIS for SQLAlchemy
55
long_description = file: README.md
66
url = https://github.com/caretdev/sqlalchemy-iris
@@ -14,6 +14,7 @@ classifiers =
1414
License :: OSI Approved :: MIT License
1515
Programming Language :: Python
1616
Programming Language :: Python :: 3
17+
Programming Language :: Python :: 3.8
1718
Programming Language :: Python :: 3.9
1819
Programming Language :: Python :: 3.10
1920
Topic :: Database :: Front-Ends
@@ -24,16 +25,15 @@ project_urls =
2425
Tracker = https://github.com/caretdev/sqlalchemy-iris/issues
2526

2627
[options]
27-
packages =
28-
sqlalchemy_iris
29-
python_requires = >=3.9
28+
python_requires = >=3.8
29+
packages = find:
3030

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

3534
[db]
3635
default=iris://_SYSTEM:SYS@localhost:1972/USER
36+
irisemb=iris+emb:///
3737
sqlite=sqlite:///:memory:
3838

3939
[sqla_testing]

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
entry_points={
2020
"sqlalchemy.dialects": [
2121
"iris = sqlalchemy_iris.iris:IRISDialect_iris",
22+
"iris.emb = sqlalchemy_iris.embedded:IRISDialect_emb",
2223
]
2324
},
2425
)

sqlalchemy_iris/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
base.dialect = dialect = iris.dialect
77

88
_registry.register("iris.iris", "sqlalchemy_iris.iris", "IRISDialect_iris")
9+
_registry.register("iris.emb", "sqlalchemy_iris.embedded", "IRISDialect_emb")
910

1011
__all__ = [
1112
dialect,

sqlalchemy_iris/base.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,14 @@ def visit_mod_binary(self, binary, operator, **kw):
524524
+ self.process(binary.right, **kw)
525525
)
526526

527+
def visit_regexp_match_op_binary(self, binary, operator, **kw):
528+
# InterSystems use own format for %MATCHES, it does not support Regular Expressions
529+
raise exc.CompileError("InterSystems IRIS does not support REGEXP")
530+
531+
def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
532+
# InterSystems use own format for %MATCHES, it does not support Regular Expressions
533+
raise exc.CompileError("InterSystems IRIS does not support REGEXP")
534+
527535

528536
class IRISDDLCompiler(sql.compiler.DDLCompiler):
529537
"""IRIS syntactic idiosyncrasies"""
@@ -642,6 +650,27 @@ def __init__(self, dialect):
642650
super(IRISIdentifierPreparer, self).__init__(
643651
dialect, omit_schema=False)
644652

653+
# def _escape_identifier(self, value):
654+
# value = value.replace(self.escape_quote, self.escape_to_quote)
655+
# return value.replace(".", "_")
656+
657+
def format_column(
658+
self,
659+
column,
660+
use_table=False,
661+
name=None,
662+
table_name=None,
663+
use_schema=False,
664+
anon_map=None,
665+
):
666+
if name is None:
667+
name = column.name
668+
669+
# if '.' in name:
670+
# name = name.replace('.', '_')
671+
672+
return super().format_column(column, use_table, name, table_name, use_schema, anon_map)
673+
645674

646675
class IRISExecutionContext(default.DefaultExecutionContext):
647676

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

679708
name = 'iris'
680709

710+
embedded = False
711+
681712
default_schema_name = "SQLUser"
682713

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

728+
supports_multivalues_insert = True
729+
697730
supports_sequences = False
698731

699732
postfetch_lastrowid = True
@@ -741,7 +774,7 @@ def _get_option(self, connection, option):
741774
def _set_option(self, connection, option, value):
742775
cursor = connection.cursor()
743776
# cursor = connection.cursor()
744-
cursor.execute('SELECT %SYSTEM_SQL.Util_SetOption(?, ?)', option, value)
777+
cursor.execute('SELECT %SYSTEM_SQL.Util_SetOption(?, ?)', [option, value])
745778
row = cursor.fetchone()
746779
if row:
747780
return row[0]
@@ -805,17 +838,55 @@ def create_connect_args(self, url):
805838

806839
opts['autoCommit'] = False
807840

841+
opts['embedded'] = self.embedded
842+
808843
return ([], opts)
809844

845+
_debug_queries = False
846+
# _debug_queries = True
847+
848+
def _debug(self, query, params, many=False):
849+
from decimal import Decimal
850+
if not self._debug_queries:
851+
return
852+
if many:
853+
for p in params:
854+
self._debug(query, p)
855+
return
856+
for p in params:
857+
if isinstance(p, Decimal):
858+
v = str(p)
859+
elif p is None:
860+
v = 'NULL'
861+
else:
862+
v = '%r' % (p, )
863+
query = query.replace('?', v, 1)
864+
print('--')
865+
print(query + ';')
866+
print('--')
867+
868+
def _debug_pre(self, query, params, many=False):
869+
print('-- do_execute' + 'many' if many else '')
870+
if not self._debug_queries:
871+
return
872+
for line in query.split('\n'):
873+
print('-- ', line)
874+
if many:
875+
print(params)
876+
else:
877+
for p in params:
878+
print('-- @param = %r' % (p, ))
879+
810880
def do_execute(self, cursor, query, params, context=None):
881+
self._debug(query, params)
811882
cursor.execute(query, params)
812883

813884
def do_executemany(self, cursor, query, params, context=None):
885+
self._debug(query, params, True)
814886
cursor.executemany(query, params)
815887

816888
def do_begin(self, connection):
817889
pass
818-
# connection.cursor().execute("START TRANSACTION")
819890

820891
def do_rollback(self, connection):
821892
connection.rollback()
@@ -1171,7 +1242,10 @@ def get_columns(self, connection, table_name, schema=None, **kw):
11711242
):
11721243
if charlen == -1:
11731244
charlen = None
1174-
kwargs["length"] = int(charlen)
1245+
try:
1246+
kwargs["length"] = int(charlen)
1247+
except ValueError:
1248+
kwargs["length"] = 0
11751249
if collation:
11761250
kwargs["collation"] = collation
11771251
if coltype is None:

sqlalchemy_iris/embedded.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from .base import IRISDialect
2+
3+
4+
class IRISDialect_emb(IRISDialect):
5+
driver = "emb"
6+
7+
embedded = True
8+
9+
supports_statement_cache = True
10+
11+
def _get_option(self, connection, option):
12+
return connection.iris.cls('%SYSTEM.SQL.Util').GetOption(option)
13+
14+
def _set_option(self, connection, option, value):
15+
return connection.iris.cls('%SYSTEM.SQL.Util').SetOption(option)
16+
17+
18+
dialect = IRISDialect_emb

sqlalchemy_iris/requirements.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def binary_literals(self):
9191
e.g. it could be ``BLOB`` or similar.
9292
"""
9393

94-
return exclusions.closed()
94+
return exclusions.open()
9595

9696
@property
9797
def foreign_key_constraint_option_reflection_ondelete(self):
@@ -173,3 +173,44 @@ def memory_process_intensive(self):
173173
174174
"""
175175
return exclusions.closed()
176+
177+
@property
178+
def ctes(self):
179+
"""Target database supports CTEs"""
180+
181+
return exclusions.open()
182+
183+
@property
184+
def ctes_with_update_delete(self):
185+
"""target database supports CTES that ride on top of a normal UPDATE
186+
or DELETE statement which refers to the CTE in a correlated subquery.
187+
188+
"""
189+
190+
return exclusions.open()
191+
192+
@property
193+
def ctes_on_dml(self):
194+
"""target database supports CTES which consist of INSERT, UPDATE
195+
or DELETE *within* the CTE, e.g. WITH x AS (UPDATE....)"""
196+
197+
return exclusions.open()
198+
199+
@property
200+
def autocommit(self):
201+
"""target dialect supports 'AUTOCOMMIT' as an isolation_level"""
202+
return exclusions.open()
203+
204+
def get_isolation_levels(self, config):
205+
levels = set(config.db.dialect._isolation_lookup)
206+
207+
default = "READ COMMITTED"
208+
levels.add("AUTOCOMMIT")
209+
210+
return {"default": default, "supported": levels}
211+
212+
@property
213+
def regexp_match(self):
214+
"""backend supports the regexp_match operator."""
215+
# InterSystems use own format for %MATCHES and %PATTERN, it does not support Regular Expressions
216+
return exclusions.closed()

test/__init__.py

Whitespace-only changes.

test/conftest.py renamed to tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import sys
2+
sys.path.insert(1, '/home/irisowner/sqlalchemy')
3+
sys.path.insert(1, '/home/irisowner/intersystems-irispython')
4+
15
from sqlalchemy.dialects import registry
26
import pytest
37

48
registry.register("iris.iris", "sqlalchemy_iris.iris", "IRISDialect_iris")
9+
registry.register("iris.emb", "sqlalchemy_iris.embedded", "IRISDialect_emb")
510

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

test/test_suite.py renamed to tests/test_suite.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from sqlalchemy.testing.suite import FetchLimitOffsetTest as _FetchLimitOffsetTest
22
from sqlalchemy.testing.suite import CompoundSelectTest as _CompoundSelectTest
3+
from sqlalchemy.testing.suite import CTETest as _CTETest
4+
from sqlalchemy.testing.suite import DifficultParametersTest as _DifficultParametersTest
35
from sqlalchemy.testing import fixtures
46
from sqlalchemy import testing
57
from sqlalchemy import Table, Column, Integer, String, select
@@ -14,6 +16,17 @@ def test_limit_offset_aliased_selectable_in_unions(self):
1416
return
1517

1618

19+
class CTETest(_CTETest):
20+
@pytest.mark.skip()
21+
def test_select_recursive_round_trip(self):
22+
pass
23+
24+
25+
@pytest.mark.skip()
26+
class DifficultParametersTest(_DifficultParametersTest):
27+
pass
28+
29+
1730
class FetchLimitOffsetTest(_FetchLimitOffsetTest):
1831

1932
def test_simple_offset_no_order(self, connection):

0 commit comments

Comments
 (0)