"""Global database feature support policy. Provides decorators to mark tests requiring specific feature support from the target database. """ from testing import \ _block_unconditionally as no_support, \ _chain_decorators_on, \ exclude, \ emits_warning_on,\ skip_if,\ fails_on import testing import sys def deferrable_constraints(fn): """Target database must support derferable constraints.""" return _chain_decorators_on( fn, no_support('firebird', 'not supported by database'), no_support('mysql', 'not supported by database'), no_support('mssql', 'not supported by database'), ) def foreign_keys(fn): """Target database must support foreign keys.""" return _chain_decorators_on( fn, no_support('sqlite', 'not supported by database'), ) def unbounded_varchar(fn): """Target database must support VARCHAR with no length""" return _chain_decorators_on( fn, no_support('firebird', 'not supported by database'), no_support('oracle', 'not supported by database'), no_support('mysql', 'not supported by database'), ) def boolean_col_expressions(fn): """Target database must support boolean expressions as columns""" return _chain_decorators_on( fn, no_support('firebird', 'not supported by database'), no_support('oracle', 'not supported by database'), no_support('mssql', 'not supported by database'), no_support('sybase', 'not supported by database'), no_support('maxdb', 'FIXME: verify not supported by database'), ) def identity(fn): """Target database must support GENERATED AS IDENTITY or a facsimile. Includes GENERATED AS IDENTITY, AUTOINCREMENT, AUTO_INCREMENT, or other column DDL feature that fills in a DB-generated identifier at INSERT-time without requiring pre-execution of a SEQUENCE or other artifact. """ return _chain_decorators_on( fn, no_support('firebird', 'not supported by database'), no_support('oracle', 'not supported by database'), no_support('postgresql', 'not supported by database'), no_support('sybase', 'not supported by database'), ) def independent_cursors(fn): """Target must support simultaneous, independent database cursors on a single connection.""" return _chain_decorators_on( fn, no_support('mssql+pyodbc', 'no driver support'), no_support('mssql+mxodbc', 'no driver support'), ) def independent_connections(fn): """Target must support simultaneous, independent database connections.""" # This is also true of some configurations of UnixODBC and probably win32 # ODBC as well. return _chain_decorators_on( fn, no_support('sqlite', 'no driver support'), exclude('mssql', '<', (9, 0, 0), 'SQL Server 2005+ is required for independent connections'), ) def row_triggers(fn): """Target must support standard statement-running EACH ROW triggers.""" return _chain_decorators_on( fn, # no access to same table no_support('mysql', 'requires SUPER priv'), exclude('mysql', '<', (5, 0, 10), 'not supported by database'), # huh? TODO: implement triggers for PG tests, remove this no_support('postgresql', 'PG triggers need to be implemented for tests'), ) def correlated_outer_joins(fn): """Target must support an outer join to a subquery which correlates to the parent.""" return _chain_decorators_on( fn, no_support('oracle', 'Raises "ORA-01799: a column may not be outer-joined to a subquery"') ) def savepoints(fn): """Target database must support savepoints.""" return _chain_decorators_on( fn, emits_warning_on('mssql', 'Savepoint support in mssql is experimental and may lead to data loss.'), no_support('access', 'not supported by database'), no_support('sqlite', 'not supported by database'), no_support('sybase', 'FIXME: guessing, needs confirmation'), exclude('mysql', '<', (5, 0, 3), 'not supported by database'), ) def denormalized_names(fn): """Target database must have 'denormalized', i.e. UPPERCASE as case insensitive names.""" return skip_if( lambda: not testing.db.dialect.requires_name_normalize, "Backend does not require denomralized names." )(fn) def schemas(fn): """Target database must support external schemas, and have one named 'test_schema'.""" return _chain_decorators_on( fn, no_support('sqlite', 'no schema support'), no_support('firebird', 'no schema support') ) def sequences(fn): """Target database must support SEQUENCEs.""" return _chain_decorators_on( fn, no_support('access', 'no SEQUENCE support'), no_support('mssql', 'no SEQUENCE support'), no_support('mysql', 'no SEQUENCE support'), no_support('sqlite', 'no SEQUENCE support'), no_support('sybase', 'no SEQUENCE support'), ) def subqueries(fn): """Target database must support subqueries.""" return _chain_decorators_on( fn, exclude('mysql', '<', (4, 1, 1), 'no subquery support'), ) def intersect(fn): """Target database must support INTERSECT or equivlaent.""" return _chain_decorators_on( fn, fails_on('firebird', 'no support for INTERSECT'), fails_on('mysql', 'no support for INTERSECT'), fails_on('sybase', 'no support for INTERSECT'), ) def except_(fn): """Target database must support EXCEPT or equivlaent (i.e. MINUS).""" return _chain_decorators_on( fn, fails_on('firebird', 'no support for EXCEPT'), fails_on('mysql', 'no support for EXCEPT'), fails_on('sybase', 'no support for EXCEPT'), ) def offset(fn): """Target database must support some method of adding OFFSET or equivalent to a result set.""" return _chain_decorators_on( fn, fails_on('sybase', 'no support for OFFSET or equivalent'), ) def returning(fn): return _chain_decorators_on( fn, no_support('access', 'not supported by database'), no_support('sqlite', 'not supported by database'), no_support('mysql', 'not supported by database'), no_support('maxdb', 'not supported by database'), no_support('sybase', 'not supported by database'), no_support('informix', 'not supported by database'), ) def two_phase_transactions(fn): """Target database must support two-phase transactions.""" return _chain_decorators_on( fn, no_support('access', 'not supported by database'), no_support('firebird', 'no SA implementation'), no_support('maxdb', 'not supported by database'), no_support('mssql', 'FIXME: guessing, needs confirmation'), no_support('oracle', 'no SA implementation'), no_support('sqlite', 'not supported by database'), no_support('sybase', 'FIXME: guessing, needs confirmation'), no_support('postgresql+zxjdbc', 'FIXME: JDBC driver confuses the transaction state, may ' 'need separate XA implementation'), exclude('mysql', '<', (5, 0, 3), 'not supported by database'), ) def unicode_connections(fn): """Target driver must support some encoding of Unicode across the wire.""" # TODO: expand to exclude MySQLdb versions w/ broken unicode return _chain_decorators_on( fn, exclude('mysql', '<', (4, 1, 1), 'no unicode connection support'), ) def unicode_ddl(fn): """Target driver must support some encoding of Unicode across the wire.""" # TODO: expand to exclude MySQLdb versions w/ broken unicode return _chain_decorators_on( fn, no_support('maxdb', 'database support flakey'), no_support('oracle', 'FIXME: no support in database?'), no_support('sybase', 'FIXME: guessing, needs confirmation'), no_support('mssql+pymssql', 'no FreeTDS support'), exclude('mysql', '<', (4, 1, 1), 'no unicode connection support'), ) def sane_rowcount(fn): return _chain_decorators_on( fn, skip_if(lambda: not testing.db.dialect.supports_sane_rowcount) ) def python2(fn): return _chain_decorators_on( fn, skip_if( lambda: sys.version_info >= (3,), "Python version 2.xx is required." ) ) def _has_sqlite(): from sqlalchemy import create_engine try: e = create_engine('sqlite://') return True except ImportError: return False def sqlite(fn): return _chain_decorators_on( fn, skip_if(lambda: not _has_sqlite()) )