Skip to content

Commit 8425112

Browse files
committed
autocommit, isolation levels, getlastrowid, patch for fetchone
1 parent 73c83e9 commit 8425112

File tree

3 files changed

+198
-46
lines changed

3 files changed

+198
-46
lines changed

sqlalchemy_iris/base.py

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import datetime
22
from telnetlib import BINARY
3+
from iris.dbapi._DBAPI import Cursor
4+
from iris.dbapi._ResultSetRow import _ResultSetRow
5+
from iris.dbapi._DBAPI import SQLType as IRISSQLType
6+
import iris._IRISNative as irisnative
7+
import iris.dbapi._DBAPI as dbapi
38
from . import information_schema as ischema
49
from sqlalchemy import exc
510
from sqlalchemy.orm import aliased
@@ -9,6 +14,7 @@
914
from sqlalchemy.sql import util as sql_util
1015
from sqlalchemy.sql import between
1116
from sqlalchemy.sql import func
17+
from sqlalchemy.sql import expression
1218
from sqlalchemy import sql, text
1319
from sqlalchemy import util
1420
from sqlalchemy import types as sqltypes
@@ -501,8 +507,41 @@ def __init__(self, dialect):
501507
dialect, omit_schema=False)
502508

503509

510+
class CursorWrapper(Cursor):
511+
def __init__(self, connection):
512+
super(CursorWrapper, self).__init__(connection)
513+
514+
def fetchone(self):
515+
retval = super(CursorWrapper, self).fetchone()
516+
if retval is None:
517+
return None
518+
if not isinstance(retval, _ResultSetRow.DataRow):
519+
return retval
520+
521+
# Workaround for fetchone, which returns values in row not from 0
522+
row = []
523+
for c in self._columns:
524+
value = retval[c.name]
525+
# Workaround for issue, when int returned as string
526+
if value is not None and c.type in (IRISSQLType.INTEGER, IRISSQLType.BIGINT,) and type(value) is not int:
527+
value = int(value)
528+
row.append(value)
529+
return row
530+
531+
504532
class IRISExecutionContext(default.DefaultExecutionContext):
505-
pass
533+
534+
def get_lastrowid(self):
535+
cursor = self.create_cursor()
536+
cursor.execute("SELECT LAST_IDENTITY()")
537+
lastrowid = cursor.fetchone()[0]
538+
cursor.close()
539+
return lastrowid
540+
541+
def create_cursor(self):
542+
# cursor = self._dbapi_connection.cursor()
543+
cursor = CursorWrapper(self._dbapi_connection)
544+
return cursor
506545

507546

508547
HOROLOG_ORDINAL = datetime.date(1840, 12, 31).toordinal()
@@ -594,7 +633,7 @@ class IRISDialect(default.DefaultDialect):
594633

595634
supports_sequences = False
596635

597-
postfetch_lastrowid = False
636+
postfetch_lastrowid = True
598637
non_native_boolean_check_constraint = False
599638
supports_simple_order_by_label = False
600639
supports_empty_insert = False
@@ -612,11 +651,58 @@ class IRISDialect(default.DefaultDialect):
612651

613652
def __init__(self, **kwargs):
614653
default.DefaultDialect.__init__(self, **kwargs)
654+
self._auto_parallel = 1
655+
656+
_isolation_lookup = set(
657+
[
658+
"READ UNCOMMITTED",
659+
"READ COMMITTED",
660+
"READ VERIFIED",
661+
]
662+
)
663+
664+
def _get_option(self, connection, option):
665+
cursor = CursorWrapper(connection)
666+
# cursor = connection.cursor()
667+
cursor.execute('SELECT %SYSTEM_SQL.Util_GetOption(?)', [option, ])
668+
row = cursor.fetchone()
669+
if row:
670+
return row[0]
671+
return None
672+
673+
def _set_option(self, connection, option, value):
674+
cursor = CursorWrapper(connection)
675+
# cursor = connection.cursor()
676+
cursor.execute('SELECT %SYSTEM_SQL.Util_SetOption(?, ?)', [option, value, ])
677+
row = cursor.fetchone()
678+
if row:
679+
return row[0]
680+
return None
681+
682+
def get_isolation_level(self, connection):
683+
level = int(self._get_option(connection, 'IsolationMode'))
684+
if level == 0:
685+
return 'READ UNCOMMITTED'
686+
elif level == 1:
687+
return 'READ COMMITTED'
688+
elif level == 3:
689+
return 'READ VERIFIED'
690+
return None
691+
692+
def set_isolation_level(self, connection, level_str):
693+
if level_str == "AUTOCOMMIT":
694+
connection.setAutoCommit(True)
695+
else:
696+
connection.setAutoCommit(False)
697+
level = 0
698+
if level_str == 'READ COMMITTED':
699+
level = 1
700+
elif level_str == 'READ VERIFIED':
701+
level = 3
702+
self._set_option(connection, 'IsolationMode', level)
615703

