1
"""Global database feature support policy.
3
Provides decorators to mark tests requiring specific feature support from the
6
External dialect test suites should subclass SuiteRequirements
7
to provide specific inclusion/exlusions.
11
from . import exclusions, config
14
class Requirements(object):
15
def __init__(self, config):
22
class SuiteRequirements(Requirements):
25
def create_table(self):
26
"""target platform can emit basic CreateTable DDL."""
28
return exclusions.open()
32
"""target platform can emit basic DropTable DDL."""
34
return exclusions.open()
37
def foreign_keys(self):
38
"""Target database must support foreign keys."""
40
return exclusions.open()
43
def on_update_cascade(self):
44
""""target database must support ON UPDATE..CASCADE behavior in
47
return exclusions.open()
50
def deferrable_fks(self):
51
return exclusions.closed()
54
def on_update_or_deferrable_fks(self):
55
# TODO: exclusions should be composable,
56
# somehow only_if([x, y]) isn't working here, negation/conjunctions
58
return exclusions.only_if(
59
lambda: self.on_update_cascade.enabled or self.deferrable_fks.enabled
64
def self_referential_foreign_keys(self):
65
"""Target database must support self-referential foreign keys."""
67
return exclusions.open()
70
def foreign_key_ddl(self):
71
"""Target database must support the DDL phrases for FOREIGN KEY."""
73
return exclusions.open()
76
def named_constraints(self):
77
"""target database must support names for constraints."""
79
return exclusions.open()
83
"""Target database must support subqueries."""
85
return exclusions.open()
89
"""target database can render OFFSET, or an equivalent, in a SELECT."""
91
return exclusions.open()
94
def boolean_col_expressions(self):
95
"""Target database must support boolean expressions as columns"""
97
return exclusions.closed()
100
def nullsordering(self):
101
"""Target backends that support nulls ordering."""
103
return exclusions.closed()
106
def standalone_binds(self):
107
"""target database/driver supports bound parameters as column expressions
108
without being in the context of a typed column.
111
return exclusions.closed()
115
"""Target database must support INTERSECT or equivalent."""
116
return exclusions.closed()
120
"""Target database must support EXCEPT or equivalent (i.e. MINUS)."""
121
return exclusions.closed()
124
def window_functions(self):
125
"""Target database must support window functions."""
126
return exclusions.closed()
129
def autoincrement_insert(self):
130
"""target platform generates new surrogate integer primary key values
131
when insert() is executed, excluding the pk column."""
133
return exclusions.open()
136
def empty_inserts(self):
137
"""target platform supports INSERT with no values, i.e.
138
INSERT DEFAULT VALUES or equivalent."""
140
return exclusions.only_if(
141
lambda: self.config.db.dialect.supports_empty_insert or \
142
self.config.db.dialect.supports_default_values,
143
"empty inserts not supported"
148
"""target platform supports RETURNING."""
150
return exclusions.only_if(
151
lambda: self.config.db.dialect.implicit_returning,
152
"'returning' not supported by database"
156
def denormalized_names(self):
157
"""Target database must have 'denormalized', i.e.
158
UPPERCASE as case insensitive names."""
160
return exclusions.skip_if(
161
lambda: not self.db.dialect.requires_name_normalize,
162
"Backend does not require denormalized names."
166
def multivalues_inserts(self):
167
"""target database must support multiple VALUES clauses in an
170
return exclusions.skip_if(
171
lambda: not self.db.dialect.supports_multivalues_insert,
172
"Backend does not support multirow inserts."
177
def implements_get_lastrowid(self):
178
""""target dialect implements the executioncontext.get_lastrowid()
179
method without reliance on RETURNING.
182
return exclusions.open()
185
def emulated_lastrowid(self):
186
""""target dialect retrieves cursor.lastrowid, or fetches
187
from a database-side function after an insert() construct executes,
188
within the get_lastrowid() method.
190
Only dialects that "pre-execute", or need RETURNING to get last
191
inserted id, would return closed/fail/skip for this.
194
return exclusions.closed()
197
def dbapi_lastrowid(self):
198
""""target platform includes a 'lastrowid' accessor on the DBAPI
202
return exclusions.closed()
206
"""Target database must support VIEWs."""
208
return exclusions.closed()
212
"""Target database must support external schemas, and have one
213
named 'test_schema'."""
215
return exclusions.closed()
219
"""Target database must support SEQUENCEs."""
221
return exclusions.only_if([
222
lambda: self.config.db.dialect.supports_sequences
223
], "no sequence support")
226
def sequences_optional(self):
227
"""Target database supports sequences, but also optionally
228
as a means of generating new PK values."""
230
return exclusions.only_if([
231
lambda: self.config.db.dialect.supports_sequences and \
232
self.config.db.dialect.sequences_optional
233
], "no sequence support, or sequences not optional")
240
def reflects_pk_names(self):
241
return exclusions.closed()
244
def table_reflection(self):
245
return exclusions.open()
248
def view_reflection(self):
252
def schema_reflection(self):
256
def primary_key_constraint_reflection(self):
257
return exclusions.open()
260
def foreign_key_constraint_reflection(self):
261
return exclusions.open()
264
def index_reflection(self):
265
return exclusions.open()
268
def unbounded_varchar(self):
269
"""Target database must support VARCHAR with no length"""
271
return exclusions.open()
274
def unicode_data(self):
275
"""Target database/dialect must support Python unicode objects with
276
non-ASCII characters represented, delivered as bound parameters
277
as well as in result rows.
280
return exclusions.open()
283
def unicode_ddl(self):
284
"""Target driver must support some degree of non-ascii symbol names."""
285
return exclusions.closed()
289
"""target dialect supports representation of Python
290
datetime.datetime() objects."""
292
return exclusions.open()
295
def datetime_microseconds(self):
296
"""target dialect supports representation of Python
297
datetime.datetime() with microsecond objects."""
299
return exclusions.open()
302
def datetime_historic(self):
303
"""target dialect supports representation of Python
304
datetime.datetime() objects with historic (pre 1970) values."""
306
return exclusions.closed()
310
"""target dialect supports representation of Python
311
datetime.date() objects."""
313
return exclusions.open()
316
def date_historic(self):
317
"""target dialect supports representation of Python
318
datetime.datetime() objects with historic (pre 1970) values."""
320
return exclusions.closed()
324
"""target dialect supports representation of Python
325
datetime.time() objects."""
327
return exclusions.open()
330
def time_microseconds(self):
331
"""target dialect supports representation of Python
332
datetime.time() with microsecond objects."""
334
return exclusions.open()
337
def precision_numerics_general(self):
338
"""target backend has general support for moderately high-precision
340
return exclusions.open()
343
def precision_numerics_enotation_small(self):
344
"""target backend supports Decimal() objects using E notation
345
to represent very small values."""
346
return exclusions.closed()
349
def precision_numerics_enotation_large(self):
350
"""target backend supports Decimal() objects using E notation
351
to represent very large values."""
352
return exclusions.closed()
355
def precision_numerics_many_significant_digits(self):
356
"""target backend supports values with many digits on both sides,
357
such as 319438950232418390.273596, 87673.594069654243
360
return exclusions.closed()
363
def precision_numerics_retains_significant_digits(self):
364
"""A precision numeric type will return empty significant digits,
365
i.e. a value such as 10.000 will come back in Decimal form with
366
the .000 maintained."""
368
return exclusions.closed()
372
"""Target database must support an unbounded Text() "
373
"type such as TEXT or CLOB"""
375
return exclusions.open()
378
def empty_strings_varchar(self):
379
"""target database can persist/return an empty string with a
383
return exclusions.open()
386
def empty_strings_text(self):
387
"""target database can persist/return an empty string with an
390
return exclusions.open()
393
def update_from(self):
394
"""Target must support UPDATE..FROM syntax"""
395
return exclusions.closed()
398
def update_where_target_in_subquery(self):
399
"""Target must support UPDATE where the same table is present in a
400
subquery in the WHERE clause.
402
This is an ANSI-standard syntax that apparently MySQL can't handle,
405
UPDATE documents SET flag=1 WHERE documents.title IN
406
(SELECT max(documents.title) AS title
407
FROM documents GROUP BY documents.user_id
410
return exclusions.open()
413
def mod_operator_as_percent_sign(self):
414
"""target database must use a plain percent '%' as the 'modulus'
416
return exclusions.closed()
419
def unicode_connections(self):
420
"""Target driver must support non-ASCII characters being passed at all."""
421
return exclusions.open()
424
def skip_mysql_on_windows(self):
425
"""Catchall for a large variety of MySQL on Windows failures"""
426
return exclusions.open()
428
def _has_mysql_on_windows(self):
431
def _has_mysql_fully_case_sensitive(self):