codekingpro/portable-devtools
114k
1# testing/requirements.py
2# Copyright (C) 2005-2024 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: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9
10"""Global database feature support policy.
11
12Provides decorators to mark tests requiring specific feature support from the
13target database.
14
15External dialect test suites should subclass SuiteRequirements
16to provide specific inclusion/exclusions.
17
18"""
19
20from __future__ import annotations
21
22import platform
23
24from . import asyncio as _test_asyncio
25from . import exclusions
26from .exclusions import only_on
27from .. import create_engine
28from .. import util
29from ..pool import QueuePool
30
31
32class Requirements:
33 pass
34
35
36class SuiteRequirements(Requirements):
37 @property
38 def create_table(self):
39 """target platform can emit basic CreateTable DDL."""
40
41 return exclusions.open()
42
43 @property
44 def drop_table(self):
45 """target platform can emit basic DropTable DDL."""
46
47 return exclusions.open()
48
49 @property
50 def table_ddl_if_exists(self):
51 """target platform supports IF NOT EXISTS / IF EXISTS for tables."""
52
53 return exclusions.closed()
54
55 @property
56 def index_ddl_if_exists(self):
57 """target platform supports IF NOT EXISTS / IF EXISTS for indexes."""
58
59 return exclusions.closed()
60
61 @property
62 def uuid_data_type(self):
63 """Return databases that support the UUID datatype."""
64
65 return exclusions.closed()
66
67 @property
68 def foreign_keys(self):
69 """Target database must support foreign keys."""
70
71 return exclusions.open()
72
73 @property
74 def foreign_keys_reflect_as_index(self):
75 """Target database creates an index that's reflected for
76 foreign keys."""
77
78 return exclusions.closed()
79
80 @property
81 def unique_index_reflect_as_unique_constraints(self):
82 """Target database reflects unique indexes as unique constrains."""
83
84 return exclusions.closed()
85
86 @property
87 def unique_constraints_reflect_as_index(self):
88 """Target database reflects unique constraints as indexes."""
89
90 return exclusions.closed()
91
92 @property
93 def table_value_constructor(self):
94 """Database / dialect supports a query like::
95
96 SELECT * FROM VALUES ( (c1, c2), (c1, c2), ...)
97 AS some_table(col1, col2)
98
99 SQLAlchemy generates this with the :func:`_sql.values` function.
100
101 """
102 return exclusions.closed()
103
104 @property
105 def standard_cursor_sql(self):
106 """Target database passes SQL-92 style statements to cursor.execute()
107 when a statement like select() or insert() is run.
108
109 A very small portion of dialect-level tests will ensure that certain
110 conditions are present in SQL strings, and these tests use very basic
111 SQL that will work on any SQL-like platform in order to assert results.
112
113 It's normally a given for any pep-249 DBAPI that a statement like
114 "SELECT id, name FROM table WHERE some_table.id=5" will work.
115 However, there are dialects that don't actually produce SQL Strings
116 and instead may work with symbolic objects instead, or dialects that
117 aren't working with SQL, so for those this requirement can be marked
118 as excluded.
119
120 """
121
122 return exclusions.open()
123
124 @property
125 def on_update_cascade(self):
126 """target database must support ON UPDATE..CASCADE behavior in
127 foreign keys."""
128
129 return exclusions.open()
130
131 @property
132 def non_updating_cascade(self):
133 """target database must *not* support ON UPDATE..CASCADE behavior in
134 foreign keys."""
135 return exclusions.closed()
136
137 @property
138 def deferrable_fks(self):
139 return exclusions.closed()
140
141 @property
142 def on_update_or_deferrable_fks(self):
143 # TODO: exclusions should be composable,
144 # somehow only_if([x, y]) isn't working here, negation/conjunctions
145 # getting confused.
146 return exclusions.only_if(
147 lambda: self.on_update_cascade.enabled
148 or self.deferrable_fks.enabled
149 )
150
151 @property
152 def queue_pool(self):
153 """target database is using QueuePool"""
154
155 def go(config):
156 return isinstance(config.db.pool, QueuePool)
157
158 return exclusions.only_if(go)
159
160 @property
161 def self_referential_foreign_keys(self):
162 """Target database must support self-referential foreign keys."""
163
164 return exclusions.open()
165
166 @property
167 def foreign_key_ddl(self):
168 """Target database must support the DDL phrases for FOREIGN KEY."""
169
170 return exclusions.open()
171
172 @property
173 def named_constraints(self):
174 """target database must support names for constraints."""
175
176 return exclusions.open()
177
178 @property
179 def implicitly_named_constraints(self):
180 """target database must apply names to unnamed constraints."""
181
182 return exclusions.open()
183
184 @property
185 def unusual_column_name_characters(self):
186 """target database allows column names that have unusual characters
187 in them, such as dots, spaces, slashes, or percent signs.
188
189 The column names are as always in such a case quoted, however the
190 DB still needs to support those characters in the name somehow.
191
192 """
193 return exclusions.open()
194
195 @property
196 def subqueries(self):
197 """Target database must support subqueries."""
198
199 return exclusions.open()
200
201 @property
202 def offset(self):
203 """target database can render OFFSET, or an equivalent, in a
204 SELECT.
205 """
206
207 return exclusions.open()
208
209 @property
210 def bound_limit_offset(self):
211 """target database can render LIMIT and/or OFFSET using a bound
212 parameter
213 """
214
215 return exclusions.open()
216
217 @property
218 def sql_expression_limit_offset(self):
219 """target database can render LIMIT and/or OFFSET with a complete
220 SQL expression, such as one that uses the addition operator.
221 parameter
222 """
223
224 return exclusions.open()
225
226 @property
227 def parens_in_union_contained_select_w_limit_offset(self):
228 """Target database must support parenthesized SELECT in UNION
229 when LIMIT/OFFSET is specifically present.
230
231 E.g. (SELECT ...) UNION (SELECT ..)
232
233 This is known to fail on SQLite.
234
235 """
236 return exclusions.open()
237
238 @property
239 def parens_in_union_contained_select_wo_limit_offset(self):
240 """Target database must support parenthesized SELECT in UNION
241 when OFFSET/LIMIT is specifically not present.
242
243 E.g. (SELECT ... LIMIT ..) UNION (SELECT .. OFFSET ..)
244
245 This is known to fail on SQLite. It also fails on Oracle
246 because without LIMIT/OFFSET, there is currently no step that
247 creates an additional subquery.
248
249 """
250 return exclusions.open()
251
252 @property
253 def boolean_col_expressions(self):
254 """Target database must support boolean expressions as columns"""
255
256 return exclusions.closed()
257
258 @property
259 def nullable_booleans(self):
260 """Target database allows boolean columns to store NULL."""
261
262 return exclusions.open()
263
264 @property
265 def nullsordering(self):
266 """Target backends that support nulls ordering."""
267
268 return exclusions.closed()
269
270 @property
271 def standalone_binds(self):
272 """target database/driver supports bound parameters as column
273 expressions without being in the context of a typed column.
274 """
275 return exclusions.open()
276
277 @property
278 def standalone_null_binds_whereclause(self):
279 """target database/driver supports bound parameters with NULL in the
280 WHERE clause, in situations where it has to be typed.
281
282 """
283 return exclusions.open()
284
285 @property
286 def intersect(self):
287 """Target database must support INTERSECT or equivalent."""
288 return exclusions.closed()
289
290 @property
291 def except_(self):
292 """Target database must support EXCEPT or equivalent (i.e. MINUS)."""
293 return exclusions.closed()
294
295 @property
296 def window_functions(self):
297 """Target database must support window functions."""
298 return exclusions.closed()
299
300 @property
301 def ctes(self):
302 """Target database supports CTEs"""
303
304 return exclusions.closed()
305
306 @property
307 def ctes_with_update_delete(self):
308 """target database supports CTES that ride on top of a normal UPDATE
309 or DELETE statement which refers to the CTE in a correlated subquery.
310
311 """
312
313 return exclusions.closed()
314
315 @property
316 def ctes_on_dml(self):
317 """target database supports CTES which consist of INSERT, UPDATE
318 or DELETE *within* the CTE, e.g. WITH x AS (UPDATE....)"""
319
320 return exclusions.closed()
321
322 @property
323 def autoincrement_insert(self):
324 """target platform generates new surrogate integer primary key values
325 when insert() is executed, excluding the pk column."""
326
327 return exclusions.open()
328
329 @property
330 def fetch_rows_post_commit(self):
331 """target platform will allow cursor.fetchone() to proceed after a
332 COMMIT.
333
334 Typically this refers to an INSERT statement with RETURNING which
335 is invoked within "autocommit". If the row can be returned
336 after the autocommit, then this rule can be open.
337
338 """
339
340 return exclusions.open()
341
342 @property
343 def group_by_complex_expression(self):
344 """target platform supports SQL expressions in GROUP BY
345
346 e.g.
347
348 SELECT x + y AS somelabel FROM table GROUP BY x + y
349
350 """
351
352 return exclusions.open()
353
354 @property
355 def sane_rowcount(self):
356 return exclusions.skip_if(
357 lambda config: not config.db.dialect.supports_sane_rowcount,
358 "driver doesn't support 'sane' rowcount",
359 )
360
361 @property
362 def sane_multi_rowcount(self):
363 return exclusions.fails_if(
364 lambda config: not config.db.dialect.supports_sane_multi_rowcount,
365 "driver %(driver)s %(doesnt_support)s 'sane' multi row count",
366 )
367
368 @property
369 def sane_rowcount_w_returning(self):
370 return exclusions.fails_if(
371 lambda config: not (
372 config.db.dialect.supports_sane_rowcount_returning
373 ),
374 "driver doesn't support 'sane' rowcount when returning is on",
375 )
376
377 @property
378 def empty_inserts(self):
379 """target platform supports INSERT with no values, i.e.
380 INSERT DEFAULT VALUES or equivalent."""
381
382 return exclusions.only_if(
383 lambda config: config.db.dialect.supports_empty_insert
384 or config.db.dialect.supports_default_values
385 or config.db.dialect.supports_default_metavalue,
386 "empty inserts not supported",
387 )
388
389 @property
390 def empty_inserts_executemany(self):
391 """target platform supports INSERT with no values, i.e.
392 INSERT DEFAULT VALUES or equivalent, within executemany()"""
393
394 return self.empty_inserts
395
396 @property
397 def insert_from_select(self):
398 """target platform supports INSERT from a SELECT."""
399
400 return exclusions.open()
401
402 @property
403 def delete_returning(self):
404 """target platform supports DELETE ... RETURNING."""
405
406 return exclusions.only_if(
407 lambda config: config.db.dialect.delete_returning,
408 "%(database)s %(does_support)s 'DELETE ... RETURNING'",
409 )
410
411 @property
412 def insert_returning(self):
413 """target platform supports INSERT ... RETURNING."""
414
415 return exclusions.only_if(
416 lambda config: config.db.dialect.insert_returning,
417 "%(database)s %(does_support)s 'INSERT ... RETURNING'",
418 )
419
420 @property
421 def update_returning(self):
422 """target platform supports UPDATE ... RETURNING."""
423
424 return exclusions.only_if(
425 lambda config: config.db.dialect.update_returning,
426 "%(database)s %(does_support)s 'UPDATE ... RETURNING'",
427 )
428
429 @property
430 def insert_executemany_returning(self):
431 """target platform supports RETURNING when INSERT is used with
432 executemany(), e.g. multiple parameter sets, indicating
433 as many rows come back as do parameter sets were passed.
434
435 """
436
437 return exclusions.only_if(
438 lambda config: config.db.dialect.insert_executemany_returning,
439 "%(database)s %(does_support)s 'RETURNING of "
440 "multiple rows with INSERT executemany'",
441 )
442
443 @property
444 def insertmanyvalues(self):
445 return exclusions.only_if(
446 lambda config: config.db.dialect.supports_multivalues_insert
447 and config.db.dialect.insert_returning
448 and config.db.dialect.use_insertmanyvalues,
449 "%(database)s %(does_support)s 'insertmanyvalues functionality",
450 )
451
452 @property
453 def tuple_in(self):
454 """Target platform supports the syntax
455 "(x, y) IN ((x1, y1), (x2, y2), ...)"
456 """
457
458 return exclusions.closed()
459
460 @property
461 def tuple_in_w_empty(self):
462 """Target platform tuple IN w/ empty set"""
463 return self.tuple_in
464
465 @property
466 def duplicate_names_in_cursor_description(self):
467 """target platform supports a SELECT statement that has
468 the same name repeated more than once in the columns list."""
469
470 return exclusions.open()
471
472 @property
473 def denormalized_names(self):
474 """Target database must have 'denormalized', i.e.
475 UPPERCASE as case insensitive names."""
476
477 return exclusions.skip_if(
478 lambda config: not config.db.dialect.requires_name_normalize,
479 "Backend does not require denormalized names.",
480 )
481
482 @property
483 def multivalues_inserts(self):
484 """target database must support multiple VALUES clauses in an
485 INSERT statement."""
486
487 return exclusions.skip_if(
488 lambda config: not config.db.dialect.supports_multivalues_insert,
489 "Backend does not support multirow inserts.",
490 )
491
492 @property
493 def implements_get_lastrowid(self):
494 """target dialect implements the executioncontext.get_lastrowid()
495 method without reliance on RETURNING.
496
497 """
498 return exclusions.open()
499
500 @property
501 def arraysize(self):
502 """dialect includes the required pep-249 attribute
503 ``cursor.arraysize``"""
504
505 return exclusions.open()
506
507 @property
508 def emulated_lastrowid(self):
509 """target dialect retrieves cursor.lastrowid, or fetches
510 from a database-side function after an insert() construct executes,
511 within the get_lastrowid() method.
512
513 Only dialects that "pre-execute", or need RETURNING to get last
514 inserted id, would return closed/fail/skip for this.
515
516 """
517 return exclusions.closed()
518
519 @property
520 def emulated_lastrowid_even_with_sequences(self):
521 """target dialect retrieves cursor.lastrowid or an equivalent
522 after an insert() construct executes, even if the table has a
523 Sequence on it.
524
525 """
526 return exclusions.closed()
527
528 @property
529 def dbapi_lastrowid(self):
530 """target platform includes a 'lastrowid' accessor on the DBAPI
531 cursor object.
532
533 """
534 return exclusions.closed()
535
536 @property
537 def views(self):
538 """Target database must support VIEWs."""
539
540 return exclusions.closed()
541
542 @property
543 def schemas(self):
544 """Target database must support external schemas, and have one
545 named 'test_schema'."""
546
547 return only_on(lambda config: config.db.dialect.supports_schemas)
548
549 @property
550 def cross_schema_fk_reflection(self):
551 """target system must support reflection of inter-schema
552 foreign keys"""
553 return exclusions.closed()
554
555 @property
556 def foreign_key_constraint_name_reflection(self):
557 """Target supports reflection of FOREIGN KEY constraints and
558 will return the name of the constraint that was used in the
559 "CONSTRAINT <name> FOREIGN KEY" DDL.
560
561 MySQL prior to version 8 and MariaDB prior to version 10.5
562 don't support this.
563
564 """
565 return exclusions.closed()
566
567 @property
568 def implicit_default_schema(self):
569 """target system has a strong concept of 'default' schema that can
570 be referred to implicitly.
571
572 basically, PostgreSQL.
573
574 """
575 return exclusions.closed()
576
577 @property
578 def default_schema_name_switch(self):
579 """target dialect implements provisioning module including
580 set_default_schema_on_connection"""
581
582 return exclusions.closed()
583
584 @property
585 def server_side_cursors(self):
586 """Target dialect must support server side cursors."""
587
588 return exclusions.only_if(
589 [lambda config: config.db.dialect.supports_server_side_cursors],
590 "no server side cursors support",
591 )
592
593 @property
594 def sequences(self):
595 """Target database must support SEQUENCEs."""
596
597 return exclusions.only_if(
598 [lambda config: config.db.dialect.supports_sequences],
599 "no sequence support",
600 )
601
602 @property
603 def no_sequences(self):
604 """the opposite of "sequences", DB does not support sequences at
605 all."""
606
607 return exclusions.NotPredicate(self.sequences)
608
609 @property
610 def sequences_optional(self):
611 """Target database supports sequences, but also optionally
612 as a means of generating new PK values."""
613
614 return exclusions.only_if(
615 [
616 lambda config: config.db.dialect.supports_sequences
617 and config.db.dialect.sequences_optional
618 ],
619 "no sequence support, or sequences not optional",
620 )
621
622 @property
623 def supports_lastrowid(self):
624 """target database / driver supports cursor.lastrowid as a means
625 of retrieving the last inserted primary key value.
626
627 note that if the target DB supports sequences also, this is still
628 assumed to work. This is a new use case brought on by MariaDB 10.3.
629
630 """
631 return exclusions.only_if(
632 [lambda config: config.db.dialect.postfetch_lastrowid]
633 )
634
635 @property
636 def no_lastrowid_support(self):
637 """the opposite of supports_lastrowid"""
638 return exclusions.only_if(
639 [lambda config: not config.db.dialect.postfetch_lastrowid]
640 )
641
642 @property
643 def reflects_pk_names(self):
644 return exclusions.closed()
645
646 @property
647 def table_reflection(self):
648 """target database has general support for table reflection"""
649 return exclusions.open()
650
651 @property
652 def reflect_tables_no_columns(self):
653 """target database supports creation and reflection of tables with no
654 columns, or at least tables that seem to have no columns."""
655
656 return exclusions.closed()
657
658 @property
659 def comment_reflection(self):
660 """Indicates if the database support table comment reflection"""
661 return exclusions.closed()
662
663 @property
664 def comment_reflection_full_unicode(self):
665 """Indicates if the database support table comment reflection in the
666 full unicode range, including emoji etc.
667 """
668 return exclusions.closed()
669
670 @property
671 def constraint_comment_reflection(self):
672 """indicates if the database support comments on constraints
673 and their reflection"""
674 return exclusions.closed()
675
676 @property
677 def view_column_reflection(self):
678 """target database must support retrieval of the columns in a view,
679 similarly to how a table is inspected.
680
681 This does not include the full CREATE VIEW definition.
682
683 """
684 return self.views
685
686 @property
687 def view_reflection(self):
688 """target database must support inspection of the full CREATE VIEW
689 definition."""
690 return self.views
691
692 @property
693 def schema_reflection(self):
694 return self.schemas
695
696 @property
697 def schema_create_delete(self):
698 """target database supports schema create and dropped with
699 'CREATE SCHEMA' and 'DROP SCHEMA'"""
700 return exclusions.closed()
701
702 @property
703 def primary_key_constraint_reflection(self):
704 return exclusions.open()
705
706 @property
707 def foreign_key_constraint_reflection(self):
708 return exclusions.open()
709
710 @property
711 def foreign_key_constraint_option_reflection_ondelete(self):
712 return exclusions.closed()
713
714 @property
715 def fk_constraint_option_reflection_ondelete_restrict(self):
716 return exclusions.closed()
717
718 @property
719 def fk_constraint_option_reflection_ondelete_noaction(self):
720 return exclusions.closed()
721
722 @property
723 def foreign_key_constraint_option_reflection_onupdate(self):
724 return exclusions.closed()
725
726 @property
727 def fk_constraint_option_reflection_onupdate_restrict(self):
728 return exclusions.closed()
729
730 @property
731 def temp_table_reflection(self):
732 return exclusions.open()
733
734 @property
735 def temp_table_reflect_indexes(self):
736 return self.temp_table_reflection
737
738 @property
739 def temp_table_names(self):
740 """target dialect supports listing of temporary table names"""
741 return exclusions.closed()
742
743 @property
744 def has_temp_table(self):
745 """target dialect supports checking a single temp table name"""
746 return exclusions.closed()
747
748 @property
749 def temporary_tables(self):
750 """target database supports temporary tables"""
751 return exclusions.open()
752
753 @property
754 def temporary_views(self):
755 """target database supports temporary views"""
756 return exclusions.closed()
757
758 @property
759 def index_reflection(self):
760 return exclusions.open()
761
762 @property
763 def index_reflects_included_columns(self):
764 return exclusions.closed()
765
766 @property
767 def indexes_with_ascdesc(self):
768 """target database supports CREATE INDEX with per-column ASC/DESC."""
769 return exclusions.open()
770
771 @property
772 def reflect_indexes_with_ascdesc(self):
773 """target database supports reflecting INDEX with per-column
774 ASC/DESC."""
775 return exclusions.open()
776
777 @property
778 def reflect_indexes_with_ascdesc_as_expression(self):
779 """target database supports reflecting INDEX with per-column
780 ASC/DESC but reflects them as expressions (like oracle)."""
781 return exclusions.closed()
782
783 @property
784 def indexes_with_expressions(self):
785 """target database supports CREATE INDEX against SQL expressions."""
786 return exclusions.closed()
787
788 @property
789 def reflect_indexes_with_expressions(self):
790 """target database supports reflection of indexes with
791 SQL expressions."""
792 return exclusions.closed()
793
794 @property
795 def unique_constraint_reflection(self):
796 """target dialect supports reflection of unique constraints"""
797 return exclusions.open()
798
799 @property
800 def check_constraint_reflection(self):
801 """target dialect supports reflection of check constraints"""
802 return exclusions.closed()
803
804 @property
805 def duplicate_key_raises_integrity_error(self):
806 """target dialect raises IntegrityError when reporting an INSERT
807 with a primary key violation. (hint: it should)
808
809 """
810 return exclusions.open()
811
812 @property
813 def unbounded_varchar(self):
814 """Target database must support VARCHAR with no length"""
815
816 return exclusions.open()
817
818 @property
819 def unicode_data_no_special_types(self):
820 """Target database/dialect can receive / deliver / compare data with
821 non-ASCII characters in plain VARCHAR, TEXT columns, without the need
822 for special "national" datatypes like NVARCHAR or similar.
823
824 """
825 return exclusions.open()
826
827 @property
828 def unicode_data(self):
829 """Target database/dialect must support Python unicode objects with
830 non-ASCII characters represented, delivered as bound parameters
831 as well as in result rows.
832
833 """
834 return exclusions.open()
835
836 @property
837 def unicode_ddl(self):
838 """Target driver must support some degree of non-ascii symbol
839 names.
840 """
841 return exclusions.closed()
842
843 @property
844 def symbol_names_w_double_quote(self):
845 """Target driver can create tables with a name like 'some " table'"""
846 return exclusions.open()
847
848 @property
849 def datetime_interval(self):
850 """target dialect supports rendering of a datetime.timedelta as a
851 literal string, e.g. via the TypeEngine.literal_processor() method.
852
853 """
854 return exclusions.closed()
855
856 @property
857 def datetime_literals(self):
858 """target dialect supports rendering of a date, time, or datetime as a
859 literal string, e.g. via the TypeEngine.literal_processor() method.
860
861 """
862
863 return exclusions.closed()
864
865 @property
866 def datetime(self):
867 """target dialect supports representation of Python
868 datetime.datetime() objects."""
869
870 return exclusions.open()
871
872 @property
873 def datetime_timezone(self):
874 """target dialect supports representation of Python
875 datetime.datetime() with tzinfo with DateTime(timezone=True)."""
876
877 return exclusions.closed()
878
879 @property
880 def time_timezone(self):
881 """target dialect supports representation of Python
882 datetime.time() with tzinfo with Time(timezone=True)."""
883
884 return exclusions.closed()
885
886 @property
887 def date_implicit_bound(self):
888 """target dialect when given a date object will bind it such
889 that the database server knows the object is a date, and not
890 a plain string.
891
892 """
893 return exclusions.open()
894
895 @property
896 def time_implicit_bound(self):
897 """target dialect when given a time object will bind it such
898 that the database server knows the object is a time, and not
899 a plain string.
900
901 """
902 return exclusions.open()
903
904 @property
905 def datetime_implicit_bound(self):
906 """target dialect when given a datetime object will bind it such
907 that the database server knows the object is a datetime, and not
908 a plain string.
909
910 """
911 return exclusions.open()
912
913 @property
914 def datetime_microseconds(self):
915 """target dialect supports representation of Python
916 datetime.datetime() with microsecond objects."""
917
918 return exclusions.open()
919
920 @property
921 def timestamp_microseconds(self):
922 """target dialect supports representation of Python
923 datetime.datetime() with microsecond objects but only
924 if TIMESTAMP is used."""
925 return exclusions.closed()
926
927 @property
928 def timestamp_microseconds_implicit_bound(self):
929 """target dialect when given a datetime object which also includes
930 a microseconds portion when using the TIMESTAMP data type
931 will bind it such that the database server knows
932 the object is a datetime with microseconds, and not a plain string.
933
934 """
935 return self.timestamp_microseconds
936
937 @property
938 def datetime_historic(self):
939 """target dialect supports representation of Python
940 datetime.datetime() objects with historic (pre 1970) values."""
941
942 return exclusions.closed()
943
944 @property
945 def date(self):
946 """target dialect supports representation of Python
947 datetime.date() objects."""
948
949 return exclusions.open()
950
951 @property
952 def date_coerces_from_datetime(self):
953 """target dialect accepts a datetime object as the target
954 of a date column."""
955
956 return exclusions.open()
957
958 @property
959 def date_historic(self):
960 """target dialect supports representation of Python
961 datetime.datetime() objects with historic (pre 1970) values."""
962
963 return exclusions.closed()
964
965 @property
966 def time(self):
967 """target dialect supports representation of Python
968 datetime.time() objects."""
969
970 return exclusions.open()
971
972 @property
973 def time_microseconds(self):
974 """target dialect supports representation of Python
975 datetime.time() with microsecond objects."""
976
977 return exclusions.open()
978
979 @property
980 def binary_comparisons(self):
981 """target database/driver can allow BLOB/BINARY fields to be compared
982 against a bound parameter value.
983 """
984
985 return exclusions.open()
986
987 @property
988 def binary_literals(self):
989 """target backend supports simple binary literals, e.g. an
990 expression like::
991
992 SELECT CAST('foo' AS BINARY)
993
994 Where ``BINARY`` is the type emitted from :class:`.LargeBinary`,
995 e.g. it could be ``BLOB`` or similar.
996
997 Basically fails on Oracle.
998
999 """
1000
1001 return exclusions.open()
1002
1003 @property
1004 def autocommit(self):
1005 """target dialect supports 'AUTOCOMMIT' as an isolation_level"""
1006 return exclusions.closed()
1007
1008 @property
1009 def isolation_level(self):
1010 """target dialect supports general isolation level settings.
1011
1012 Note that this requirement, when enabled, also requires that
1013 the get_isolation_levels() method be implemented.
1014
1015 """
1016 return exclusions.closed()
1017
1018 def get_isolation_levels(self, config):
1019 """Return a structure of supported isolation levels for the current
1020 testing dialect.
1021
1022 The structure indicates to the testing suite what the expected
1023 "default" isolation should be, as well as the other values that
1024 are accepted. The dictionary has two keys, "default" and "supported".
1025 The "supported" key refers to a list of all supported levels and
1026 it should include AUTOCOMMIT if the dialect supports it.
1027
1028 If the :meth:`.DefaultRequirements.isolation_level` requirement is
1029 not open, then this method has no return value.
1030
1031 E.g.::
1032
1033 >>> testing.requirements.get_isolation_levels()
1034 {
1035 "default": "READ_COMMITTED",
1036 "supported": [
1037 "SERIALIZABLE", "READ UNCOMMITTED",
1038 "READ COMMITTED", "REPEATABLE READ",
1039 "AUTOCOMMIT"
1040 ]
1041 }
1042 """
1043 with config.db.connect() as conn:
1044 try:
1045 supported = conn.dialect.get_isolation_level_values(
1046 conn.connection.dbapi_connection
1047 )
1048 except NotImplementedError:
1049 return None
1050 else:
1051 return {
1052 "default": conn.dialect.default_isolation_level,
1053 "supported": supported,
1054 }
1055
1056 @property
1057 def get_isolation_level_values(self):
1058 """target dialect supports the
1059 :meth:`_engine.Dialect.get_isolation_level_values`
1060 method added in SQLAlchemy 2.0.
1061
1062 """
1063
1064 def go(config):
1065 with config.db.connect() as conn:
1066 try:
1067 conn.dialect.get_isolation_level_values(
1068 conn.connection.dbapi_connection
1069 )
1070 except NotImplementedError:
1071 return False
1072 else:
1073 return True
1074
1075 return exclusions.only_if(go)
1076
1077 @property
1078 def dialect_level_isolation_level_param(self):
1079 """test that the dialect allows the 'isolation_level' argument
1080 to be handled by DefaultDialect"""
1081
1082 def go(config):
1083 try:
1084 e = create_engine(
1085 config.db.url, isolation_level="READ COMMITTED"
1086 )
1087 except:
1088 return False
1089 else:
1090 return (
1091 e.dialect._on_connect_isolation_level == "READ COMMITTED"
1092 )
1093
1094 return exclusions.only_if(go)
1095
1096 @property
1097 def json_type(self):
1098 """target platform implements a native JSON type."""
1099
1100 return exclusions.closed()
1101
1102 @property
1103 def json_array_indexes(self):
1104 """target platform supports numeric array indexes
1105 within a JSON structure"""
1106
1107 return self.json_type
1108
1109 @property
1110 def json_index_supplementary_unicode_element(self):
1111 return exclusions.open()
1112
1113 @property
1114 def legacy_unconditional_json_extract(self):
1115 """Backend has a JSON_EXTRACT or similar function that returns a
1116 valid JSON string in all cases.
1117
1118 Used to test a legacy feature and is not needed.
1119
1120 """
1121 return exclusions.closed()
1122
1123 @property
1124 def precision_numerics_general(self):
1125 """target backend has general support for moderately high-precision
1126 numerics."""
1127 return exclusions.open()
1128
1129 @property
1130 def precision_numerics_enotation_small(self):
1131 """target backend supports Decimal() objects using E notation
1132 to represent very small values."""
1133 return exclusions.closed()
1134
1135 @property
1136 def precision_numerics_enotation_large(self):
1137 """target backend supports Decimal() objects using E notation
1138 to represent very large values."""
1139 return exclusions.open()
1140
1141 @property
1142 def precision_numerics_many_significant_digits(self):
1143 """target backend supports values with many digits on both sides,
1144 such as 319438950232418390.273596, 87673.594069654243
1145
1146 """
1147 return exclusions.closed()
1148
1149 @property
1150 def cast_precision_numerics_many_significant_digits(self):
1151 """same as precision_numerics_many_significant_digits but within the
1152 context of a CAST statement (hello MySQL)
1153
1154 """
1155 return self.precision_numerics_many_significant_digits
1156
1157 @property
1158 def implicit_decimal_binds(self):
1159 """target backend will return a selected Decimal as a Decimal, not
1160 a string.
1161
1162 e.g.::
1163
1164 expr = decimal.Decimal("15.7563")
1165
1166 value = e.scalar(
1167 select(literal(expr))
1168 )
1169
1170 assert value == expr
1171
1172 See :ticket:`4036`
1173
1174 """
1175
1176 return exclusions.open()
1177
1178 @property
1179 def numeric_received_as_decimal_untyped(self):
1180 """target backend will return result columns that are explicitly
1181 against NUMERIC or similar precision-numeric datatypes (not including
1182 FLOAT or INT types) as Python Decimal objects, and not as floats
1183 or ints, including when no SQLAlchemy-side typing information is
1184 associated with the statement (e.g. such as a raw SQL string).
1185
1186 This should be enabled if either the DBAPI itself returns Decimal
1187 objects, or if the dialect has set up DBAPI-specific return type
1188 handlers such that Decimal objects come back automatically.
1189
1190 """
1191 return exclusions.open()
1192
1193 @property
1194 def nested_aggregates(self):
1195 """target database can select an aggregate from a subquery that's
1196 also using an aggregate
1197
1198 """
1199 return exclusions.open()
1200
