crate 0.35.2__py2.py3-none-any.whl → 1.0.0.dev0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crate/client/__init__.py +1 -1
- crate/testing/test_datetime_old.py +90 -0
- crate-1.0.0.dev0-py3.11-nspkg.pth +1 -0
- {crate-0.35.2.dist-info → crate-1.0.0.dev0.dist-info}/METADATA +15 -19
- crate-1.0.0.dev0.dist-info/RECORD +26 -0
- {crate-0.35.2.dist-info → crate-1.0.0.dev0.dist-info}/WHEEL +1 -1
- crate/client/sqlalchemy/__init__.py +0 -50
- crate/client/sqlalchemy/compat/__init__.py +0 -0
- crate/client/sqlalchemy/compat/api13.py +0 -156
- crate/client/sqlalchemy/compat/core10.py +0 -264
- crate/client/sqlalchemy/compat/core14.py +0 -359
- crate/client/sqlalchemy/compat/core20.py +0 -447
- crate/client/sqlalchemy/compiler.py +0 -318
- crate/client/sqlalchemy/dialect.py +0 -369
- crate/client/sqlalchemy/predicates/__init__.py +0 -99
- crate/client/sqlalchemy/sa_version.py +0 -28
- crate/client/sqlalchemy/support.py +0 -62
- crate/client/sqlalchemy/tests/__init__.py +0 -59
- crate/client/sqlalchemy/tests/array_test.py +0 -111
- crate/client/sqlalchemy/tests/bulk_test.py +0 -256
- crate/client/sqlalchemy/tests/compiler_test.py +0 -434
- crate/client/sqlalchemy/tests/connection_test.py +0 -129
- crate/client/sqlalchemy/tests/create_table_test.py +0 -313
- crate/client/sqlalchemy/tests/datetime_test.py +0 -90
- crate/client/sqlalchemy/tests/dialect_test.py +0 -156
- crate/client/sqlalchemy/tests/dict_test.py +0 -460
- crate/client/sqlalchemy/tests/function_test.py +0 -47
- crate/client/sqlalchemy/tests/insert_from_select_test.py +0 -85
- crate/client/sqlalchemy/tests/match_test.py +0 -137
- crate/client/sqlalchemy/tests/query_caching.py +0 -143
- crate/client/sqlalchemy/tests/update_test.py +0 -115
- crate/client/sqlalchemy/tests/warnings_test.py +0 -64
- crate/client/sqlalchemy/types.py +0 -277
- crate/client/tests.py +0 -416
- crate/testing/tests.py +0 -34
- crate-0.35.2-py3.11-nspkg.pth +0 -1
- crate-0.35.2.dist-info/RECORD +0 -55
- crate-0.35.2.dist-info/entry_points.txt +0 -2
- {crate-0.35.2.dist-info → crate-1.0.0.dev0.dist-info}/LICENSE +0 -0
- {crate-0.35.2.dist-info → crate-1.0.0.dev0.dist-info}/NOTICE +0 -0
- {crate-0.35.2.dist-info → crate-1.0.0.dev0.dist-info}/namespace_packages.txt +0 -0
- {crate-0.35.2.dist-info → crate-1.0.0.dev0.dist-info}/top_level.txt +0 -0
@@ -1,434 +0,0 @@
|
|
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
|
-
import warnings
|
22
|
-
from textwrap import dedent
|
23
|
-
from unittest import mock, skipIf, TestCase
|
24
|
-
from unittest.mock import MagicMock, patch
|
25
|
-
|
26
|
-
from crate.client.cursor import Cursor
|
27
|
-
from crate.client.sqlalchemy.compiler import crate_before_execute
|
28
|
-
|
29
|
-
import sqlalchemy as sa
|
30
|
-
from sqlalchemy.sql import text, Update
|
31
|
-
|
32
|
-
from crate.testing.util import ExtraAssertions
|
33
|
-
|
34
|
-
try:
|
35
|
-
from sqlalchemy.orm import declarative_base
|
36
|
-
except ImportError:
|
37
|
-
from sqlalchemy.ext.declarative import declarative_base
|
38
|
-
|
39
|
-
from crate.client.sqlalchemy.sa_version import SA_VERSION, SA_1_4, SA_2_0
|
40
|
-
from crate.client.sqlalchemy.types import ObjectType
|
41
|
-
from crate.client.test_util import ParametrizedTestCase
|
42
|
-
|
43
|
-
from crate.testing.settings import crate_host
|
44
|
-
|
45
|
-
|
46
|
-
class SqlAlchemyCompilerTest(ParametrizedTestCase, ExtraAssertions):
|
47
|
-
|
48
|
-
def setUp(self):
|
49
|
-
self.crate_engine = sa.create_engine('crate://')
|
50
|
-
if isinstance(self.param, dict) and "server_version_info" in self.param:
|
51
|
-
server_version_info = self.param["server_version_info"]
|
52
|
-
self.crate_engine.dialect.server_version_info = server_version_info
|
53
|
-
self.sqlite_engine = sa.create_engine('sqlite://')
|
54
|
-
self.metadata = sa.MetaData()
|
55
|
-
self.mytable = sa.Table('mytable', self.metadata,
|
56
|
-
sa.Column('name', sa.String),
|
57
|
-
sa.Column('data', ObjectType))
|
58
|
-
|
59
|
-
self.update = Update(self.mytable).where(text('name=:name'))
|
60
|
-
self.values = [{'name': 'crate'}]
|
61
|
-
self.values = (self.values, )
|
62
|
-
|
63
|
-
def test_sqlite_update_not_rewritten(self):
|
64
|
-
clauseelement, multiparams, params = crate_before_execute(
|
65
|
-
self.sqlite_engine, self.update, self.values, {}
|
66
|
-
)
|
67
|
-
|
68
|
-
self.assertFalse(hasattr(clauseelement, '_crate_specific'))
|
69
|
-
|
70
|
-
def test_crate_update_rewritten(self):
|
71
|
-
clauseelement, multiparams, params = crate_before_execute(
|
72
|
-
self.crate_engine, self.update, self.values, {}
|
73
|
-
)
|
74
|
-
|
75
|
-
self.assertTrue(hasattr(clauseelement, '_crate_specific'))
|
76
|
-
|
77
|
-
def test_bulk_update_on_builtin_type(self):
|
78
|
-
"""
|
79
|
-
The "before_execute" hook in the compiler doesn't get
|
80
|
-
access to the parameters in case of a bulk update. It
|
81
|
-
should not try to optimize any parameters.
|
82
|
-
"""
|
83
|
-
data = ({},)
|
84
|
-
clauseelement, multiparams, params = crate_before_execute(
|
85
|
-
self.crate_engine, self.update, data, None
|
86
|
-
)
|
87
|
-
|
88
|
-
self.assertFalse(hasattr(clauseelement, '_crate_specific'))
|
89
|
-
|
90
|
-
def test_select_with_ilike_no_escape(self):
|
91
|
-
"""
|
92
|
-
Verify the compiler uses CrateDB's native `ILIKE` method.
|
93
|
-
"""
|
94
|
-
selectable = self.mytable.select().where(self.mytable.c.name.ilike("%foo%"))
|
95
|
-
statement = str(selectable.compile(bind=self.crate_engine))
|
96
|
-
if self.crate_engine.dialect.has_ilike_operator():
|
97
|
-
self.assertEqual(statement, dedent("""
|
98
|
-
SELECT mytable.name, mytable.data
|
99
|
-
FROM mytable
|
100
|
-
WHERE mytable.name ILIKE ?
|
101
|
-
""").strip()) # noqa: W291
|
102
|
-
else:
|
103
|
-
self.assertEqual(statement, dedent("""
|
104
|
-
SELECT mytable.name, mytable.data
|
105
|
-
FROM mytable
|
106
|
-
WHERE lower(mytable.name) LIKE lower(?)
|
107
|
-
""").strip()) # noqa: W291
|
108
|
-
|
109
|
-
def test_select_with_not_ilike_no_escape(self):
|
110
|
-
"""
|
111
|
-
Verify the compiler uses CrateDB's native `ILIKE` method.
|
112
|
-
"""
|
113
|
-
selectable = self.mytable.select().where(self.mytable.c.name.notilike("%foo%"))
|
114
|
-
statement = str(selectable.compile(bind=self.crate_engine))
|
115
|
-
if SA_VERSION < SA_1_4 or not self.crate_engine.dialect.has_ilike_operator():
|
116
|
-
self.assertEqual(statement, dedent("""
|
117
|
-
SELECT mytable.name, mytable.data
|
118
|
-
FROM mytable
|
119
|
-
WHERE lower(mytable.name) NOT LIKE lower(?)
|
120
|
-
""").strip()) # noqa: W291
|
121
|
-
else:
|
122
|
-
self.assertEqual(statement, dedent("""
|
123
|
-
SELECT mytable.name, mytable.data
|
124
|
-
FROM mytable
|
125
|
-
WHERE mytable.name NOT ILIKE ?
|
126
|
-
""").strip()) # noqa: W291
|
127
|
-
|
128
|
-
def test_select_with_ilike_and_escape(self):
|
129
|
-
"""
|
130
|
-
Verify the compiler fails when using CrateDB's native `ILIKE` method together with `ESCAPE`.
|
131
|
-
"""
|
132
|
-
|
133
|
-
selectable = self.mytable.select().where(self.mytable.c.name.ilike("%foo%", escape='\\'))
|
134
|
-
with self.assertRaises(NotImplementedError) as cmex:
|
135
|
-
selectable.compile(bind=self.crate_engine)
|
136
|
-
self.assertEqual(str(cmex.exception), "Unsupported feature: ESCAPE is not supported")
|
137
|
-
|
138
|
-
@skipIf(SA_VERSION < SA_1_4, "SQLAlchemy 1.3 and earlier do not support native `NOT ILIKE` compilation")
|
139
|
-
def test_select_with_not_ilike_and_escape(self):
|
140
|
-
"""
|
141
|
-
Verify the compiler fails when using CrateDB's native `ILIKE` method together with `ESCAPE`.
|
142
|
-
"""
|
143
|
-
|
144
|
-
selectable = self.mytable.select().where(self.mytable.c.name.notilike("%foo%", escape='\\'))
|
145
|
-
with self.assertRaises(NotImplementedError) as cmex:
|
146
|
-
selectable.compile(bind=self.crate_engine)
|
147
|
-
self.assertEqual(str(cmex.exception), "Unsupported feature: ESCAPE is not supported")
|
148
|
-
|
149
|
-
def test_select_with_offset(self):
|
150
|
-
"""
|
151
|
-
Verify the `CrateCompiler.limit_clause` method, with offset.
|
152
|
-
"""
|
153
|
-
selectable = self.mytable.select().offset(5)
|
154
|
-
statement = str(selectable.compile(bind=self.crate_engine))
|
155
|
-
if SA_VERSION >= SA_1_4:
|
156
|
-
self.assertEqual(statement, "SELECT mytable.name, mytable.data \nFROM mytable\n LIMIT ALL OFFSET ?")
|
157
|
-
else:
|
158
|
-
self.assertEqual(statement, "SELECT mytable.name, mytable.data \nFROM mytable \n LIMIT ALL OFFSET ?")
|
159
|
-
|
160
|
-
def test_select_with_limit(self):
|
161
|
-
"""
|
162
|
-
Verify the `CrateCompiler.limit_clause` method, with limit.
|
163
|
-
"""
|
164
|
-
selectable = self.mytable.select().limit(42)
|
165
|
-
statement = str(selectable.compile(bind=self.crate_engine))
|
166
|
-
self.assertEqual(statement, "SELECT mytable.name, mytable.data \nFROM mytable \n LIMIT ?")
|
167
|
-
|
168
|
-
def test_select_with_offset_and_limit(self):
|
169
|
-
"""
|
170
|
-
Verify the `CrateCompiler.limit_clause` method, with offset and limit.
|
171
|
-
"""
|
172
|
-
selectable = self.mytable.select().offset(5).limit(42)
|
173
|
-
statement = str(selectable.compile(bind=self.crate_engine))
|
174
|
-
self.assertEqual(statement, "SELECT mytable.name, mytable.data \nFROM mytable \n LIMIT ? OFFSET ?")
|
175
|
-
|
176
|
-
def test_insert_multivalues(self):
|
177
|
-
"""
|
178
|
-
Verify that "in-place multirow inserts" aka. "multivalues inserts" aka.
|
179
|
-
the `supports_multivalues_insert` dialect feature works.
|
180
|
-
|
181
|
-
When this feature is not enabled, using it will raise an error:
|
182
|
-
|
183
|
-
CompileError: The 'crate' dialect with current database version
|
184
|
-
settings does not support in-place multirow inserts
|
185
|
-
|
186
|
-
> The Insert construct also supports being passed a list of dictionaries
|
187
|
-
> or full-table-tuples, which on the server will render the less common
|
188
|
-
> SQL syntax of "multiple values" - this syntax is supported on backends
|
189
|
-
> such as SQLite, PostgreSQL, MySQL, but not necessarily others.
|
190
|
-
|
191
|
-
> It is essential to note that passing multiple values is NOT the same
|
192
|
-
> as using traditional `executemany()` form. The above syntax is a special
|
193
|
-
> syntax not typically used. To emit an INSERT statement against
|
194
|
-
> multiple rows, the normal method is to pass a multiple values list to
|
195
|
-
> the `Connection.execute()` method, which is supported by all database
|
196
|
-
> backends and is generally more efficient for a very large number of
|
197
|
-
> parameters.
|
198
|
-
|
199
|
-
- https://docs.sqlalchemy.org/core/dml.html#sqlalchemy.sql.expression.Insert.values.params.*args
|
200
|
-
"""
|
201
|
-
records = [{"name": f"foo_{i}"} for i in range(3)]
|
202
|
-
insertable = self.mytable.insert().values(records)
|
203
|
-
statement = str(insertable.compile(bind=self.crate_engine))
|
204
|
-
self.assertEqual(statement, "INSERT INTO mytable (name) VALUES (?), (?), (?)")
|
205
|
-
|
206
|
-
@skipIf(SA_VERSION < SA_2_0, "SQLAlchemy 1.x does not support the 'insertmanyvalues' dialect feature")
|
207
|
-
def test_insert_manyvalues(self):
|
208
|
-
"""
|
209
|
-
Verify the `use_insertmanyvalues` and `use_insertmanyvalues_wo_returning` dialect features.
|
210
|
-
|
211
|
-
> For DML statements such as "INSERT", "UPDATE" and "DELETE", we can
|
212
|
-
> send multiple parameter sets to the `Connection.execute()` method by
|
213
|
-
> passing a list of dictionaries instead of a single dictionary, which
|
214
|
-
> indicates that the single SQL statement should be invoked multiple
|
215
|
-
> times, once for each parameter set. This style of execution is known
|
216
|
-
> as "executemany".
|
217
|
-
|
218
|
-
> A key characteristic of "insertmanyvalues" is that the size of the INSERT
|
219
|
-
> statement is limited on a fixed max number of "values" clauses as well as
|
220
|
-
> a dialect-specific fixed total number of bound parameters that may be
|
221
|
-
> represented in one INSERT statement at a time.
|
222
|
-
> When the number of parameter dictionaries given exceeds a fixed limit [...],
|
223
|
-
> multiple INSERT statements will be invoked within the scope of a single
|
224
|
-
> `Connection.execute()` call, each of which accommodate for a portion of the
|
225
|
-
> parameter dictionaries, referred towards as a "batch".
|
226
|
-
|
227
|
-
- https://docs.sqlalchemy.org/tutorial/dbapi_transactions.html#tutorial-multiple-parameters
|
228
|
-
- https://docs.sqlalchemy.org/glossary.html#term-executemany
|
229
|
-
- https://docs.sqlalchemy.org/core/connections.html#engine-insertmanyvalues
|
230
|
-
- https://docs.sqlalchemy.org/core/connections.html#controlling-the-batch-size
|
231
|
-
"""
|
232
|
-
|
233
|
-
# Don't truncate unittest's diff output on `assertListEqual`.
|
234
|
-
self.maxDiff = None
|
235
|
-
|
236
|
-
# Five records with a batch size of two should produce three `INSERT` statements.
|
237
|
-
record_count = 5
|
238
|
-
batch_size = 2
|
239
|
-
|
240
|
-
# Prepare input data and verify insert statement.
|
241
|
-
records = [{"name": f"foo_{i}"} for i in range(record_count)]
|
242
|
-
insertable = self.mytable.insert()
|
243
|
-
statement = str(insertable.compile(bind=self.crate_engine))
|
244
|
-
self.assertEqual(statement, "INSERT INTO mytable (name, data) VALUES (?, ?)")
|
245
|
-
|
246
|
-
with mock.patch("crate.client.http.Client.sql", autospec=True, return_value={"cols": []}) as client_mock:
|
247
|
-
|
248
|
-
with self.crate_engine.begin() as conn:
|
249
|
-
# Adjust page size on a per-connection level.
|
250
|
-
conn.execution_options(insertmanyvalues_page_size=batch_size)
|
251
|
-
conn.execute(insertable, parameters=records)
|
252
|
-
|
253
|
-
# Verify that input data has been batched correctly.
|
254
|
-
self.assertListEqual(client_mock.mock_calls, [
|
255
|
-
mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?), (?)', ('foo_0', 'foo_1'), None),
|
256
|
-
mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?), (?)', ('foo_2', 'foo_3'), None),
|
257
|
-
mock.call(mock.ANY, 'INSERT INTO mytable (name) VALUES (?)', ('foo_4', ), None),
|
258
|
-
])
|
259
|
-
|
260
|
-
def test_for_update(self):
|
261
|
-
"""
|
262
|
-
Verify the `CrateCompiler.for_update_clause` method to
|
263
|
-
omit the clause, since CrateDB does not support it.
|
264
|
-
"""
|
265
|
-
|
266
|
-
with warnings.catch_warnings(record=True) as w:
|
267
|
-
|
268
|
-
# By default, warnings from a loop will only be emitted once.
|
269
|
-
# This scenario tests exactly this behaviour, to verify logs
|
270
|
-
# don't get flooded.
|
271
|
-
warnings.simplefilter("once")
|
272
|
-
|
273
|
-
selectable = self.mytable.select().with_for_update()
|
274
|
-
_ = str(selectable.compile(bind=self.crate_engine))
|
275
|
-
|
276
|
-
selectable = self.mytable.select().with_for_update()
|
277
|
-
statement = str(selectable.compile(bind=self.crate_engine))
|
278
|
-
|
279
|
-
# Verify SQL statement.
|
280
|
-
self.assertEqual(statement, "SELECT mytable.name, mytable.data \nFROM mytable")
|
281
|
-
|
282
|
-
# Verify if corresponding warning is emitted, once.
|
283
|
-
self.assertEqual(len(w), 1)
|
284
|
-
self.assertIsSubclass(w[-1].category, UserWarning)
|
285
|
-
self.assertIn("CrateDB does not support the 'INSERT ... FOR UPDATE' clause, "
|
286
|
-
"it will be omitted when generating SQL statements.", str(w[-1].message))
|
287
|
-
|
288
|
-
|
289
|
-
FakeCursor = MagicMock(name='FakeCursor', spec=Cursor)
|
290
|
-
|
291
|
-
|
292
|
-
class CompilerTestCase(TestCase):
|
293
|
-
"""
|
294
|
-
A base class for providing mocking infrastructure to validate the DDL compiler.
|
295
|
-
"""
|
296
|
-
|
297
|
-
def setUp(self):
|
298
|
-
self.engine = sa.create_engine(f"crate://{crate_host}")
|
299
|
-
self.metadata = sa.MetaData(schema="testdrive")
|
300
|
-
self.session = sa.orm.Session(bind=self.engine)
|
301
|
-
self.setup_mock()
|
302
|
-
|
303
|
-
def setup_mock(self):
|
304
|
-
"""
|
305
|
-
Set up a fake cursor, in order to intercept query execution.
|
306
|
-
"""
|
307
|
-
|
308
|
-
self.fake_cursor = MagicMock(name="fake_cursor")
|
309
|
-
FakeCursor.return_value = self.fake_cursor
|
310
|
-
|
311
|
-
self.executed_statement = None
|
312
|
-
self.fake_cursor.execute = self.execute_wrapper
|
313
|
-
|
314
|
-
def execute_wrapper(self, query, *args, **kwargs):
|
315
|
-
"""
|
316
|
-
Receive the SQL query expression, and store it.
|
317
|
-
"""
|
318
|
-
self.executed_statement = query
|
319
|
-
return self.fake_cursor
|
320
|
-
|
321
|
-
|
322
|
-
@patch('crate.client.connection.Cursor', FakeCursor)
|
323
|
-
class SqlAlchemyDDLCompilerTest(CompilerTestCase, ExtraAssertions):
|
324
|
-
"""
|
325
|
-
Verify a few scenarios regarding the DDL compiler.
|
326
|
-
"""
|
327
|
-
|
328
|
-
def test_ddl_with_foreign_keys(self):
|
329
|
-
"""
|
330
|
-
Verify the CrateDB dialect properly ignores foreign key constraints.
|
331
|
-
"""
|
332
|
-
|
333
|
-
Base = declarative_base(metadata=self.metadata)
|
334
|
-
|
335
|
-
class RootStore(Base):
|
336
|
-
"""The main store."""
|
337
|
-
|
338
|
-
__tablename__ = "root"
|
339
|
-
|
340
|
-
id = sa.Column(sa.Integer, primary_key=True)
|
341
|
-
name = sa.Column(sa.String)
|
342
|
-
|
343
|
-
items = sa.orm.relationship(
|
344
|
-
"ItemStore",
|
345
|
-
back_populates="root",
|
346
|
-
passive_deletes=True,
|
347
|
-
)
|
348
|
-
|
349
|
-
class ItemStore(Base):
|
350
|
-
"""The auxiliary store."""
|
351
|
-
|
352
|
-
__tablename__ = "item"
|
353
|
-
|
354
|
-
id = sa.Column(sa.Integer, primary_key=True)
|
355
|
-
name = sa.Column(sa.String)
|
356
|
-
root_id = sa.Column(
|
357
|
-
sa.Integer,
|
358
|
-
sa.ForeignKey(
|
359
|
-
f"{RootStore.__tablename__}.id",
|
360
|
-
ondelete="CASCADE",
|
361
|
-
),
|
362
|
-
)
|
363
|
-
root = sa.orm.relationship(RootStore, back_populates="items")
|
364
|
-
|
365
|
-
with warnings.catch_warnings(record=True) as w:
|
366
|
-
|
367
|
-
# Cause all warnings to always be triggered.
|
368
|
-
warnings.simplefilter("always")
|
369
|
-
|
370
|
-
# Verify SQL DDL statement.
|
371
|
-
self.metadata.create_all(self.engine, tables=[RootStore.__table__], checkfirst=False)
|
372
|
-
self.assertEqual(self.executed_statement, dedent("""
|
373
|
-
CREATE TABLE testdrive.root (
|
374
|
-
\tid INT NOT NULL,
|
375
|
-
\tname STRING,
|
376
|
-
\tPRIMARY KEY (id)
|
377
|
-
)
|
378
|
-
|
379
|
-
""")) # noqa: W291, W293
|
380
|
-
|
381
|
-
# Verify SQL DDL statement.
|
382
|
-
self.metadata.create_all(self.engine, tables=[ItemStore.__table__], checkfirst=False)
|
383
|
-
self.assertEqual(self.executed_statement, dedent("""
|
384
|
-
CREATE TABLE testdrive.item (
|
385
|
-
\tid INT NOT NULL,
|
386
|
-
\tname STRING,
|
387
|
-
\troot_id INT,
|
388
|
-
\tPRIMARY KEY (id)
|
389
|
-
)
|
390
|
-
|
391
|
-
""")) # noqa: W291, W293
|
392
|
-
|
393
|
-
# Verify if corresponding warning is emitted.
|
394
|
-
self.assertEqual(len(w), 1)
|
395
|
-
self.assertIsSubclass(w[-1].category, UserWarning)
|
396
|
-
self.assertIn("CrateDB does not support foreign key constraints, "
|
397
|
-
"they will be omitted when generating DDL statements.", str(w[-1].message))
|
398
|
-
|
399
|
-
def test_ddl_with_unique_key(self):
|
400
|
-
"""
|
401
|
-
Verify the CrateDB dialect properly ignores unique key constraints.
|
402
|
-
"""
|
403
|
-
|
404
|
-
Base = declarative_base(metadata=self.metadata)
|
405
|
-
|
406
|
-
class FooBar(Base):
|
407
|
-
"""The entity."""
|
408
|
-
|
409
|
-
__tablename__ = "foobar"
|
410
|
-
|
411
|
-
id = sa.Column(sa.Integer, primary_key=True)
|
412
|
-
name = sa.Column(sa.String, unique=True)
|
413
|
-
|
414
|
-
with warnings.catch_warnings(record=True) as w:
|
415
|
-
|
416
|
-
# Cause all warnings to always be triggered.
|
417
|
-
warnings.simplefilter("always")
|
418
|
-
|
419
|
-
# Verify SQL DDL statement.
|
420
|
-
self.metadata.create_all(self.engine, tables=[FooBar.__table__], checkfirst=False)
|
421
|
-
self.assertEqual(self.executed_statement, dedent("""
|
422
|
-
CREATE TABLE testdrive.foobar (
|
423
|
-
\tid INT NOT NULL,
|
424
|
-
\tname STRING,
|
425
|
-
\tPRIMARY KEY (id)
|
426
|
-
)
|
427
|
-
|
428
|
-
""")) # noqa: W291, W293
|
429
|
-
|
430
|
-
# Verify if corresponding warning is emitted.
|
431
|
-
self.assertEqual(len(w), 1)
|
432
|
-
self.assertIsSubclass(w[-1].category, UserWarning)
|
433
|
-
self.assertIn("CrateDB does not support unique constraints, "
|
434
|
-
"they will be omitted when generating DDL statements.", str(w[-1].message))
|
@@ -1,129 +0,0 @@
|
|
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 unittest import TestCase
|
23
|
-
import sqlalchemy as sa
|
24
|
-
from sqlalchemy.exc import NoSuchModuleError
|
25
|
-
|
26
|
-
|
27
|
-
class SqlAlchemyConnectionTest(TestCase):
|
28
|
-
|
29
|
-
def test_connection_server_uri_unknown_sa_plugin(self):
|
30
|
-
with self.assertRaises(NoSuchModuleError):
|
31
|
-
sa.create_engine("foobar://otherhost:19201")
|
32
|
-
|
33
|
-
def test_default_connection(self):
|
34
|
-
engine = sa.create_engine('crate://')
|
35
|
-
conn = engine.raw_connection()
|
36
|
-
self.assertEqual("<Connection <Client ['http://127.0.0.1:4200']>>",
|
37
|
-
repr(conn.driver_connection))
|
38
|
-
conn.close()
|
39
|
-
engine.dispose()
|
40
|
-
|
41
|
-
def test_connection_server_uri_http(self):
|
42
|
-
engine = sa.create_engine(
|
43
|
-
"crate://otherhost:19201")
|
44
|
-
conn = engine.raw_connection()
|
45
|
-
self.assertEqual("<Connection <Client ['http://otherhost:19201']>>",
|
46
|
-
repr(conn.driver_connection))
|
47
|
-
conn.close()
|
48
|
-
engine.dispose()
|
49
|
-
|
50
|
-
def test_connection_server_uri_https(self):
|
51
|
-
engine = sa.create_engine(
|
52
|
-
"crate://otherhost:19201/?ssl=true")
|
53
|
-
conn = engine.raw_connection()
|
54
|
-
self.assertEqual("<Connection <Client ['https://otherhost:19201']>>",
|
55
|
-
repr(conn.driver_connection))
|
56
|
-
conn.close()
|
57
|
-
engine.dispose()
|
58
|
-
|
59
|
-
def test_connection_server_uri_invalid_port(self):
|
60
|
-
with self.assertRaises(ValueError) as context:
|
61
|
-
sa.create_engine("crate://foo:bar")
|
62
|
-
self.assertIn("invalid literal for int() with base 10: 'bar'", str(context.exception))
|
63
|
-
|
64
|
-
def test_connection_server_uri_https_with_trusted_user(self):
|
65
|
-
engine = sa.create_engine(
|
66
|
-
"crate://foo@otherhost:19201/?ssl=true")
|
67
|
-
conn = engine.raw_connection()
|
68
|
-
self.assertEqual("<Connection <Client ['https://otherhost:19201']>>",
|
69
|
-
repr(conn.driver_connection))
|
70
|
-
self.assertEqual(conn.driver_connection.client.username, "foo")
|
71
|
-
self.assertEqual(conn.driver_connection.client.password, None)
|
72
|
-
conn.close()
|
73
|
-
engine.dispose()
|
74
|
-
|
75
|
-
def test_connection_server_uri_https_with_credentials(self):
|
76
|
-
engine = sa.create_engine(
|
77
|
-
"crate://foo:bar@otherhost:19201/?ssl=true")
|
78
|
-
conn = engine.raw_connection()
|
79
|
-
self.assertEqual("<Connection <Client ['https://otherhost:19201']>>",
|
80
|
-
repr(conn.driver_connection))
|
81
|
-
self.assertEqual(conn.driver_connection.client.username, "foo")
|
82
|
-
self.assertEqual(conn.driver_connection.client.password, "bar")
|
83
|
-
conn.close()
|
84
|
-
engine.dispose()
|
85
|
-
|
86
|
-
def test_connection_server_uri_parameter_timeout(self):
|
87
|
-
engine = sa.create_engine(
|
88
|
-
"crate://otherhost:19201/?timeout=42.42")
|
89
|
-
conn = engine.raw_connection()
|
90
|
-
self.assertEqual(conn.driver_connection.client._pool_kw["timeout"], 42.42)
|
91
|
-
conn.close()
|
92
|
-
engine.dispose()
|
93
|
-
|
94
|
-
def test_connection_server_uri_parameter_pool_size(self):
|
95
|
-
engine = sa.create_engine(
|
96
|
-
"crate://otherhost:19201/?pool_size=20")
|
97
|
-
conn = engine.raw_connection()
|
98
|
-
self.assertEqual(conn.driver_connection.client._pool_kw["maxsize"], 20)
|
99
|
-
conn.close()
|
100
|
-
engine.dispose()
|
101
|
-
|
102
|
-
def test_connection_multiple_server_http(self):
|
103
|
-
engine = sa.create_engine(
|
104
|
-
"crate://", connect_args={
|
105
|
-
'servers': ['localhost:4201', 'localhost:4202']
|
106
|
-
}
|
107
|
-
)
|
108
|
-
conn = engine.raw_connection()
|
109
|
-
self.assertEqual(
|
110
|
-
"<Connection <Client ['http://localhost:4201', " +
|
111
|
-
"'http://localhost:4202']>>",
|
112
|
-
repr(conn.driver_connection))
|
113
|
-
conn.close()
|
114
|
-
engine.dispose()
|
115
|
-
|
116
|
-
def test_connection_multiple_server_https(self):
|
117
|
-
engine = sa.create_engine(
|
118
|
-
"crate://", connect_args={
|
119
|
-
'servers': ['localhost:4201', 'localhost:4202'],
|
120
|
-
'ssl': True,
|
121
|
-
}
|
122
|
-
)
|
123
|
-
conn = engine.raw_connection()
|
124
|
-
self.assertEqual(
|
125
|
-
"<Connection <Client ['https://localhost:4201', " +
|
126
|
-
"'https://localhost:4202']>>",
|
127
|
-
repr(conn.driver_connection))
|
128
|
-
conn.close()
|
129
|
-
engine.dispose()
|