1# testing/requirements.py
2# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
8"""Global database feature support policy.
9
10Provides decorators to mark tests requiring specific feature support from the
11target database.
12
13External dialect test suites should subclass SuiteRequirements
14to provide specific inclusion/exclusions.
15
16"""
17
18from . import exclusions
19from .. import util
20
21
22class Requirements(object):
23    pass
24
25
26class SuiteRequirements(Requirements):
27
28    @property
29    def create_table(self):
30        """target platform can emit basic CreateTable DDL."""
31
32        return exclusions.open()
33
34    @property
35    def drop_table(self):
36        """target platform can emit basic DropTable DDL."""
37
38        return exclusions.open()
39
40    @property
41    def foreign_keys(self):
42        """Target database must support foreign keys."""
43
44        return exclusions.open()
45
46    @property
47    def on_update_cascade(self):
48        """"target database must support ON UPDATE..CASCADE behavior in
49        foreign keys."""
50
51        return exclusions.open()
52
53    @property
54    def non_updating_cascade(self):
55        """target database must *not* support ON UPDATE..CASCADE behavior in
56        foreign keys."""
57        return exclusions.closed()
58
59    @property
60    def deferrable_fks(self):
61        return exclusions.closed()
62
63    @property
64    def on_update_or_deferrable_fks(self):
65        # TODO: exclusions should be composable,
66        # somehow only_if([x, y]) isn't working here, negation/conjunctions
67        # getting confused.
68        return exclusions.only_if(
69            lambda: self.on_update_cascade.enabled or
70            self.deferrable_fks.enabled
71        )
72
73    @property
74    def self_referential_foreign_keys(self):
75        """Target database must support self-referential foreign keys."""
76
77        return exclusions.open()
78
79    @property
80    def foreign_key_ddl(self):
81        """Target database must support the DDL phrases for FOREIGN KEY."""
82
83        return exclusions.open()
84
85    @property
86    def named_constraints(self):
87        """target database must support names for constraints."""
88
89        return exclusions.open()
90
91    @property
92    def subqueries(self):
93        """Target database must support subqueries."""
94
95        return exclusions.open()
96
97    @property
98    def offset(self):
99        """target database can render OFFSET, or an equivalent, in a
100        SELECT.
101        """
102
103        return exclusions.open()
104
105    @property
106    def bound_limit_offset(self):
107        """target database can render LIMIT and/or OFFSET using a bound
108        parameter
109        """
110
111        return exclusions.open()
112
113    @property
114    def boolean_col_expressions(self):
115        """Target database must support boolean expressions as columns"""
116
117        return exclusions.closed()
118
119    @property
120    def nullsordering(self):
121        """Target backends that support nulls ordering."""
122
123        return exclusions.closed()
124
125    @property
126    def standalone_binds(self):
127        """target database/driver supports bound parameters as column expressions
128        without being in the context of a typed column.
129
130        """
131        return exclusions.closed()
132
133    @property
134    def intersect(self):
135        """Target database must support INTERSECT or equivalent."""
136        return exclusions.closed()
137
138    @property
139    def except_(self):
140        """Target database must support EXCEPT or equivalent (i.e. MINUS)."""
141        return exclusions.closed()
142
143    @property
144    def window_functions(self):
145        """Target database must support window functions."""
146        return exclusions.closed()
147
148    @property
149    def autoincrement_insert(self):
150        """target platform generates new surrogate integer primary key values
151        when insert() is executed, excluding the pk column."""
152
153        return exclusions.open()
154
155    @property
156    def fetch_rows_post_commit(self):
157        """target platform will allow cursor.fetchone() to proceed after a
158        COMMIT.
159
160        Typically this refers to an INSERT statement with RETURNING which
161        is invoked within "autocommit".   If the row can be returned
162        after the autocommit, then this rule can be open.
163
164        """
165
166        return exclusions.open()
167
168    @property
169    def empty_inserts(self):
170        """target platform supports INSERT with no values, i.e.
171        INSERT DEFAULT VALUES or equivalent."""
172
173        return exclusions.only_if(
174            lambda config: config.db.dialect.supports_empty_insert or
175            config.db.dialect.supports_default_values,
176            "empty inserts not supported"
177        )
178
179    @property
180    def insert_from_select(self):
181        """target platform supports INSERT from a SELECT."""
182
183        return exclusions.open()
184
185    @property
186    def returning(self):
187        """target platform supports RETURNING."""
188
189        return exclusions.only_if(
190            lambda config: config.db.dialect.implicit_returning,
191            "%(database)s %(does_support)s 'returning'"
192        )
193
194    @property
195    def duplicate_names_in_cursor_description(self):
196        """target platform supports a SELECT statement that has
197        the same name repeated more than once in the columns list."""
198
199        return exclusions.open()
200
201    @property
202    def denormalized_names(self):
203        """Target database must have 'denormalized', i.e.
204        UPPERCASE as case insensitive names."""
205
206        return exclusions.skip_if(
207            lambda config: not config.db.dialect.requires_name_normalize,
208            "Backend does not require denormalized names."
209        )
210
211    @property
212    def multivalues_inserts(self):
213        """target database must support multiple VALUES clauses in an
214        INSERT statement."""
215
216        return exclusions.skip_if(
217            lambda config: not config.db.dialect.supports_multivalues_insert,
218            "Backend does not support multirow inserts."
219        )
220
221    @property
222    def implements_get_lastrowid(self):
223        """"target dialect implements the executioncontext.get_lastrowid()
224        method without reliance on RETURNING.
225
226        """
227        return exclusions.open()
228
229    @property
230    def emulated_lastrowid(self):
231        """"target dialect retrieves cursor.lastrowid, or fetches
232        from a database-side function after an insert() construct executes,
233        within the get_lastrowid() method.
234
235        Only dialects that "pre-execute", or need RETURNING to get last
236        inserted id, would return closed/fail/skip for this.
237
238        """
239        return exclusions.closed()
240
241    @property
242    def dbapi_lastrowid(self):
243        """"target platform includes a 'lastrowid' accessor on the DBAPI
244        cursor object.
245
246        """
247        return exclusions.closed()
248
249    @property
250    def views(self):
251        """Target database must support VIEWs."""
252
253        return exclusions.closed()
254
255    @property
256    def schemas(self):
257        """Target database must support external schemas, and have one
258        named 'test_schema'."""
259
260        return exclusions.closed()
261
262    @property
263    def sequences(self):
264        """Target database must support SEQUENCEs."""
265
266        return exclusions.only_if([
267            lambda config: config.db.dialect.supports_sequences
268        ], "no sequence support")
269
270    @property
271    def sequences_optional(self):
272        """Target database supports sequences, but also optionally
273        as a means of generating new PK values."""
274
275        return exclusions.only_if([
276            lambda config: config.db.dialect.supports_sequences and
277            config.db.dialect.sequences_optional
278        ], "no sequence support, or sequences not optional")
279
280    @property
281    def reflects_pk_names(self):
282        return exclusions.closed()
283
284    @property
285    def table_reflection(self):
286        return exclusions.open()
287
288    @property
289    def view_column_reflection(self):
290        """target database must support retrieval of the columns in a view,
291        similarly to how a table is inspected.
292
293        This does not include the full CREATE VIEW definition.
294
295        """
296        return self.views
297
298    @property
299    def view_reflection(self):
300        """target database must support inspection of the full CREATE VIEW definition.
301        """
302        return self.views
303
304    @property
305    def schema_reflection(self):
306        return self.schemas
307
308    @property
309    def primary_key_constraint_reflection(self):
310        return exclusions.open()
311
312    @property
313    def foreign_key_constraint_reflection(self):
314        return exclusions.open()
315
316    @property
317    def temp_table_reflection(self):
318        return exclusions.open()
319
320    @property
321    def temp_table_names(self):
322        """target dialect supports listing of temporary table names"""
323        return exclusions.closed()
324
325    @property
326    def temporary_tables(self):
327        """target database supports temporary tables"""
328        return exclusions.open()
329
330    @property
331    def temporary_views(self):
332        """target database supports temporary views"""
333        return exclusions.closed()
334
335    @property
336    def index_reflection(self):
337        return exclusions.open()
338
339    @property
340    def unique_constraint_reflection(self):
341        """target dialect supports reflection of unique constraints"""
342        return exclusions.open()
343
344    @property
345    def duplicate_key_raises_integrity_error(self):
346        """target dialect raises IntegrityError when reporting an INSERT
347        with a primary key violation.  (hint: it should)
348
349        """
350        return exclusions.open()
351
352    @property
353    def unbounded_varchar(self):
354        """Target database must support VARCHAR with no length"""
355
356        return exclusions.open()
357
358    @property
359    def unicode_data(self):
360        """Target database/dialect must support Python unicode objects with
361        non-ASCII characters represented, delivered as bound parameters
362        as well as in result rows.
363
364        """
365        return exclusions.open()
366
367    @property
368    def unicode_ddl(self):
369        """Target driver must support some degree of non-ascii symbol
370        names.
371        """
372        return exclusions.closed()
373
374    @property
375    def datetime_literals(self):
376        """target dialect supports rendering of a date, time, or datetime as a
377        literal string, e.g. via the TypeEngine.literal_processor() method.
378
379        """
380
381        return exclusions.closed()
382
383    @property
384    def datetime(self):
385        """target dialect supports representation of Python
386        datetime.datetime() objects."""
387
388        return exclusions.open()
389
390    @property
391    def datetime_microseconds(self):
392        """target dialect supports representation of Python
393        datetime.datetime() with microsecond objects."""
394
395        return exclusions.open()
396
397    @property
398    def datetime_historic(self):
399        """target dialect supports representation of Python
400        datetime.datetime() objects with historic (pre 1970) values."""
401
402        return exclusions.closed()
403
404    @property
405    def date(self):
406        """target dialect supports representation of Python
407        datetime.date() objects."""
408
409        return exclusions.open()
410
411    @property
412    def date_coerces_from_datetime(self):
413        """target dialect accepts a datetime object as the target
414        of a date column."""
415
416        return exclusions.open()
417
418    @property
419    def date_historic(self):
420        """target dialect supports representation of Python
421        datetime.datetime() objects with historic (pre 1970) values."""
422
423        return exclusions.closed()
424
425    @property
426    def time(self):
427        """target dialect supports representation of Python
428        datetime.time() objects."""
429
430        return exclusions.open()
431
432    @property
433    def time_microseconds(self):
434        """target dialect supports representation of Python
435        datetime.time() with microsecond objects."""
436
437        return exclusions.open()
438
439    @property
440    def binary_comparisons(self):
441        """target database/driver can allow BLOB/BINARY fields to be compared
442        against a bound parameter value.
443        """
444
445        return exclusions.open()
446
447    @property
448    def binary_literals(self):
449        """target backend supports simple binary literals, e.g. an
450        expression like::
451
452            SELECT CAST('foo' AS BINARY)
453
454        Where ``BINARY`` is the type emitted from :class:`.LargeBinary`,
455        e.g. it could be ``BLOB`` or similar.
456
457        Basically fails on Oracle.
458
459        """
460
461        return exclusions.open()
462
463    @property
464    def precision_numerics_general(self):
465        """target backend has general support for moderately high-precision
466        numerics."""
467        return exclusions.open()
468
469    @property
470    def precision_numerics_enotation_small(self):
471        """target backend supports Decimal() objects using E notation
472        to represent very small values."""
473        return exclusions.closed()
474
475    @property
476    def precision_numerics_enotation_large(self):
477        """target backend supports Decimal() objects using E notation
478        to represent very large values."""
479        return exclusions.closed()
480
481    @property
482    def precision_numerics_many_significant_digits(self):
483        """target backend supports values with many digits on both sides,
484        such as 319438950232418390.273596, 87673.594069654243
485
486        """
487        return exclusions.closed()
488
489    @property
490    def precision_numerics_retains_significant_digits(self):
491        """A precision numeric type will return empty significant digits,
492        i.e. a value such as 10.000 will come back in Decimal form with
493        the .000 maintained."""
494
495        return exclusions.closed()
496
497    @property
498    def precision_generic_float_type(self):
499        """target backend will return native floating point numbers with at
500        least seven decimal places when using the generic Float type.
501
502        """
503        return exclusions.open()
504
505    @property
506    def floats_to_four_decimals(self):
507        """target backend can return a floating-point number with four
508        significant digits (such as 15.7563) accurately
509        (i.e. without FP inaccuracies, such as 15.75629997253418).
510
511        """
512        return exclusions.open()
513
514    @property
515    def fetch_null_from_numeric(self):
516        """target backend doesn't crash when you try to select a NUMERIC
517        value that has a value of NULL.
518
519        Added to support Pyodbc bug #351.
520        """
521
522        return exclusions.open()
523
524    @property
525    def text_type(self):
526        """Target database must support an unbounded Text() "
527        "type such as TEXT or CLOB"""
528
529        return exclusions.open()
530
531    @property
532    def empty_strings_varchar(self):
533        """target database can persist/return an empty string with a
534        varchar.
535
536        """
537        return exclusions.open()
538
539    @property
540    def empty_strings_text(self):
541        """target database can persist/return an empty string with an
542        unbounded text."""
543
544        return exclusions.open()
545
546    @property
547    def selectone(self):
548        """target driver must support the literal statement 'select 1'"""
549        return exclusions.open()
550
551    @property
552    def savepoints(self):
553        """Target database must support savepoints."""
554
555        return exclusions.closed()
556
557    @property
558    def two_phase_transactions(self):
559        """Target database must support two-phase transactions."""
560
561        return exclusions.closed()
562
563    @property
564    def update_from(self):
565        """Target must support UPDATE..FROM syntax"""
566        return exclusions.closed()
567
568    @property
569    def update_where_target_in_subquery(self):
570        """Target must support UPDATE where the same table is present in a
571        subquery in the WHERE clause.
572
573        This is an ANSI-standard syntax that apparently MySQL can't handle,
574        such as:
575
576        UPDATE documents SET flag=1 WHERE documents.title IN
577            (SELECT max(documents.title) AS title
578                FROM documents GROUP BY documents.user_id
579            )
580        """
581        return exclusions.open()
582
583    @property
584    def mod_operator_as_percent_sign(self):
585        """target database must use a plain percent '%' as the 'modulus'
586        operator."""
587        return exclusions.closed()
588
589    @property
590    def percent_schema_names(self):
591        """target backend supports weird identifiers with percent signs
592        in them, e.g. 'some % column'.
593
594        this is a very weird use case but often has problems because of
595        DBAPIs that use python formatting.  It's not a critical use
596        case either.
597
598        """
599        return exclusions.closed()
600
601    @property
602    def order_by_label_with_expression(self):
603        """target backend supports ORDER BY a column label within an
604        expression.
605
606        Basically this::
607
608            select data as foo from test order by foo || 'bar'
609
610        Lots of databases including Postgresql don't support this,
611        so this is off by default.
612
613        """
614        return exclusions.closed()
615
616    @property
617    def unicode_connections(self):
618        """Target driver must support non-ASCII characters being passed at
619        all.
620        """
621        return exclusions.open()
622
623    @property
624    def graceful_disconnects(self):
625        """Target driver must raise a DBAPI-level exception, such as
626        InterfaceError, when the underlying connection has been closed
627        and the execute() method is called.
628        """
629        return exclusions.open()
630
631    @property
632    def skip_mysql_on_windows(self):
633        """Catchall for a large variety of MySQL on Windows failures"""
634        return exclusions.open()
635
636    @property
637    def ad_hoc_engines(self):
638        """Test environment must allow ad-hoc engine/connection creation.
639
640        DBs that scale poorly for many connections, even when closed, i.e.
641        Oracle, may use the "--low-connections" option which flags this
642        requirement as not present.
643
644        """
645        return exclusions.skip_if(
646            lambda config: config.options.low_connections)
647
648    @property
649    def timing_intensive(self):
650        return exclusions.requires_tag("timing_intensive")
651
652    @property
653    def memory_intensive(self):
654        return exclusions.requires_tag("memory_intensive")
655
656    @property
657    def threading_with_mock(self):
658        """Mark tests that use threading and mock at the same time - stability
659        issues have been observed with coverage + python 3.3
660
661        """
662        return exclusions.skip_if(
663            lambda config: util.py3k and config.options.has_coverage,
664            "Stability issues with coverage + py3k"
665        )
666
667    @property
668    def no_coverage(self):
669        """Test should be skipped if coverage is enabled.
670
671        This is to block tests that exercise libraries that seem to be
672        sensitive to coverage, such as Postgresql notice logging.
673
674        """
675        return exclusions.skip_if(
676            lambda config: config.options.has_coverage,
677            "Issues observed when coverage is enabled"
678        )
679
680    def _has_mysql_on_windows(self, config):
681        return False
682
683    def _has_mysql_fully_case_sensitive(self, config):
684        return False
685
686    @property
687    def sqlite(self):
688        return exclusions.skip_if(lambda: not self._has_sqlite())
689
690    @property
691    def cextensions(self):
692        return exclusions.skip_if(
693            lambda: not self._has_cextensions(), "C extensions not installed"
694        )
695
696    def _has_sqlite(self):
697        from sqlalchemy import create_engine
698        try:
699            create_engine('sqlite://')
700            return True
701        except ImportError:
702            return False
703
704    def _has_cextensions(self):
705        try:
706            from sqlalchemy import cresultproxy, cprocessors
707            return True
708        except ImportError:
709            return False
710