Skip to content

Commit 2f8e185

Browse files
committed
SQLAlchemy: Improve DDL compiler to ignore foreign key constraints
1 parent c276159 commit 2f8e185

File tree

4 files changed

+111
-2
lines changed

4 files changed

+111
-2
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Unreleased
1313
certificate details are immanent, like no longer accepting the long
1414
deprecated ``commonName`` attribute. Instead, going forward, only the
1515
``subjectAltName`` attribute will be used.
16+
- SQLAlchemy: Improve DDL compiler to ignore foreign key constraints
1617

1718
.. _urllib3 v2.0 migration guide: https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html
1819
.. _urllib3 v2.0 roadmap: https://urllib3.readthedocs.io/en/stable/v2-roadmap.html

src/crate/client/sqlalchemy/compiler.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ def post_create_table(self, table):
178178
', '.join(sorted(table_opts)))
179179
return special_options
180180

181+
def visit_foreign_key_constraint(self, constraint, **kw):
182+
"""
183+
CrateDB does not support foreign key constraints.
184+
"""
185+
return None
181186

182187
class CrateTypeCompiler(compiler.GenericTypeCompiler):
183188

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .connection_test import SqlAlchemyConnectionTest
1515
from .dict_test import SqlAlchemyDictTypeTest
1616
from .datetime_test import SqlAlchemyDateAndDateTimeTest
17-
from .compiler_test import SqlAlchemyCompilerTest
17+
from .compiler_test import SqlAlchemyCompilerTest, SqlAlchemyDDLCompilerTest
1818
from .update_test import SqlAlchemyUpdateTest
1919
from .match_test import SqlAlchemyMatchTest
2020
from .bulk_test import SqlAlchemyBulkTest
@@ -36,6 +36,7 @@ def test_suite_unit():
3636
tests.addTest(makeSuite(SqlAlchemyDictTypeTest))
3737
tests.addTest(makeSuite(SqlAlchemyDateAndDateTimeTest))
3838
tests.addTest(makeSuite(SqlAlchemyCompilerTest))
39+
tests.addTest(makeSuite(SqlAlchemyDDLCompilerTest))
3940
tests.addTest(ParametrizedTestCase.parametrize(SqlAlchemyCompilerTest, param={"server_version_info": None}))
4041
tests.addTest(ParametrizedTestCase.parametrize(SqlAlchemyCompilerTest, param={"server_version_info": (4, 0, 12)}))
4142
tests.addTest(ParametrizedTestCase.parametrize(SqlAlchemyCompilerTest, param={"server_version_info": (4, 1, 10)}))

src/crate/client/sqlalchemy/tests/compiler_test.py

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
# with Crate these terms will supersede the license and you may use the
2020
# software solely pursuant to the terms of the relevant commercial agreement.
2121
from textwrap import dedent
22-
from unittest import mock, skipIf
22+
from unittest import mock, skipIf, TestCase
23+
from unittest.mock import MagicMock, patch
2324

25+
from crate.client.cursor import Cursor
2426
from crate.client.sqlalchemy.compiler import crate_before_execute
2527

2628
import sqlalchemy as sa
@@ -30,6 +32,8 @@
3032
from crate.client.sqlalchemy.types import ObjectType
3133
from crate.client.test_util import ParametrizedTestCase
3234

35+
from crate.testing.settings import crate_host
36+
3337

3438
class SqlAlchemyCompilerTest(ParametrizedTestCase):
3539

@@ -244,3 +248,101 @@ def test_insert_manyvalues(self):
244248
mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?), (?)', ('foo_2', 'foo_3'), None),
245249
mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?)', ('foo_4', ), None),
246250
])
251+
252+
253+
FakeCursor = MagicMock(name='FakeCursor', spec=Cursor)
254+
255+
256+
class CompilerTestCase(TestCase):
257+
"""
258+
A base class for providing mocking infrastructure to validate the DDL compiler.
259+
"""
260+
261+
def setUp(self):
262+
self.engine = sa.create_engine(f"crate://{crate_host}")
263+
self.metadata = sa.MetaData(schema="testdrive")
264+
self.session = sa.orm.Session(bind=self.engine)
265+
self.setup_mock()
266+
267+
def setup_mock(self):
268+
"""
269+
Set up a fake cursor, in order to intercept query execution.
270+
"""
271+
272+
self.fake_cursor = MagicMock(name="fake_cursor")
273+
FakeCursor.return_value = self.fake_cursor
274+
275+
self.executed_statement = None
276+
self.fake_cursor.execute = self.execute_wrapper
277+
278+
def execute_wrapper(self, query, *args, **kwargs):
279+
"""
280+
Receive the SQL query expression, and store it.
281+
"""
282+
self.executed_statement = query
283+
return self.fake_cursor
284+
285+
286+
@patch('crate.client.connection.Cursor', FakeCursor)
287+
class SqlAlchemyDDLCompilerTest(CompilerTestCase):
288+
"""
289+
Verify a few scenarios regarding the DDL compiler.
290+
"""
291+
292+
def test_ddl_with_foreign_keys(self):
293+
"""
294+
Verify the CrateDB dialect properly ignores foreign key constraints.
295+
"""
296+
297+
Base = sa.orm.declarative_base(metadata=self.metadata)
298+
299+
class RootStore(Base):
300+
"""The main store."""
301+
302+
__tablename__ = "root"
303+
304+
id = sa.Column(sa.Integer, primary_key=True)
305+
name = sa.Column(sa.String)
306+
307+
items = sa.orm.relationship(
308+
"ItemStore",
309+
back_populates="root",
310+
passive_deletes=True,
311+
)
312+
313+
class ItemStore(Base):
314+
"""The auxiliary store."""
315+
316+
__tablename__ = "item"
317+
318+
id = sa.Column(sa.Integer, primary_key=True)
319+
name = sa.Column(sa.String)
320+
root_id = sa.Column(
321+
sa.Integer,
322+
sa.ForeignKey(
323+
f"{RootStore.__tablename__}.id",
324+
ondelete="CASCADE",
325+
),
326+
)
327+
root = sa.orm.relationship(RootStore, back_populates="items")
328+
329+
self.metadata.create_all(self.engine, tables=[RootStore.__table__], checkfirst=False)
330+
self.assertEqual(self.executed_statement, dedent("""
331+
CREATE TABLE testdrive.root (
332+
\tid INT NOT NULL,
333+
\tname STRING,
334+
\tPRIMARY KEY (id)
335+
)
336+
337+
""")) # noqa: W291
338+
339+
self.metadata.create_all(self.engine, tables=[ItemStore.__table__], checkfirst=False)
340+
self.assertEqual(self.executed_statement, dedent("""
341+
CREATE TABLE testdrive.item (
342+
\tid INT NOT NULL,
343+
\tname STRING,
344+
\troot_id INT,
345+
\tPRIMARY KEY (id)
346+
)
347+
348+
""")) # noqa: W291, W293

0 commit comments

Comments
 (0)