|
19 | 19 | # with Crate these terms will supersede the license and you may use the
|
20 | 20 | # software solely pursuant to the terms of the relevant commercial agreement.
|
21 | 21 | from textwrap import dedent
|
22 |
| -from unittest import mock, skipIf |
| 22 | +from unittest import mock, skipIf, TestCase |
| 23 | +from unittest.mock import MagicMock, patch |
23 | 24 |
|
| 25 | +from crate.client.cursor import Cursor |
24 | 26 | from crate.client.sqlalchemy.compiler import crate_before_execute
|
25 | 27 |
|
26 | 28 | import sqlalchemy as sa
|
|
30 | 32 | from crate.client.sqlalchemy.types import ObjectType
|
31 | 33 | from crate.client.test_util import ParametrizedTestCase
|
32 | 34 |
|
| 35 | +from crate.testing.settings import crate_host |
| 36 | + |
33 | 37 |
|
34 | 38 | class SqlAlchemyCompilerTest(ParametrizedTestCase):
|
35 | 39 |
|
@@ -244,3 +248,101 @@ def test_insert_manyvalues(self):
|
244 | 248 | mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?), (?)', ('foo_2', 'foo_3'), None),
|
245 | 249 | mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?)', ('foo_4', ), None),
|
246 | 250 | ])
|
| 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