616704
@classmethod
617705
def dbapi(cls):
618-
import iris._IRISNative as irisnative
619-
import iris.dbapi._DBAPI as dbapi
620706
dbapi.connect = irisnative.connect
621707
dbapi.paramstyle = "format"
622708
return dbapi
@@ -629,6 +715,8 @@ def create_connect_args(self, url):
629715
opts["username"] = url.username if url.username else ''
630716
opts["password"] = url.password if url.password else ''
631717

718+
opts['autoCommit'] = False
719+
632720
return ([], opts)
633721

634722
def _fix_for_params(self, query, params, many=False):
@@ -659,6 +747,21 @@ def do_executemany(self, cursor, query, params, context=None):
659747
query, params = self._fix_for_params(query, params, True)
660748
cursor.executemany(query, params)
661749

750+
def do_begin(self, connection):
751+
pass
752+
753+
def do_rollback(self, connection):
754+
connection.rollback()
755+
756+
def do_commit(self, connection):
757+
connection.commit()
758+
759+
def do_savepoint(self, connection, name):
760+
connection.execute(expression.SavepointClause(name))
761+
762+
def do_release_savepoint(self, connection, name):
763+
pass
764+
662765
def get_schema(self, schema=None):
663766
if schema is None:
664767
return 'SQLUser'

sqlalchemy_iris/requirements.py

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def implements_get_lastrowid(self):
298298
method without reliance on RETURNING.
299299
300300
"""
301-
# return exclusions.open()
301+
return exclusions.open()
302302
return exclusions.closed()
303303

304304
@property
@@ -658,7 +658,7 @@ def binary_literals(self):
658658
@property
659659
def autocommit(self):
660660
"""target dialect supports 'AUTOCOMMIT' as an isolation_level"""
661-
return exclusions.closed()
661+
return exclusions.open()
662662

663663
@property
664664
def isolation_level(self):
@@ -668,33 +668,18 @@ def isolation_level(self):
668668
the get_isolation_levels() method be implemented.
669669
670670
"""
671-
return exclusions.closed()
671+
return exclusions.open()
672672

673673
def get_isolation_levels(self, config):
674-
"""Return a structure of supported isolation levels for the current
675-
testing dialect.
676-
677-
The structure indicates to the testing suite what the expected
678-
"default" isolation should be, as well as the other values that
679-
are accepted. The dictionary has two keys, "default" and "supported".
680-
The "supported" key refers to a list of all supported levels and
681-
it should include AUTOCOMMIT if the dialect supports it.
682-
683-
If the :meth:`.DefaultRequirements.isolation_level` requirement is
684-
not open, then this method has no return value.
685-
686-
E.g.::
687-
688-
>>> testing.requirements.get_isolation_levels()
689-
{
690-
"default": "READ_COMMITTED",
691-
"supported": [
692-
"SERIALIZABLE", "READ UNCOMMITTED",
693-
"READ COMMITTED", "REPEATABLE READ",
694-
"AUTOCOMMIT"
695-
]
696-
}
697-
"""
674+
return {
675+
"default": "READ UNCOMMITTED",
676+
"supported": [
677+
"AUTOCOMMIT",
678+
"READ UNCOMMITTED",
679+
"READ COMMITTED",
680+
"READ VERIFIED",
681+
]
682+
}
698683

699684
@property
700685
def json_type(self):
@@ -878,7 +863,7 @@ def selectone(self):
878863
def savepoints(self):
879864
"""Target database must support savepoints."""
880865

881-
return exclusions.closed()
866+
return exclusions.open()
882867

