Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 640a2db

Browse filesBrowse files
committed
SQLAlchemy: Fix SQL statement caching for CrateDB's OBJECT type
The SQLAlchemy implementation of CrateDB's `OBJECT` type offers indexed access to the instance's content in form of a dictionary. Thus, it must not use `cache_ok = True` on its implementation, i.e. this part of the compiled SQL clause must not be cached. Tests: Add integration test cases verifying SA's SQL statement caching Specifically, the special types `OBJECT` and `ARRAY` are of concern here.
1 parent 4e20913 commit 640a2db
Copy full SHA for 640a2db

File tree

Expand file treeCollapse file tree

5 files changed

+132
-4
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+132
-4
lines changed

‎CHANGES.txt

Copy file name to clipboardExpand all lines: CHANGES.txt
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Unreleased
1313

1414
- Allow handling datetime values tagged with time zone info when inserting or updating.
1515

16+
- SQLAlchemy: Fix SQL statement caching for CrateDB's ``OBJECT`` type.
17+
1618

1719
2023/04/18 0.31.1
1820
=================

‎src/crate/client/sqlalchemy/tests/__init__.py

Copy file name to clipboardExpand all lines: src/crate/client/sqlalchemy/tests/__init__.py
+8-1Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
from .dialect_test import SqlAlchemyDialectTest
2424
from .function_test import SqlAlchemyFunctionTest
2525
from .warnings_test import SqlAlchemyWarningsTest
26+
from .query_caching import SqlAlchemyQueryCompilationCaching
2627

2728

28-
def test_suite():
29+
def test_suite_unit():
2930
tests = TestSuite()
3031
tests.addTest(makeSuite(SqlAlchemyConnectionTest))
3132
tests.addTest(makeSuite(SqlAlchemyDictTypeTest))
@@ -42,3 +43,9 @@ def test_suite():
4243
tests.addTest(makeSuite(SqlAlchemyArrayTypeTest))
4344
tests.addTest(makeSuite(SqlAlchemyWarningsTest))
4445
return tests
46+
47+
48+
def test_suite_integration():
49+
tests = TestSuite()
50+
tests.addTest(makeSuite(SqlAlchemyQueryCompilationCaching))
51+
return tests
+117Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# -*- coding: utf-8; -*-
2+
#
3+
# Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
4+
# license agreements. See the NOTICE file distributed with this work for
5+
# additional information regarding copyright ownership. Crate licenses
6+
# this file to you under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License. You may
8+
# obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15+
# License for the specific language governing permissions and limitations
16+
# under the License.
17+
#
18+
# However, if you have executed another commercial license agreement
19+
# with Crate these terms will supersede the license and you may use the
20+
# software solely pursuant to the terms of the relevant commercial agreement.
21+
22+
from __future__ import absolute_import
23+
from unittest import TestCase, skipIf
24+
25+
import sqlalchemy as sa
26+
from sqlalchemy.orm import Session
27+
from sqlalchemy.sql.operators import eq
28+
29+
from crate.client.sqlalchemy import SA_VERSION, SA_1_4
30+
from crate.testing.settings import crate_host
31+
32+
try:
33+
from sqlalchemy.orm import declarative_base
34+
except ImportError:
35+
from sqlalchemy.ext.declarative import declarative_base
36+
37+
from crate.client.sqlalchemy.types import Object, ObjectArray
38+
39+
40+
class SqlAlchemyQueryCompilationCaching(TestCase):
41+
42+
def setUp(self):
43+
self.engine = sa.create_engine(f"crate://{crate_host}")
44+
self.metadata = sa.MetaData(schema="testdrive")
45+
self.session = Session(bind=self.engine)
46+
self.Character = self.setup_entity()
47+
48+
def setup_entity(self):
49+
"""
50+
Define ORM entity.
51+
"""
52+
Base = declarative_base(metadata=self.metadata)
53+
54+
class Character(Base):
55+
__tablename__ = 'characters'
56+
name = sa.Column(sa.String, primary_key=True)
57+
age = sa.Column(sa.Integer)
58+
data = sa.Column(Object)
59+
data_list = sa.Column(ObjectArray)
60+
61+
return Character
62+
63+
def setup_data(self):
64+
"""
65+
Insert two records into the `characters` table.
66+
"""
67+
self.metadata.drop_all(self.engine)
68+
self.metadata.create_all(self.engine)
69+
70+
Character = self.Character
71+
char1 = Character(name='Trillian', data={'x': 1}, data_list=[{'foo': 1, 'bar': 10}])
72+
char2 = Character(name='Slartibartfast', data={'y': 2}, data_list=[{'bar': 2}])
73+
self.session.add(char1)
74+
self.session.add(char2)
75+
self.session.commit()
76+
self.session.execute(sa.text("REFRESH TABLE testdrive.characters;"))
77+
78+
@skipIf(SA_VERSION < SA_1_4, "On SA13, the 'ResultProxy' object has no attribute 'scalar_one'")
79+
def test_object_multiple_select(self):
80+
"""
81+
The SQLAlchemy implementation of CrateDB's `OBJECT` type offers indexed
82+
access to the instance's content in form of a dictionary. Thus, it must
83+
not use `cache_ok = True` on its implementation, i.e. this part of the
84+
compiled SQL clause must not be cached.
85+
86+
This test verifies that two subsequent `SELECT` statements are translated
87+
well, and don't trip on incorrect SQL compiled statement caching.
88+
"""
89+
self.setup_data()
90+
Character = self.Character
91+
92+
selectable = sa.select(Character).where(Character.data['x'] == 1)
93+
result = self.session.execute(selectable).scalar_one().data
94+
self.assertEqual({"x": 1}, result)
95+
96+
selectable = sa.select(Character).where(Character.data['y'] == 2)
97+
result = self.session.execute(selectable).scalar_one().data
98+
self.assertEqual({"y": 2}, result)
99+
100+
@skipIf(SA_VERSION < SA_1_4, "On SA13, the 'ResultProxy' object has no attribute 'scalar_one'")
101+
def test_objectarray_multiple_select(self):
102+
"""
103+
The SQLAlchemy implementation of CrateDB's `ARRAY` type in form of the
104+
`ObjectArray`, does *not* offer indexed access to the instance's content.
105+
Thus, using `cache_ok = True` on that type should be sane, and not mess
106+
up SQLAlchemy's SQL compiled statement caching.
107+
"""
108+
self.setup_data()
109+
Character = self.Character
110+
111+
selectable = sa.select(Character).where(Character.data_list['foo'].any(1, operator=eq))
112+
result = self.session.execute(selectable).scalar_one().data
113+
self.assertEqual({"x": 1}, result)
114+
115+
selectable = sa.select(Character).where(Character.data_list['bar'].any(2, operator=eq))
116+
result = self.session.execute(selectable).scalar_one().data
117+
self.assertEqual({"y": 2}, result)

