439 lines
12 KiB
Python
439 lines
12 KiB
Python
"""Global database feature support policy.
|
|
|
|
Provides decorators to mark tests requiring specific feature support from the
|
|
target database.
|
|
|
|
External dialect test suites should subclass SuiteRequirements
|
|
to provide specific inclusion/exlusions.
|
|
|
|
"""
|
|
|
|
from . import exclusions, config
|
|
|
|
|
|
class Requirements(object):
|
|
def __init__(self, config):
|
|
self.config = config
|
|
|
|
@property
|
|
def db(self):
|
|
return config.db
|
|
|
|
class SuiteRequirements(Requirements):
|
|
|
|
@property
|
|
def create_table(self):
|
|
"""target platform can emit basic CreateTable DDL."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def drop_table(self):
|
|
"""target platform can emit basic DropTable DDL."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def foreign_keys(self):
|
|
"""Target database must support foreign keys."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def on_update_cascade(self):
|
|
""""target database must support ON UPDATE..CASCADE behavior in
|
|
foreign keys."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def deferrable_fks(self):
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def on_update_or_deferrable_fks(self):
|
|
# TODO: exclusions should be composable,
|
|
# somehow only_if([x, y]) isn't working here, negation/conjunctions
|
|
# getting confused.
|
|
return exclusions.only_if(
|
|
lambda: self.on_update_cascade.enabled or self.deferrable_fks.enabled
|
|
)
|
|
|
|
|
|
@property
|
|
def self_referential_foreign_keys(self):
|
|
"""Target database must support self-referential foreign keys."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def foreign_key_ddl(self):
|
|
"""Target database must support the DDL phrases for FOREIGN KEY."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def named_constraints(self):
|
|
"""target database must support names for constraints."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def subqueries(self):
|
|
"""Target database must support subqueries."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def offset(self):
|
|
"""target database can render OFFSET, or an equivalent, in a SELECT."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def boolean_col_expressions(self):
|
|
"""Target database must support boolean expressions as columns"""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def nullsordering(self):
|
|
"""Target backends that support nulls ordering."""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def standalone_binds(self):
|
|
"""target database/driver supports bound parameters as column expressions
|
|
without being in the context of a typed column.
|
|
|
|
"""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def intersect(self):
|
|
"""Target database must support INTERSECT or equivalent."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def except_(self):
|
|
"""Target database must support EXCEPT or equivalent (i.e. MINUS)."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def window_functions(self):
|
|
"""Target database must support window functions."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def autoincrement_insert(self):
|
|
"""target platform generates new surrogate integer primary key values
|
|
when insert() is executed, excluding the pk column."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def empty_inserts(self):
|
|
"""target platform supports INSERT with no values, i.e.
|
|
INSERT DEFAULT VALUES or equivalent."""
|
|
|
|
return exclusions.only_if(
|
|
lambda: self.config.db.dialect.supports_empty_insert or \
|
|
self.config.db.dialect.supports_default_values,
|
|
"empty inserts not supported"
|
|
)
|
|
|
|
@property
|
|
def insert_from_select(self):
|
|
"""target platform supports INSERT from a SELECT."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def returning(self):
|
|
"""target platform supports RETURNING."""
|
|
|
|
return exclusions.only_if(
|
|
lambda: self.config.db.dialect.implicit_returning,
|
|
"'returning' not supported by database"
|
|
)
|
|
|
|
@property
|
|
def denormalized_names(self):
|
|
"""Target database must have 'denormalized', i.e.
|
|
UPPERCASE as case insensitive names."""
|
|
|
|
return exclusions.skip_if(
|
|
lambda: not self.db.dialect.requires_name_normalize,
|
|
"Backend does not require denormalized names."
|
|
)
|
|
|
|
@property
|
|
def multivalues_inserts(self):
|
|
"""target database must support multiple VALUES clauses in an
|
|
INSERT statement."""
|
|
|
|
return exclusions.skip_if(
|
|
lambda: not self.db.dialect.supports_multivalues_insert,
|
|
"Backend does not support multirow inserts."
|
|
)
|
|
|
|
|
|
@property
|
|
def implements_get_lastrowid(self):
|
|
""""target dialect implements the executioncontext.get_lastrowid()
|
|
method without reliance on RETURNING.
|
|
|
|
"""
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def emulated_lastrowid(self):
|
|
""""target dialect retrieves cursor.lastrowid, or fetches
|
|
from a database-side function after an insert() construct executes,
|
|
within the get_lastrowid() method.
|
|
|
|
Only dialects that "pre-execute", or need RETURNING to get last
|
|
inserted id, would return closed/fail/skip for this.
|
|
|
|
"""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def dbapi_lastrowid(self):
|
|
""""target platform includes a 'lastrowid' accessor on the DBAPI
|
|
cursor object.
|
|
|
|
"""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def views(self):
|
|
"""Target database must support VIEWs."""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def schemas(self):
|
|
"""Target database must support external schemas, and have one
|
|
named 'test_schema'."""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def sequences(self):
|
|
"""Target database must support SEQUENCEs."""
|
|
|
|
return exclusions.only_if([
|
|
lambda: self.config.db.dialect.supports_sequences
|
|
], "no sequence support")
|
|
|
|
@property
|
|
def sequences_optional(self):
|
|
"""Target database supports sequences, but also optionally
|
|
as a means of generating new PK values."""
|
|
|
|
return exclusions.only_if([
|
|
lambda: self.config.db.dialect.supports_sequences and \
|
|
self.config.db.dialect.sequences_optional
|
|
], "no sequence support, or sequences not optional")
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
def reflects_pk_names(self):
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def table_reflection(self):
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def view_reflection(self):
|
|
return self.views
|
|
|
|
@property
|
|
def schema_reflection(self):
|
|
return self.schemas
|
|
|
|
@property
|
|
def primary_key_constraint_reflection(self):
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def foreign_key_constraint_reflection(self):
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def index_reflection(self):
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def unbounded_varchar(self):
|
|
"""Target database must support VARCHAR with no length"""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def unicode_data(self):
|
|
"""Target database/dialect must support Python unicode objects with
|
|
non-ASCII characters represented, delivered as bound parameters
|
|
as well as in result rows.
|
|
|
|
"""
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def unicode_ddl(self):
|
|
"""Target driver must support some degree of non-ascii symbol names."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def datetime(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.datetime() objects."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def datetime_microseconds(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.datetime() with microsecond objects."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def datetime_historic(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.datetime() objects with historic (pre 1970) values."""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def date(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.date() objects."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def date_historic(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.datetime() objects with historic (pre 1970) values."""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def time(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.time() objects."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def time_microseconds(self):
|
|
"""target dialect supports representation of Python
|
|
datetime.time() with microsecond objects."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def precision_numerics_general(self):
|
|
"""target backend has general support for moderately high-precision
|
|
numerics."""
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def precision_numerics_enotation_small(self):
|
|
"""target backend supports Decimal() objects using E notation
|
|
to represent very small values."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def precision_numerics_enotation_large(self):
|
|
"""target backend supports Decimal() objects using E notation
|
|
to represent very large values."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def precision_numerics_many_significant_digits(self):
|
|
"""target backend supports values with many digits on both sides,
|
|
such as 319438950232418390.273596, 87673.594069654243
|
|
|
|
"""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def precision_numerics_retains_significant_digits(self):
|
|
"""A precision numeric type will return empty significant digits,
|
|
i.e. a value such as 10.000 will come back in Decimal form with
|
|
the .000 maintained."""
|
|
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def text_type(self):
|
|
"""Target database must support an unbounded Text() "
|
|
"type such as TEXT or CLOB"""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def empty_strings_varchar(self):
|
|
"""target database can persist/return an empty string with a
|
|
varchar.
|
|
|
|
"""
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def empty_strings_text(self):
|
|
"""target database can persist/return an empty string with an
|
|
unbounded text."""
|
|
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def update_from(self):
|
|
"""Target must support UPDATE..FROM syntax"""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def update_where_target_in_subquery(self):
|
|
"""Target must support UPDATE where the same table is present in a
|
|
subquery in the WHERE clause.
|
|
|
|
This is an ANSI-standard syntax that apparently MySQL can't handle,
|
|
such as:
|
|
|
|
UPDATE documents SET flag=1 WHERE documents.title IN
|
|
(SELECT max(documents.title) AS title
|
|
FROM documents GROUP BY documents.user_id
|
|
)
|
|
"""
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def mod_operator_as_percent_sign(self):
|
|
"""target database must use a plain percent '%' as the 'modulus'
|
|
operator."""
|
|
return exclusions.closed()
|
|
|
|
@property
|
|
def unicode_connections(self):
|
|
"""Target driver must support non-ASCII characters being passed at all."""
|
|
return exclusions.open()
|
|
|
|
@property
|
|
def skip_mysql_on_windows(self):
|
|
"""Catchall for a large variety of MySQL on Windows failures"""
|
|
return exclusions.open()
|
|
|
|
def _has_mysql_on_windows(self):
|
|
return False
|
|
|
|
def _has_mysql_fully_case_sensitive(self):
|
|
return False
|