883868
@property
884869
def two_phase_transactions(self):
@@ -1125,3 +1110,17 @@ def autoincrement_without_sequence(self):
11251110
"""
11261111
return exclusions.open()
11271112
# return exclusions.closed()
1113+
1114+
#
1115+
# SQLAlchemy Tests
1116+
# pytest --dburi iris://_SYSTEM:SYS@localhost:1972/USER \
1117+
# --requirements sqlalchemy_iris.requirements:Requirements
1118+
#
1119+
1120+
@property
1121+
def memory_process_intensive(self):
1122+
return exclusions.closed()
1123+
1124+
@property
1125+
def array_type(self):
1126+
return exclusions.closed()

test/test_suite.py

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from sqlalchemy.testing.suite import QuotedNameArgumentTest as _QuotedNameArgumentTest
22
from sqlalchemy.testing.suite import FetchLimitOffsetTest as _FetchLimitOffsetTest
33
from sqlalchemy.testing.suite import CompoundSelectTest as _CompoundSelectTest
4-
from sqlalchemy.testing import fixtures, AssertsExecutionResults, AssertsCompiledSQL
4+
from sqlalchemy.testing import fixtures
5+
# from sqlalchemy.testing import AssertsExecutionResults, AssertsCompiledSQL
56
from sqlalchemy import testing
67
from sqlalchemy import Table, Column, Integer, String, select
78
import pytest
@@ -56,25 +57,74 @@ def test_simple_limit_offset_no_order(self, connection, cases):
5657
)
5758

5859

59-
class MiscTest(AssertsExecutionResults, AssertsCompiledSQL, fixtures.TablesTest):
60+
# class MiscTest(AssertsExecutionResults, AssertsCompiledSQL, fixtures.TablesTest):
6061

61-
__backend__ = True
62+
# __backend__ = True
63+
64+
# __only_on__ = "iris"
65+
66+
# @classmethod
67+
# def define_tables(cls, metadata):
68+
# Table(
69+
# "some_table",
70+
# metadata,
71+
# Column("id", Integer, primary_key=True),
72+
# Column("x", Integer),
73+
# Column("y", Integer),
74+
# Column("z", String(50)),
75+
# )
76+
77+
# # def test_compile(self):
78+
# # table = self.tables.some_table
79+
80+
# # stmt = select(table.c.id, table.c.x).offset(20).limit(10)
6281

63-
__only_on__ = "iris"
82+
83+
class TransactionTest(fixtures.TablesTest):
84+
__backend__ = True
6485

6586
@classmethod
6687
def define_tables(cls, metadata):
6788
Table(
68-
"some_table",
89+
"users",
6990
metadata,
70-
Column("id", Integer, primary_key=True),
71-
Column("x", Integer),
72-
Column("y", Integer),
73-
Column("z", String(50)),
91+
Column("user_id", Integer, primary_key=True),
92+
Column("user_name", String(20)),
93+
test_needs_acid=True,
7494
)
7595

76-
# def test_compile(self):
77-
# table = self.tables.some_table
78-
79-
# stmt = select(table.c.id, table.c.x).offset(20).limit(10)
80-
96+
@testing.fixture
97+
def local_connection(self):
98+
with testing.db.connect() as conn:
99+
yield conn
100+
101+
def test_commits(self, local_connection):
102+
users = self.tables.users
103+
connection = local_connection
104+
transaction = connection.begin()
105+
connection.execute(users.insert(), dict(user_id=1, user_name="user1"))
106+
transaction.commit()
107+
108+
transaction = connection.begin()
109+
connection.execute(users.insert(), dict(user_id=2, user_name="user2"))
110+
connection.execute(users.insert(), dict(user_id=3, user_name="user3"))
111+
transaction.commit()
112+
113+
transaction = connection.begin()
114+
result = connection.exec_driver_sql("select * from users")
115+
assert len(result.fetchall()) == 3
116+
transaction.commit()
117+
connection.close()
118+
119+
def test_rollback(self, local_connection):
120+
"""test a basic rollback"""
121+
122+
users = self.tables.users
123+
connection = local_connection
124+
transaction = connection.begin()
125+
connection.execute(users.insert(), dict(user_id=1, user_name="user1"))
126+
connection.execute(users.insert(), dict(user_id=2, user_name="user2"))
127+
connection.execute(users.insert(), dict(user_id=3, user_name="user3"))
128+
transaction.rollback()
129+
result = connection.exec_driver_sql("select * from users")
130+
assert len(result.fetchall()) == 0

0 commit comments

Comments
 (0)