‎src/crate/client/sqlalchemy/types.py

Copy file name to clipboardExpand all lines: src/crate/client/sqlalchemy/types.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def __eq__(self, other):
132132

133133

134134
class _Craty(sqltypes.UserDefinedType):
135-
cache_ok = True
135+
cache_ok = False
136136

137137
class Comparator(sqltypes.TypeEngine.Comparator):
138138

‎src/crate/client/tests.py

Copy file name to clipboardExpand all lines: src/crate/client/tests.py
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
TestCrateJsonEncoder,
5555
TestDefaultSchemaHeader,
5656
)
57-
from .sqlalchemy.tests import test_suite as sqlalchemy_test_suite
57+
from .sqlalchemy.tests import test_suite_unit as sqlalchemy_test_suite_unit
58+
from .sqlalchemy.tests import test_suite_integration as sqlalchemy_test_suite_integration
5859

5960
log = logging.getLogger('crate.testing.layer')
6061
ch = logging.StreamHandler()
@@ -344,7 +345,7 @@ def test_suite():
344345
suite.addTest(unittest.makeSuite(TestUsernameSentAsHeader))
345346
suite.addTest(unittest.makeSuite(TestCrateJsonEncoder))
346347
suite.addTest(unittest.makeSuite(TestDefaultSchemaHeader))
347-
suite.addTest(sqlalchemy_test_suite())
348+
suite.addTest(sqlalchemy_test_suite_unit())
348349
suite.addTest(doctest.DocTestSuite('crate.client.connection'))
349350
suite.addTest(doctest.DocTestSuite('crate.client.http'))
350351

@@ -394,6 +395,7 @@ def test_suite():
394395
encoding='utf-8'
395396
)
396397
s.layer = ensure_cratedb_layer()
398+
s.addTest(sqlalchemy_test_suite_integration())
397399
suite.addTest(s)
398400

399401
return suite

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.