| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918 |
- # testing/requirements.py
- # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- # mypy: ignore-errors
- """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/exclusions.
- """
- from __future__ import annotations
- import os
- import platform
- from . import asyncio as _test_asyncio
- from . import exclusions
- from .exclusions import only_on
- from .. import create_engine
- from .. import util
- from ..pool import QueuePool
- class Requirements:
- pass
- 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 table_ddl_if_exists(self):
- """target platform supports IF NOT EXISTS / IF EXISTS for tables."""
- return exclusions.closed()
- @property
- def index_ddl_if_exists(self):
- """target platform supports IF NOT EXISTS / IF EXISTS for indexes."""
- return exclusions.closed()
- @property
- def uuid_data_type(self):
- """Return databases that support the UUID datatype."""
- return exclusions.closed()
- @property
- def foreign_keys(self):
- """Target database must support foreign keys."""
- return exclusions.open()
- @property
- def foreign_keys_reflect_as_index(self):
- """Target database creates an index that's reflected for
- foreign keys."""
- return exclusions.closed()
- @property
- def unique_index_reflect_as_unique_constraints(self):
- """Target database reflects unique indexes as unique constrains."""
- return exclusions.closed()
- @property
- def unique_constraints_reflect_as_index(self):
- """Target database reflects unique constraints as indexes."""
- return exclusions.closed()
- @property
- def table_value_constructor(self):
- """Database / dialect supports a query like:
- .. sourcecode:: sql
- SELECT * FROM VALUES ( (c1, c2), (c1, c2), ...)
- AS some_table(col1, col2)
- SQLAlchemy generates this with the :func:`_sql.values` function.
- """
- return exclusions.closed()
- @property
- def standard_cursor_sql(self):
- """Target database passes SQL-92 style statements to cursor.execute()
- when a statement like select() or insert() is run.
- A very small portion of dialect-level tests will ensure that certain
- conditions are present in SQL strings, and these tests use very basic
- SQL that will work on any SQL-like platform in order to assert results.
- It's normally a given for any pep-249 DBAPI that a statement like
- "SELECT id, name FROM table WHERE some_table.id=5" will work.
- However, there are dialects that don't actually produce SQL Strings
- and instead may work with symbolic objects instead, or dialects that
- aren't working with SQL, so for those this requirement can be marked
- as excluded.
- """
- 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 non_updating_cascade(self):
- """target database must *not* support ON UPDATE..CASCADE behavior in
- foreign keys."""
- return exclusions.closed()
- @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 queue_pool(self):
- """target database is using QueuePool"""
- def go(config):
- return isinstance(config.db.pool, QueuePool)
- return exclusions.only_if(go)
- @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 implicitly_named_constraints(self):
- """target database must apply names to unnamed constraints."""
- return exclusions.open()
- @property
- def unusual_column_name_characters(self):
- """target database allows column names that have unusual characters
- in them, such as dots, spaces, slashes, or percent signs.
- The column names are as always in such a case quoted, however the
- DB still needs to support those characters in the name somehow.
- """
- 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 bound_limit_offset(self):
- """target database can render LIMIT and/or OFFSET using a bound
- parameter
- """
- return exclusions.open()
- @property
- def sql_expression_limit_offset(self):
- """target database can render LIMIT and/or OFFSET with a complete
- SQL expression, such as one that uses the addition operator.
- parameter
- """
- return exclusions.open()
- @property
- def parens_in_union_contained_select_w_limit_offset(self):
- """Target database must support parenthesized SELECT in UNION
- when LIMIT/OFFSET is specifically present.
- E.g. (SELECT ...) UNION (SELECT ..)
- This is known to fail on SQLite.
- """
- return exclusions.open()
- @property
- def parens_in_union_contained_select_wo_limit_offset(self):
- """Target database must support parenthesized SELECT in UNION
- when OFFSET/LIMIT is specifically not present.
- E.g. (SELECT ... LIMIT ..) UNION (SELECT .. OFFSET ..)
- This is known to fail on SQLite. It also fails on Oracle
- because without LIMIT/OFFSET, there is currently no step that
- creates an additional subquery.
- """
- return exclusions.open()
- @property
- def boolean_col_expressions(self):
- """Target database must support boolean expressions as columns"""
- return exclusions.closed()
- @property
- def nullable_booleans(self):
- """Target database allows boolean columns to store NULL."""
- return exclusions.open()
- @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.open()
- @property
- def standalone_null_binds_whereclause(self):
- """target database/driver supports bound parameters with NULL in the
- WHERE clause, in situations where it has to be typed.
- """
- return exclusions.open()
- @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 ctes(self):
- """Target database supports CTEs"""
- return exclusions.closed()
- @property
- def ctes_with_update_delete(self):
- """target database supports CTES that ride on top of a normal UPDATE
- or DELETE statement which refers to the CTE in a correlated subquery.
- """
- return exclusions.closed()
- @property
- def ctes_with_values(self):
- """target database supports CTES that ride on top of a VALUES
- clause."""
- return exclusions.closed()
- @property
- def ctes_on_dml(self):
- """target database supports CTES which consist of INSERT, UPDATE
- or DELETE *within* the CTE, e.g. WITH x AS (UPDATE....)"""
- 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 fetch_rows_post_commit(self):
- """target platform will allow cursor.fetchone() to proceed after a
- COMMIT.
- Typically this refers to an INSERT statement with RETURNING which
- is invoked within "autocommit". If the row can be returned
- after the autocommit, then this rule can be open.
- """
- return exclusions.open()
- @property
- def group_by_complex_expression(self):
- """target platform supports SQL expressions in GROUP BY
- e.g.
- SELECT x + y AS somelabel FROM table GROUP BY x + y
- """
- return exclusions.open()
- @property
- def sane_rowcount(self):
- return exclusions.skip_if(
- lambda config: not config.db.dialect.supports_sane_rowcount,
- "driver doesn't support 'sane' rowcount",
- )
- @property
- def sane_multi_rowcount(self):
- return exclusions.fails_if(
- lambda config: not config.db.dialect.supports_sane_multi_rowcount,
- "driver %(driver)s %(doesnt_support)s 'sane' multi row count",
- )
- @property
- def sane_rowcount_w_returning(self):
- return exclusions.fails_if(
- lambda config: not (
- config.db.dialect.supports_sane_rowcount_returning
- ),
- "driver doesn't support 'sane' rowcount when returning is on",
- )
- @property
- def empty_inserts(self):
- """target platform supports INSERT with no values, i.e.
- INSERT DEFAULT VALUES or equivalent."""
- return exclusions.only_if(
- lambda config: config.db.dialect.supports_empty_insert
- or config.db.dialect.supports_default_values
- or config.db.dialect.supports_default_metavalue,
- "empty inserts not supported",
- )
- @property
- def empty_inserts_executemany(self):
- """target platform supports INSERT with no values, i.e.
- INSERT DEFAULT VALUES or equivalent, within executemany()"""
- return self.empty_inserts
- @property
- def insert_from_select(self):
- """target platform supports INSERT from a SELECT."""
- return exclusions.open()
- @property
- def delete_returning(self):
- """target platform supports DELETE ... RETURNING."""
- return exclusions.only_if(
- lambda config: config.db.dialect.delete_returning,
- "%(database)s %(does_support)s 'DELETE ... RETURNING'",
- )
- @property
- def insert_returning(self):
- """target platform supports INSERT ... RETURNING."""
- return exclusions.only_if(
- lambda config: config.db.dialect.insert_returning,
- "%(database)s %(does_support)s 'INSERT ... RETURNING'",
- )
- @property
- def update_returning(self):
- """target platform supports UPDATE ... RETURNING."""
- return exclusions.only_if(
- lambda config: config.db.dialect.update_returning,
- "%(database)s %(does_support)s 'UPDATE ... RETURNING'",
- )
- @property
- def insert_executemany_returning(self):
- """target platform supports RETURNING when INSERT is used with
- executemany(), e.g. multiple parameter sets, indicating
- as many rows come back as do parameter sets were passed.
- """
- return exclusions.only_if(
- lambda config: config.db.dialect.insert_executemany_returning,
- "%(database)s %(does_support)s 'RETURNING of "
- "multiple rows with INSERT executemany'",
- )
- @property
- def insertmanyvalues(self):
- return exclusions.only_if(
- lambda config: config.db.dialect.supports_multivalues_insert
- and config.db.dialect.insert_returning
- and config.db.dialect.use_insertmanyvalues,
- "%(database)s %(does_support)s 'insertmanyvalues functionality",
- )
- @property
- def tuple_in(self):
- """Target platform supports the syntax
- "(x, y) IN ((x1, y1), (x2, y2), ...)"
- """
- return exclusions.closed()
- @property
- def tuple_in_w_empty(self):
- """Target platform tuple IN w/ empty set"""
- return self.tuple_in
- @property
- def duplicate_names_in_cursor_description(self):
- """target platform supports a SELECT statement that has
- the same name repeated more than once in the columns list."""
- return exclusions.open()
- @property
- def denormalized_names(self):
- """Target database must have 'denormalized', i.e.
- UPPERCASE as case insensitive names."""
- return exclusions.skip_if(
- lambda config: not config.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 config: not config.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 arraysize(self):
- """dialect includes the required pep-249 attribute
- ``cursor.arraysize``"""
- 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 emulated_lastrowid_even_with_sequences(self):
- """target dialect retrieves cursor.lastrowid or an equivalent
- after an insert() construct executes, even if the table has a
- Sequence on it.
- """
- 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 only_on(lambda config: config.db.dialect.supports_schemas)
- @property
- def cross_schema_fk_reflection(self):
- """target system must support reflection of inter-schema
- foreign keys"""
- return exclusions.closed()
- @property
- def foreign_key_constraint_name_reflection(self):
- """Target supports reflection of FOREIGN KEY constraints and
- will return the name of the constraint that was used in the
- "CONSTRAINT <name> FOREIGN KEY" DDL.
- MySQL prior to version 8 and MariaDB prior to version 10.5
- don't support this.
- """
- return exclusions.closed()
- @property
- def implicit_default_schema(self):
- """target system has a strong concept of 'default' schema that can
- be referred to implicitly.
- basically, PostgreSQL.
- """
- return exclusions.closed()
- @property
- def default_schema_name_switch(self):
- """target dialect implements provisioning module including
- set_default_schema_on_connection"""
- return exclusions.closed()
- @property
- def server_side_cursors(self):
- """Target dialect must support server side cursors."""
- return exclusions.only_if(
- [lambda config: config.db.dialect.supports_server_side_cursors],
- "no server side cursors support",
- )
- @property
- def sequences(self):
- """Target database must support SEQUENCEs."""
- return exclusions.only_if(
- [lambda config: config.db.dialect.supports_sequences],
- "no sequence support",
- )
- @property
- def no_sequences(self):
- """the opposite of "sequences", DB does not support sequences at
- all."""
- return exclusions.NotPredicate(self.sequences)
- @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 config: config.db.dialect.supports_sequences
- and config.db.dialect.sequences_optional
- ],
- "no sequence support, or sequences not optional",
- )
- @property
- def supports_lastrowid(self):
- """target database / driver supports cursor.lastrowid as a means
- of retrieving the last inserted primary key value.
- note that if the target DB supports sequences also, this is still
- assumed to work. This is a new use case brought on by MariaDB 10.3.
- """
- return exclusions.only_if(
- [lambda config: config.db.dialect.postfetch_lastrowid]
- )
- @property
- def no_lastrowid_support(self):
- """the opposite of supports_lastrowid"""
- return exclusions.only_if(
- [lambda config: not config.db.dialect.postfetch_lastrowid]
- )
- @property
- def reflects_pk_names(self):
- return exclusions.closed()
- @property
- def table_reflection(self):
- """target database has general support for table reflection"""
- return exclusions.open()
- @property
- def reflect_tables_no_columns(self):
- """target database supports creation and reflection of tables with no
- columns, or at least tables that seem to have no columns."""
- return exclusions.closed()
- @property
- def temp_table_comment_reflection(self):
- """indicates if database supports comments on temp tables and
- the dialect can reflect them"""
- return exclusions.closed()
- @property
- def comment_reflection(self):
- """Indicates if the database support table comment reflection"""
- return exclusions.closed()
- @property
- def comment_reflection_full_unicode(self):
- """Indicates if the database support table comment reflection in the
- full unicode range, including emoji etc.
- """
- return exclusions.closed()
- @property
- def constraint_comment_reflection(self):
- """indicates if the database support comments on constraints
- and their reflection"""
- return exclusions.closed()
- @property
- def view_column_reflection(self):
- """target database must support retrieval of the columns in a view,
- similarly to how a table is inspected.
- This does not include the full CREATE VIEW definition.
- """
- return self.views
- @property
- def view_reflection(self):
- """target database must support inspection of the full CREATE VIEW
- definition."""
- return self.views
- @property
- def schema_reflection(self):
- return self.schemas
- @property
- def schema_create_delete(self):
- """target database supports schema create and dropped with
- 'CREATE SCHEMA' and 'DROP SCHEMA'"""
- return exclusions.closed()
- @property
- def primary_key_constraint_reflection(self):
- return exclusions.open()
- @property
- def foreign_key_constraint_reflection(self):
- return exclusions.open()
- @property
- def foreign_key_constraint_option_reflection_ondelete(self):
- return exclusions.closed()
- @property
- def fk_constraint_option_reflection_ondelete_restrict(self):
- return exclusions.closed()
- @property
- def fk_constraint_option_reflection_ondelete_noaction(self):
- return exclusions.closed()
- @property
- def foreign_key_constraint_option_reflection_onupdate(self):
- return exclusions.closed()
- @property
- def fk_constraint_option_reflection_onupdate_restrict(self):
- return exclusions.closed()
- @property
- def temp_table_reflection(self):
- return exclusions.open()
- @property
- def temp_table_reflect_indexes(self):
- return self.temp_table_reflection
- @property
- def temp_table_names(self):
- """target dialect supports listing of temporary table names"""
- return exclusions.closed()
- @property
- def has_temp_table(self):
- """target dialect supports checking a single temp table name"""
- return exclusions.closed()
- @property
- def temporary_tables(self):
- """target database supports temporary tables"""
- return exclusions.open()
- @property
- def temporary_views(self):
- """target database supports temporary views"""
- return exclusions.closed()
- @property
- def index_reflection(self):
- return exclusions.open()
- @property
- def index_reflects_included_columns(self):
- return exclusions.closed()
- @property
- def indexes_with_ascdesc(self):
- """target database supports CREATE INDEX with per-column ASC/DESC."""
- return exclusions.open()
- @property
- def reflect_indexes_with_ascdesc(self):
- """target database supports reflecting INDEX with per-column
- ASC/DESC."""
- return exclusions.open()
- @property
- def reflect_indexes_with_ascdesc_as_expression(self):
- """target database supports reflecting INDEX with per-column
- ASC/DESC but reflects them as expressions (like oracle)."""
- return exclusions.closed()
- @property
- def indexes_with_expressions(self):
- """target database supports CREATE INDEX against SQL expressions."""
- return exclusions.closed()
- @property
- def reflect_indexes_with_expressions(self):
- """target database supports reflection of indexes with
- SQL expressions."""
- return exclusions.closed()
- @property
- def unique_constraint_reflection(self):
- """target dialect supports reflection of unique constraints"""
- return exclusions.open()
- @property
- def inline_check_constraint_reflection(self):
- """target dialect supports reflection of inline check constraints"""
- return exclusions.closed()
- @property
- def check_constraint_reflection(self):
- """target dialect supports reflection of check constraints"""
- return exclusions.closed()
- @property
- def duplicate_key_raises_integrity_error(self):
- """target dialect raises IntegrityError when reporting an INSERT
- with a primary key violation. (hint: it should)
- """
- return exclusions.open()
- @property
- def unbounded_varchar(self):
- """Target database must support VARCHAR with no length"""
- return exclusions.open()
- @property
- def nvarchar_types(self):
- """target database supports NVARCHAR and NCHAR as an actual datatype"""
- return exclusions.closed()
- @property
- def unicode_data_no_special_types(self):
- """Target database/dialect can receive / deliver / compare data with
- non-ASCII characters in plain VARCHAR, TEXT columns, without the need
- for special "national" datatypes like NVARCHAR or similar.
- """
- 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 symbol_names_w_double_quote(self):
- """Target driver can create tables with a name like 'some " table'"""
- return exclusions.open()
- @property
- def datetime_interval(self):
- """target dialect supports rendering of a datetime.timedelta as a
- literal string, e.g. via the TypeEngine.literal_processor() method.
- """
- return exclusions.closed()
- @property
- def datetime_literals(self):
- """target dialect supports rendering of a date, time, or datetime as a
- literal string, e.g. via the TypeEngine.literal_processor() method.
- """
- return exclusions.closed()
- @property
- def datetime(self):
- """target dialect supports representation of Python
- datetime.datetime() objects."""
- return exclusions.open()
- @property
- def datetime_timezone(self):
- """target dialect supports representation of Python
- datetime.datetime() with tzinfo with DateTime(timezone=True)."""
- return exclusions.closed()
- @property
- def time_timezone(self):
- """target dialect supports representation of Python
- datetime.time() with tzinfo with Time(timezone=True)."""
- return exclusions.closed()
- @property
- def date_implicit_bound(self):
- """target dialect when given a date object will bind it such
- that the database server knows the object is a date, and not
- a plain string.
- """
- return exclusions.open()
- @property
- def time_implicit_bound(self):
- """target dialect when given a time object will bind it such
- that the database server knows the object is a time, and not
- a plain string.
- """
- return exclusions.open()
- @property
- def datetime_implicit_bound(self):
- """target dialect when given a datetime object will bind it such
- that the database server knows the object is a datetime, and not
- a plain string.
- """
- return exclusions.open()
- @property
- def datetime_microseconds(self):
- """target dialect supports representation of Python
- datetime.datetime() with microsecond objects."""
- return exclusions.open()
- @property
- def timestamp_microseconds(self):
- """target dialect supports representation of Python
- datetime.datetime() with microsecond objects but only
- if TIMESTAMP is used."""
- return exclusions.closed()
- @property
- def timestamp_microseconds_implicit_bound(self):
- """target dialect when given a datetime object which also includes
- a microseconds portion when using the TIMESTAMP data type
- will bind it such that the database server knows
- the object is a datetime with microseconds, and not a plain string.
- """
- return self.timestamp_microseconds
- @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_coerces_from_datetime(self):
- """target dialect accepts a datetime object as the target
- of a date column."""
- 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 binary_comparisons(self):
- """target database/driver can allow BLOB/BINARY fields to be compared
- against a bound parameter value.
- """
- return exclusions.open()
- @property
- def binary_literals(self):
- """target backend supports simple binary literals, e.g. an
- expression like:
- .. sourcecode:: sql
- SELECT CAST('foo' AS BINARY)
- Where ``BINARY`` is the type emitted from :class:`.LargeBinary`,
- e.g. it could be ``BLOB`` or similar.
- Basically fails on Oracle.
- """
- return exclusions.open()
- @property
- def autocommit(self):
- """target dialect supports 'AUTOCOMMIT' as an isolation_level"""
- return exclusions.closed()
- @property
- def skip_autocommit_rollback(self):
- """target dialect supports the detect_autocommit_setting() method and
- uses the default implementation of do_rollback()"""
- return exclusions.closed()
- @property
- def isolation_level(self):
- """target dialect supports general isolation level settings.
- Note that this requirement, when enabled, also requires that
- the get_isolation_levels() method be implemented.
- """
- return exclusions.closed()
- def get_isolation_levels(self, config):
- """Return a structure of supported isolation levels for the current
- testing dialect.
- The structure indicates to the testing suite what the expected
- "default" isolation should be, as well as the other values that
- are accepted. The dictionary has two keys, "default" and "supported".
- The "supported" key refers to a list of all supported levels and
- it should include AUTOCOMMIT if the dialect supports it.
- If the :meth:`.DefaultRequirements.isolation_level` requirement is
- not open, then this method has no return value.
- E.g.::
- >>> testing.requirements.get_isolation_levels()
- {
- "default": "READ_COMMITTED",
- "supported": [
- "SERIALIZABLE", "READ UNCOMMITTED",
- "READ COMMITTED", "REPEATABLE READ",
- "AUTOCOMMIT"
- ]
- }
- """
- with config.db.connect() as conn:
- try:
- supported = conn.dialect.get_isolation_level_values(
- conn.connection.dbapi_connection
- )
- except NotImplementedError:
- return None
- else:
- return {
- "default": conn.dialect.default_isolation_level,
- "supported": supported,
- }
- @property
- def get_isolation_level_values(self):
- """target dialect supports the
- :meth:`_engine.Dialect.get_isolation_level_values`
- method added in SQLAlchemy 2.0.
- """
- def go(config):
- with config.db.connect() as conn:
- try:
- conn.dialect.get_isolation_level_values(
- conn.connection.dbapi_connection
- )
- except NotImplementedError:
- return False
- else:
- return True
- return exclusions.only_if(go)
- @property
- def dialect_level_isolation_level_param(self):
- """test that the dialect allows the 'isolation_level' argument
- to be handled by DefaultDialect"""
- def go(config):
- try:
- e = create_engine(
- config.db.url, isolation_level="READ COMMITTED"
- )
- except:
- return False
- else:
- return (
- e.dialect._on_connect_isolation_level == "READ COMMITTED"
- )
- return exclusions.only_if(go)
- @property
- def array_type(self):
- """Target platform implements a native ARRAY type"""
- return exclusions.closed()
- @property
- def json_type(self):
- """target platform implements a native JSON type."""
- return exclusions.closed()
- @property
- def json_array_indexes(self):
- """target platform supports numeric array indexes
- within a JSON structure"""
- return self.json_type
- @property
- def json_index_supplementary_unicode_element(self):
- return exclusions.open()
- @property
- def legacy_unconditional_json_extract(self):
- """Backend has a JSON_EXTRACT or similar function that returns a
- valid JSON string in all cases.
- Used to test a legacy feature and is not needed.
- """
- return exclusions.closed()
- @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.open()
- @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 cast_precision_numerics_many_significant_digits(self):
- """same as precision_numerics_many_significant_digits but within the
- context of a CAST statement (hello MySQL)
- """
- return self.precision_numerics_many_significant_digits
- @property
- def server_defaults(self):
- """Target backend supports server side defaults for columns"""
- return exclusions.closed()
- @property
- def expression_server_defaults(self):
- """Target backend supports server side defaults with SQL expressions
- for columns"""
- return exclusions.closed()
- @property
- def implicit_decimal_binds(self):
- """target backend will return a selected Decimal as a Decimal, not
- a string.
- e.g.::
- expr = decimal.Decimal("15.7563")
- value = e.scalar(select(literal(expr)))
- assert value == expr
- See :ticket:`4036`
- """
- return exclusions.open()
- @property
- def numeric_received_as_decimal_untyped(self):
- """target backend will return result columns that are explicitly
- against NUMERIC or similar precision-numeric datatypes (not including
- FLOAT or INT types) as Python Decimal objects, and not as floats
- or ints, including when no SQLAlchemy-side typing information is
- associated with the statement (e.g. such as a raw SQL string).
- This should be enabled if either the DBAPI itself returns Decimal
- objects, or if the dialect has set up DBAPI-specific return type
- handlers such that Decimal objects come back automatically.
- """
- return exclusions.open()
- @property
- def nested_aggregates(self):
- """target database can select an aggregate from a subquery that's
- also using an aggregate
- """
- return exclusions.open()
- @property
- def recursive_fk_cascade(self):
- """target database must support ON DELETE CASCADE on a self-referential
- foreign key
- """
- return exclusions.open()
- @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 infinity_floats(self):
- """The Float type can persist and load float('inf'), float('-inf')."""
- return exclusions.closed()
- @property
- def float_or_double_precision_behaves_generically(self):
- return exclusions.closed()
- @property
- def precision_generic_float_type(self):
- """target backend will return native floating point numbers with at
- least seven decimal places when using the generic Float type.
- """
- return exclusions.open()
- @property
- def literal_float_coercion(self):
- """target backend will return the exact float value 15.7563
- with only four significant digits from this statement:
- SELECT :param
- where :param is the Python float 15.7563
- i.e. it does not return 15.75629997253418
- """
- return exclusions.open()
- @property
- def floats_to_four_decimals(self):
- """target backend can return a floating-point number with four
- significant digits (such as 15.7563) accurately
- (i.e. without FP inaccuracies, such as 15.75629997253418).
- """
- return exclusions.open()
- @property
- def fetch_null_from_numeric(self):
- """target backend doesn't crash when you try to select a NUMERIC
- value that has a value of NULL.
- Added to support Pyodbc bug #351.
- """
- return exclusions.open()
- @property
- def float_is_numeric(self):
- """target backend uses Numeric for Float/Dual"""
- return exclusions.open()
- @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 expressions_against_unbounded_text(self):
- """target database supports use of an unbounded textual field in a
- WHERE clause."""
- return exclusions.open()
- @property
- def selectone(self):
- """target driver must support the literal statement 'select 1'"""
- return exclusions.open()
- @property
- def savepoints(self):
- """Target database must support savepoints."""
- return exclusions.closed()
- @property
- def two_phase_transactions(self):
- """Target database must support two-phase transactions."""
- return exclusions.closed()
- @property
- def update_from(self):
- """Target must support UPDATE..FROM syntax"""
- return exclusions.closed()
- @property
- def delete_from(self):
- """Target must support DELETE FROM..FROM or DELETE..USING syntax"""
- return exclusions.closed()
- @property
- def update_where_target_in_subquery(self):
- """Target must support UPDATE (or DELETE) 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:
- .. sourcecode:: sql
- 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 percent_schema_names(self):
- """target backend supports weird identifiers with percent signs
- in them, e.g. 'some % column'.
- this is a very weird use case but often has problems because of
- DBAPIs that use python formatting. It's not a critical use
- case either.
- """
- return exclusions.closed()
- @property
- def order_by_col_from_union(self):
- """target database supports ordering by a column from a SELECT
- inside of a UNION
- E.g.:
- .. sourcecode:: sql
- (SELECT id, ...) UNION (SELECT id, ...) ORDER BY id
- """
- return exclusions.open()
- @property
- def order_by_label_with_expression(self):
- """target backend supports ORDER BY a column label within an
- expression.
- Basically this:
- .. sourcecode:: sql
- select data as foo from test order by foo || 'bar'
- Lots of databases including PostgreSQL don't support this,
- so this is off by default.
- """
- return exclusions.closed()
- @property
- def order_by_collation(self):
- def check(config):
- try:
- self.get_order_by_collation(config)
- return False
- except NotImplementedError:
- return True
- return exclusions.skip_if(check)
- def get_order_by_collation(self, config):
- raise NotImplementedError()
- @property
- def unicode_connections(self):
- """Target driver must support non-ASCII characters being passed at
- all.
- """
- return exclusions.open()
- @property
- def graceful_disconnects(self):
- """Target driver must raise a DBAPI-level exception, such as
- InterfaceError, when the underlying connection has been closed
- and the execute() method is called.
- """
- return exclusions.open()
- @property
- def independent_connections(self):
- """
- Target must support simultaneous, independent database connections.
- """
- return exclusions.open()
- @property
- def independent_readonly_connections(self):
- """
- Target must support simultaneous, independent database connections
- that will be used in a readonly fashion.
- """
- return exclusions.open()
- @property
- def skip_mysql_on_windows(self):
- """Catchall for a large variety of MySQL on Windows failures"""
- return exclusions.open()
- @property
- def ad_hoc_engines(self):
- """Test environment must allow ad-hoc engine/connection creation.
- DBs that scale poorly for many connections, even when closed, i.e.
- Oracle, may use the "--low-connections" option which flags this
- requirement as not present.
- """
- return exclusions.skip_if(
- lambda config: config.options.low_connections
- )
- @property
- def no_windows(self):
- return exclusions.skip_if(self._running_on_windows())
- def _running_on_windows(self):
- return exclusions.LambdaPredicate(
- lambda: platform.system() == "Windows",
- description="running on Windows",
- )
- @property
- def timing_intensive(self):
- from . import config
- return config.add_to_marker.timing_intensive
- @property
- def posix(self):
- return exclusions.skip_if(lambda: os.name != "posix")
- @property
- def memory_intensive(self):
- from . import config
- return config.add_to_marker.memory_intensive
- @property
- def threading_with_mock(self):
- """Mark tests that use threading and mock at the same time - stability
- issues have been observed with coverage
- """
- return exclusions.skip_if(
- lambda config: config.options.has_coverage,
- "Stability issues with coverage",
- )
- @property
- def sqlalchemy2_stubs(self):
- def check(config):
- try:
- __import__("sqlalchemy-stubs.ext.mypy")
- except ImportError:
- return False
- else:
- return True
- return exclusions.only_if(check)
- @property
- def no_sqlalchemy2_stubs(self):
- def check(config):
- try:
- __import__("sqlalchemy-stubs.ext.mypy")
- except ImportError:
- return False
- else:
- return True
- return exclusions.skip_if(check)
- @property
- def up_to_date_typealias_type(self):
- # this checks a particular quirk found in typing_extensions <=4.12.0
- # using older python versions like 3.10 or 3.9, we use TypeAliasType
- # from typing_extensions which does not provide for sufficient
- # introspection prior to 4.13.0
- def check(config):
- import typing
- import typing_extensions
- TypeAliasType = getattr(
- typing, "TypeAliasType", typing_extensions.TypeAliasType
- )
- TV = typing.TypeVar("TV")
- TA_generic = TypeAliasType( # type: ignore
- "TA_generic", typing.List[TV], type_params=(TV,)
- )
- return hasattr(TA_generic[int], "__value__")
- return exclusions.only_if(check)
- @property
- def python38(self):
- return exclusions.only_if(
- lambda: util.py38, "Python 3.8 or above required"
- )
- @property
- def python39(self):
- return exclusions.only_if(
- lambda: util.py39, "Python 3.9 or above required"
- )
- @property
- def python310(self):
- return exclusions.only_if(
- lambda: util.py310, "Python 3.10 or above required"
- )
- @property
- def python311(self):
- return exclusions.only_if(
- lambda: util.py311, "Python 3.11 or above required"
- )
- @property
- def python312(self):
- return exclusions.only_if(
- lambda: util.py312, "Python 3.12 or above required"
- )
- @property
- def fail_python314b1(self):
- return exclusions.fails_if(
- lambda: util.compat.py314b1, "Fails as of python 3.14.0b1"
- )
- @property
- def not_python314(self):
- """This requirement is interim to assist with backporting of
- issue #12405.
- SQLAlchemy 2.0 still includes the ``await_fallback()`` method that
- makes use of ``asyncio.get_event_loop_policy()``. This is removed
- in SQLAlchemy 2.1.
- """
- return exclusions.skip_if(
- lambda: util.py314, "Python 3.14 or above not supported"
- )
- @property
- def cpython(self):
- return exclusions.only_if(
- lambda: util.cpython, "cPython interpreter needed"
- )
- @property
- def is64bit(self):
- return exclusions.only_if(lambda: util.is64bit, "64bit required")
- @property
- def patch_library(self):
- def check_lib():
- try:
- __import__("patch")
- except ImportError:
- return False
- else:
- return True
- return exclusions.only_if(check_lib, "patch library needed")
- @property
- def predictable_gc(self):
- """target platform must remove all cycles unconditionally when
- gc.collect() is called, as well as clean out unreferenced subclasses.
- """
- return self.cpython
- @property
- def no_coverage(self):
- """Test should be skipped if coverage is enabled.
- This is to block tests that exercise libraries that seem to be
- sensitive to coverage, such as PostgreSQL notice logging.
- """
- return exclusions.skip_if(
- lambda config: config.options.has_coverage,
- "Issues observed when coverage is enabled",
- )
- def _has_mysql_on_windows(self, config):
- return False
- def _has_mysql_fully_case_sensitive(self, config):
- return False
- @property
- def sqlite(self):
- return exclusions.skip_if(lambda: not self._has_sqlite())
- @property
- def cextensions(self):
- return exclusions.skip_if(
- lambda: not util.has_compiled_ext(),
- "Cython extensions not installed",
- )
- def _has_sqlite(self):
- from sqlalchemy import create_engine
- try:
- create_engine("sqlite://")
- return True
- except ImportError:
- return False
- @property
- def async_dialect(self):
- """dialect makes use of await_() to invoke operations on the DBAPI."""
- return exclusions.closed()
- @property
- def asyncio(self):
- return self.greenlet
- @property
- def no_greenlet(self):
- def go(config):
- try:
- import greenlet # noqa: F401
- except ImportError:
- return True
- else:
- return False
- return exclusions.only_if(go)
- @property
- def greenlet(self):
- def go(config):
- if not _test_asyncio.ENABLE_ASYNCIO:
- return False
- try:
- import greenlet # noqa: F401
- except ImportError:
- return False
- else:
- return True
- return exclusions.only_if(go)
- @property
- def computed_columns(self):
- "Supports computed columns"
- return exclusions.closed()
- @property
- def computed_columns_stored(self):
- "Supports computed columns with `persisted=True`"
- return exclusions.closed()
- @property
- def computed_columns_virtual(self):
- "Supports computed columns with `persisted=False`"
- return exclusions.closed()
- @property
- def computed_columns_default_persisted(self):
- """If the default persistence is virtual or stored when `persisted`
- is omitted"""
- return exclusions.closed()
- @property
- def computed_columns_reflect_persisted(self):
- """If persistence information is returned by the reflection of
- computed columns"""
- return exclusions.closed()
- @property
- def supports_distinct_on(self):
- """If a backend supports the DISTINCT ON in a select"""
- return exclusions.closed()
- @property
- def supports_is_distinct_from(self):
- """Supports some form of "x IS [NOT] DISTINCT FROM y" construct.
- Different dialects will implement their own flavour, e.g.,
- sqlite will emit "x IS NOT y" instead of "x IS DISTINCT FROM y".
- .. seealso::
- :meth:`.ColumnOperators.is_distinct_from`
- """
- return exclusions.skip_if(
- lambda config: not config.db.dialect.supports_is_distinct_from,
- "driver doesn't support an IS DISTINCT FROM construct",
- )
- @property
- def identity_columns(self):
- """If a backend supports GENERATED { ALWAYS | BY DEFAULT }
- AS IDENTITY"""
- return exclusions.closed()
- @property
- def identity_columns_standard(self):
- """If a backend supports GENERATED { ALWAYS | BY DEFAULT }
- AS IDENTITY with a standard syntax.
- This is mainly to exclude MSSql.
- """
- return exclusions.closed()
- @property
- def regexp_match(self):
- """backend supports the regexp_match operator."""
- return exclusions.closed()
- @property
- def regexp_replace(self):
- """backend supports the regexp_replace operator."""
- return exclusions.closed()
- @property
- def fetch_first(self):
- """backend supports the fetch first clause."""
- return exclusions.closed()
- @property
- def fetch_percent(self):
- """backend supports the fetch first clause with percent."""
- return exclusions.closed()
- @property
- def fetch_ties(self):
- """backend supports the fetch first clause with ties."""
- return exclusions.closed()
- @property
- def fetch_no_order_by(self):
- """backend supports the fetch first without order by"""
- return exclusions.closed()
- @property
- def fetch_offset_with_options(self):
- """backend supports the offset when using fetch first with percent
- or ties. basically this is "not mssql"
- """
- return exclusions.closed()
- @property
- def fetch_expression(self):
- """backend supports fetch / offset with expression in them, like
- SELECT * FROM some_table
- OFFSET 1 + 1 ROWS FETCH FIRST 1 + 1 ROWS ONLY
- """
- return exclusions.closed()
- @property
- def autoincrement_without_sequence(self):
- """If autoincrement=True on a column does not require an explicit
- sequence. This should be false only for oracle.
- """
- return exclusions.open()
- @property
- def generic_classes(self):
- "If X[Y] can be implemented with ``__class_getitem__``. py3.7+"
- return exclusions.open()
- @property
- def json_deserializer_binary(self):
- "indicates if the json_deserializer function is called with bytes"
- return exclusions.closed()
- @property
- def reflect_table_options(self):
- """Target database must support reflecting table_options."""
- return exclusions.closed()
- @property
- def materialized_views(self):
- """Target database must support MATERIALIZED VIEWs."""
- return exclusions.closed()
- @property
- def materialized_views_reflect_pk(self):
- """Target database reflect MATERIALIZED VIEWs pks."""
- return exclusions.closed()
- @property
- def supports_bitwise_or(self):
- """Target database supports bitwise or"""
- return exclusions.closed()
- @property
- def supports_bitwise_and(self):
- """Target database supports bitwise and"""
- return exclusions.closed()
- @property
- def supports_bitwise_not(self):
- """Target database supports bitwise not"""
- return exclusions.closed()
- @property
- def supports_bitwise_xor(self):
- """Target database supports bitwise xor"""
- return exclusions.closed()
- @property
- def supports_bitwise_shift(self):
- """Target database supports bitwise left or right shift"""
- return exclusions.closed()
- @property
- def like_escapes(self):
- """Target backend supports custom ESCAPE characters
- with LIKE comparisons"""
- return exclusions.open()
|