| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551 |
- """Requirements specific to SQLAlchemy's own unit tests.
- """
- import sys
- from sqlalchemy import exc
- from sqlalchemy import util
- from sqlalchemy.sql import text
- from sqlalchemy.testing import exclusions
- from sqlalchemy.testing.exclusions import against
- from sqlalchemy.testing.exclusions import fails_if
- from sqlalchemy.testing.exclusions import fails_on
- from sqlalchemy.testing.exclusions import fails_on_everything_except
- from sqlalchemy.testing.exclusions import LambdaPredicate
- from sqlalchemy.testing.exclusions import NotPredicate
- from sqlalchemy.testing.exclusions import only_if
- from sqlalchemy.testing.exclusions import only_on
- from sqlalchemy.testing.exclusions import skip_if
- from sqlalchemy.testing.exclusions import SpecPredicate
- from sqlalchemy.testing.exclusions import succeeds_if
- from sqlalchemy.testing.requirements import SuiteRequirements
- def no_support(db, reason):
- return SpecPredicate(db, description=reason)
- def exclude(db, op, spec, description=None):
- return SpecPredicate(db, op, spec, description=description)
- class DefaultRequirements(SuiteRequirements):
- @property
- def deferrable_or_no_constraints(self):
- """Target database must support deferrable constraints."""
- return skip_if(
- [
- no_support("firebird", "not supported by database"),
- no_support("mysql", "not supported by database"),
- no_support("mssql", "not supported by database"),
- ]
- )
- @property
- def check_constraints(self):
- """Target database must support check constraints."""
- return exclusions.open()
- @property
- def enforces_check_constraints(self):
- """Target database must also enforce check constraints."""
- return self.check_constraints + fails_on(
- self._mysql_check_constraints_dont_exist,
- "check constraints don't enforce on MySQL, MariaDB<10.2",
- )
- @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 skip_if([no_support("sqlite", "not supported by database")])
- @property
- def foreign_keys(self):
- """Target database must support foreign keys."""
- return skip_if(no_support("sqlite", "not supported by database"))
- @property
- def on_update_cascade(self):
- """target database must support ON UPDATE..CASCADE behavior in
- foreign keys."""
- return skip_if(
- ["sqlite", "oracle"],
- "target backend %(doesnt_support)s ON UPDATE CASCADE",
- )
- @property
- def non_updating_cascade(self):
- """target database must *not* support ON UPDATE..CASCADE behavior in
- foreign keys."""
- return fails_on_everything_except(
- "sqlite", "oracle", "+zxjdbc"
- ) + skip_if("mssql")
- @property
- def recursive_fk_cascade(self):
- """target database must support ON DELETE CASCADE on a self-referential
- foreign key"""
- return skip_if(["mssql"])
- @property
- def deferrable_fks(self):
- """target database must support deferrable fks"""
- return only_on(["oracle"])
- @property
- def foreign_key_constraint_option_reflection_ondelete(self):
- return only_on(["postgresql", "mysql", "sqlite", "oracle"])
- @property
- def foreign_key_constraint_option_reflection_onupdate(self):
- return only_on(["postgresql", "mysql", "sqlite"])
- @property
- def comment_reflection(self):
- return only_on(["postgresql", "mysql", "oracle"])
- @property
- def unbounded_varchar(self):
- """Target database must support VARCHAR with no length"""
- return skip_if(
- ["firebird", "oracle", "mysql"], "not supported by database"
- )
- @property
- def boolean_col_expressions(self):
- """Target database must support boolean expressions as columns"""
- return skip_if(
- [
- no_support("firebird", "not supported by database"),
- no_support("oracle", "not supported by database"),
- no_support("mssql", "not supported by database"),
- no_support("sybase", "not supported by database"),
- ]
- )
- @property
- def non_native_boolean_unconstrained(self):
- """target database is not native boolean and allows arbitrary integers
- in it's "bool" column"""
- return skip_if(
- [
- LambdaPredicate(
- lambda config: against(config, "mssql"),
- "SQL Server drivers / odbc seem to change "
- "their mind on this",
- ),
- LambdaPredicate(
- lambda config: config.db.dialect.supports_native_boolean,
- "native boolean dialect",
- ),
- ]
- )
- @property
- def standalone_binds(self):
- """target database/driver supports bound parameters as column expressions
- without being in the context of a typed column.
- """
- return skip_if(["firebird", "mssql+mxodbc"], "not supported by driver")
- @property
- def no_quoting_special_bind_names(self):
- """Target database will quote bound parameter names, doesn't support
- EXPANDING"""
- return skip_if(["oracle"])
- @property
- def identity(self):
- """Target database must support GENERATED AS IDENTITY or a facsimile.
- Includes GENERATED AS IDENTITY, AUTOINCREMENT, AUTO_INCREMENT, or other
- column DDL feature that fills in a DB-generated identifier at
- INSERT-time without requiring pre-execution of a SEQUENCE or other
- artifact.
- """
- return skip_if(
- ["firebird", "oracle", "postgresql", "sybase"],
- "not supported by database",
- )
- @property
- def temporary_tables(self):
- """target database supports temporary tables"""
- return skip_if(
- ["mssql", "firebird", self._sqlite_file_db], "not supported (?)"
- )
- @property
- def temp_table_reflection(self):
- return self.temporary_tables
- @property
- def reflectable_autoincrement(self):
- """Target database must support tables that can automatically generate
- PKs assuming they were reflected.
- this is essentially all the DBs in "identity" plus PostgreSQL, which
- has SERIAL support. FB and Oracle (and sybase?) require the Sequence
- to be explicitly added, including if the table was reflected.
- """
- return skip_if(
- ["firebird", "oracle", "sybase"], "not supported by database"
- )
- @property
- def insert_from_select(self):
- return skip_if(["firebird"], "crashes for unknown reason")
- @property
- def fetch_rows_post_commit(self):
- return skip_if(["firebird"], "not supported")
- @property
- def non_broken_binary(self):
- """target DBAPI must work fully with binary values"""
- # see https://github.com/pymssql/pymssql/issues/504
- return skip_if(["mssql+pymssql"])
- @property
- def binary_comparisons(self):
- """target database/driver can allow BLOB/BINARY fields to be compared
- against a bound parameter value.
- """
- return skip_if(["oracle", "mssql"], "not supported by database/driver")
- @property
- def binary_literals(self):
- """target backend supports simple binary literals, e.g. an
- expression like::
- 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.
- """
- # adding mssql here since it doesn't support comparisons either,
- # have observed generally bad behavior with binary / mssql.
- return skip_if(["oracle", "mssql"], "not supported by database/driver")
- @property
- def tuple_in(self):
- def _sqlite_tuple_in(config):
- return against(
- config, "sqlite"
- ) and config.db.dialect.dbapi.sqlite_version_info >= (3, 15, 0)
- return only_on(["mysql", "postgresql", _sqlite_tuple_in])
- @property
- def independent_cursors(self):
- """Target must support simultaneous, independent database cursors
- on a single connection."""
- return skip_if(["mssql", "mysql"], "no driver support")
- @property
- def independent_connections(self):
- """
- Target must support simultaneous, independent database connections.
- """
- # This is also true of some configurations of UnixODBC and probably
- # win32 ODBC as well.
- return skip_if(
- [
- no_support(
- "sqlite",
- "independent connections disabled "
- "when :memory: connections are used",
- ),
- exclude(
- "mssql",
- "<",
- (9, 0, 0),
- "SQL Server 2005+ is required for "
- "independent connections",
- ),
- ]
- )
- @property
- def memory_process_intensive(self):
- """Driver is able to handle the memory tests which run in a subprocess
- and iterate through hundreds of connections
- """
- return skip_if(
- [
- no_support("oracle", "Oracle XE usually can't handle these"),
- no_support("mssql+pyodbc", "MS ODBC drivers struggle"),
- ]
- )
- @property
- def updateable_autoincrement_pks(self):
- """Target must support UPDATE on autoincrement/integer primary key."""
- return skip_if(
- ["mssql", "sybase"], "IDENTITY columns can't be updated"
- )
- @property
- def isolation_level(self):
- return only_on(
- ("postgresql", "sqlite", "mysql", "mssql", "oracle"),
- "DBAPI has no isolation level support",
- ) + fails_on(
- "postgresql+pypostgresql",
- "pypostgresql bombs on multiple isolation level calls",
- )
- def get_isolation_levels(self, config):
- levels = set(config.db.dialect._isolation_lookup)
- if against(config, "sqlite"):
- default = "SERIALIZABLE"
- levels.add("AUTOCOMMIT")
- elif against(config, "postgresql"):
- default = "READ COMMITTED"
- levels.add("AUTOCOMMIT")
- elif against(config, "mysql"):
- default = "REPEATABLE READ"
- levels.add("AUTOCOMMIT")
- elif against(config, "mssql"):
- default = "READ COMMITTED"
- levels.add("AUTOCOMMIT")
- elif against(config, "oracle"):
- default = "READ COMMITTED"
- levels.add("AUTOCOMMIT")
- else:
- raise NotImplementedError()
- return {"default": default, "supported": levels}
- @property
- def autocommit(self):
- """target dialect supports 'AUTOCOMMIT' as an isolation_level"""
- return self.isolation_level + only_if(
- lambda config: "AUTOCOMMIT"
- in self.get_isolation_levels(config)["supported"]
- )
- @property
- def row_triggers(self):
- """Target must support standard statement-running EACH ROW triggers."""
- return skip_if(
- [
- # no access to same table
- no_support("mysql", "requires SUPER priv"),
- exclude("mysql", "<", (5, 0, 10), "not supported by database"),
- # huh? TODO: implement triggers for PG tests, remove this
- no_support(
- "postgresql",
- "PG triggers need to be implemented for tests",
- ),
- ]
- )
- @property
- def sequences_as_server_defaults(self):
- """Target database must support SEQUENCE as a server side default."""
- return only_on(
- "postgresql", "doesn't support sequences as a server side default."
- )
- @property
- def sql_expressions_inserted_as_primary_key(self):
- return only_if([self.returning, self.sqlite])
- @property
- def computed_columns_on_update_returning(self):
- return self.computed_columns + skip_if("oracle")
- @property
- def correlated_outer_joins(self):
- """Target must support an outer join to a subquery which
- correlates to the parent."""
- return skip_if(
- "oracle",
- 'Raises "ORA-01799: a column may not be '
- 'outer-joined to a subquery"',
- )
- @property
- def update_from(self):
- """Target must support UPDATE..FROM syntax"""
- return only_on(
- ["postgresql", "mssql", "mysql"],
- "Backend does not support UPDATE..FROM",
- )
- @property
- def delete_from(self):
- """Target must support DELETE FROM..FROM or DELETE..USING syntax"""
- return only_on(
- ["postgresql", "mssql", "mysql", "sybase"],
- "Backend does not support DELETE..FROM",
- )
- @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::
- UPDATE documents SET flag=1 WHERE documents.title IN
- (SELECT max(documents.title) AS title
- FROM documents GROUP BY documents.user_id
- )
- """
- return fails_if(
- self._mysql_not_mariadb_103,
- 'MySQL error 1093 "Cant specify target table '
- 'for update in FROM clause", resolved by MariaDB 10.3',
- )
- @property
- def savepoints(self):
- """Target database must support savepoints."""
- return skip_if(
- ["sqlite", "sybase", ("mysql", "<", (5, 0, 3))],
- "savepoints not supported",
- )
- @property
- def savepoints_w_release(self):
- return self.savepoints + skip_if(
- ["oracle", "mssql"],
- "database doesn't support release of savepoint",
- )
- @property
- def schemas(self):
- """Target database must support external schemas, and have one
- named 'test_schema'."""
- return skip_if(["firebird"], "no schema support")
- @property
- def cross_schema_fk_reflection(self):
- """target system must support reflection of inter-schema foreign keys
- """
- return only_on(["postgresql", "mysql", "mssql"])
- @property
- def implicit_default_schema(self):
- """target system has a strong concept of 'default' schema that can
- be referred to implicitly.
- basically, PostgreSQL.
- """
- return only_on(["postgresql"])
- @property
- def unique_constraint_reflection(self):
- return fails_on_everything_except(
- "postgresql", "mysql", "sqlite", "oracle"
- )
- @property
- def unique_constraint_reflection_no_index_overlap(self):
- return (
- self.unique_constraint_reflection
- + skip_if("mysql")
- + skip_if("oracle")
- )
- @property
- def check_constraint_reflection(self):
- return fails_on_everything_except(
- "postgresql",
- "sqlite",
- "oracle",
- self._mysql_and_check_constraints_exist,
- )
- @property
- def indexes_with_expressions(self):
- return only_on(["postgresql", "sqlite>=3.9.0"])
- @property
- def temp_table_names(self):
- """target dialect supports listing of temporary table names"""
- return only_on(["sqlite", "oracle"]) + skip_if(self._sqlite_file_db)
- @property
- def temporary_views(self):
- """target database supports temporary views"""
- return only_on(["sqlite", "postgresql"]) + skip_if(
- self._sqlite_file_db
- )
- @property
- def update_nowait(self):
- """Target database must support SELECT...FOR UPDATE NOWAIT"""
- return skip_if(
- ["firebird", "mssql", "mysql", "sqlite", "sybase"],
- "no FOR UPDATE NOWAIT support",
- )
- @property
- def subqueries(self):
- """Target database must support subqueries."""
- return skip_if(exclude("mysql", "<", (4, 1, 1)), "no subquery support")
- @property
- def ctes(self):
- """Target database supports CTEs"""
- return only_on(
- [
- lambda config: against(config, "mysql")
- and (
- (
- config.db.dialect._is_mariadb
- and config.db.dialect._mariadb_normalized_version_info
- >= (10, 2)
- )
- or (
- not config.db.dialect._is_mariadb
- and config.db.dialect.server_version_info >= (8,)
- )
- ),
- "postgresql",
- "mssql",
- "oracle",
- "sqlite>=3.8.3",
- ]
- )
- @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 only_on(
- [
- "postgresql",
- "mssql",
- # "oracle" - oracle can do this but SQLAlchemy doesn't support
- # their syntax yet
- ]
- )
- @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 only_if(["postgresql"])
- @property
- def mod_operator_as_percent_sign(self):
- """target database must use a plain percent '%' as the 'modulus'
- operator."""
- return only_if(["mysql", "sqlite", "postgresql+psycopg2", "mssql"])
- @property
- def intersect(self):
- """Target database must support INTERSECT or equivalent."""
- return fails_if(
- ["firebird", self._mysql_not_mariadb_103, "sybase"],
- "no support for INTERSECT",
- )
- @property
- def except_(self):
- """Target database must support EXCEPT or equivalent (i.e. MINUS)."""
- return fails_if(
- ["firebird", self._mysql_not_mariadb_103, "sybase"],
- "no support for EXCEPT",
- )
- @property
- def order_by_col_from_union(self):
- """target database supports ordering by a column from a SELECT
- inside of a UNION
- E.g. (SELECT id, ...) UNION (SELECT id, ...) ORDER BY id
- Fails on SQL Server
- """
- return fails_if("mssql")
- @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 ... LIMIT ..) UNION (SELECT .. OFFSET ..)
- This is known to fail on SQLite.
- """
- return fails_if("sqlite")
- @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 ...) UNION (SELECT ..)
- 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 fails_if(["sqlite", "oracle"])
- @property
- def offset(self):
- """Target database must support some method of adding OFFSET or
- equivalent to a result set."""
- return fails_if(["sybase"], "no support for OFFSET or equivalent")
- @property
- def sql_expression_limit_offset(self):
- return (
- fails_if(
- ["mysql"],
- "Target backend can't accommodate full expressions in "
- "OFFSET or LIMIT",
- )
- + self.offset
- )
- @property
- def window_functions(self):
- return only_if(
- ["postgresql>=8.4", "mssql", "oracle", "sqlite>=3.25.0"],
- "Backend does not support window functions",
- )
- @property
- def two_phase_transactions(self):
- """Target database must support two-phase transactions."""
- def pg_prepared_transaction(config):
- if not against(config, "postgresql"):
- return False
- with config.db.connect() as conn:
- try:
- num = conn.scalar(
- text(
- "select cast(setting AS integer) from pg_settings "
- "where name = 'max_prepared_transactions'"
- )
- )
- except exc.OperationalError:
- return False
- else:
- return num > 0
- return skip_if(
- [
- no_support("firebird", "no SA implementation"),
- no_support("mssql", "two-phase xact not supported by drivers"),
- no_support(
- "oracle", "two-phase xact not implemented in SQLA/oracle"
- ),
- no_support(
- "drizzle", "two-phase xact not supported by database"
- ),
- no_support(
- "sqlite", "two-phase xact not supported by database"
- ),
- no_support(
- "sybase", "two-phase xact not supported by drivers/SQLA"
- ),
- no_support(
- "postgresql+zxjdbc",
- "FIXME: JDBC driver confuses the transaction state, "
- "may need separate XA implementation",
- ),
- no_support(
- "mysql",
- "recent MySQL communiity editions have too many issues "
- "(late 2016), disabling for now",
- ),
- NotPredicate(
- LambdaPredicate(
- pg_prepared_transaction,
- "max_prepared_transactions not available or zero",
- )
- ),
- ]
- )
- @property
- def two_phase_recovery(self):
- return self.two_phase_transactions + (
- skip_if("mysql", "crashes on most mariadb and mysql versions")
- )
- @property
- def views(self):
- """Target database must support VIEWs."""
- return skip_if("drizzle", "no VIEW support")
- @property
- def empty_strings_varchar(self):
- """
- target database can persist/return an empty string with a varchar.
- """
- return fails_if(
- ["oracle"], "oracle converts empty strings to a blank space"
- )
- @property
- def empty_strings_text(self):
- """target database can persist/return an empty string with an
- unbounded text."""
- return fails_if(
- ["oracle"], "oracle converts empty strings to a blank space"
- )
- @property
- def expressions_against_unbounded_text(self):
- """target database supports use of an unbounded textual field in a
- WHERE clause."""
- return fails_if(
- ["oracle"],
- "ORA-00932: inconsistent datatypes: expected - got CLOB",
- )
- @property
- def unicode_data(self):
- """target drive must support unicode data stored in columns."""
- return skip_if([no_support("sybase", "no unicode driver support")])
- @property
- def unicode_connections(self):
- """
- Target driver must support some encoding of Unicode across the wire.
- """
- # TODO: expand to exclude MySQLdb versions w/ broken unicode
- return skip_if(
- [exclude("mysql", "<", (4, 1, 1), "no unicode connection support")]
- )
- @property
- def unicode_ddl(self):
- """Target driver must support some degree of non-ascii symbol names."""
- # TODO: expand to exclude MySQLdb versions w/ broken unicode
- return skip_if(
- [
- no_support("oracle", "FIXME: no support in database?"),
- no_support("sybase", "FIXME: guessing, needs confirmation"),
- no_support("mssql+pymssql", "no FreeTDS support"),
- LambdaPredicate(
- lambda config: against(config, "mysql+mysqlconnector")
- and config.db.dialect._mysqlconnector_version_info > (2, 0)
- and util.py2k,
- "bug in mysqlconnector 2.0",
- ),
- exclude(
- "mysql", "<", (4, 1, 1), "no unicode connection support"
- ),
- ]
- )
- @property
- def emulated_lastrowid(self):
- """"target dialect retrieves cursor.lastrowid or an equivalent
- after an insert() construct executes.
- """
- return fails_on_everything_except(
- "mysql", "sqlite+pysqlite", "sqlite+pysqlcipher", "sybase", "mssql"
- )
- @property
- def implements_get_lastrowid(self):
- return skip_if([no_support("sybase", "not supported by database")])
- @property
- def dbapi_lastrowid(self):
- """"target backend includes a 'lastrowid' accessor on the DBAPI
- cursor object.
- """
- return skip_if(
- "mssql+pymssql", "crashes on pymssql"
- ) + fails_on_everything_except(
- "mysql", "sqlite+pysqlite", "sqlite+pysqlcipher"
- )
- @property
- def nullsordering(self):
- """Target backends that support nulls ordering."""
- return fails_on_everything_except(
- "postgresql", "oracle", "firebird", "sqlite >= 3.30.0"
- )
- @property
- def reflects_pk_names(self):
- """Target driver reflects the name of primary key constraints."""
- return fails_on_everything_except(
- "postgresql", "oracle", "mssql", "sybase", "sqlite"
- )
- @property
- def nested_aggregates(self):
- """target database can select an aggregate from a subquery that's
- also using an aggregate"""
- return skip_if(["mssql", "sqlite"])
- @property
- def array_type(self):
- return only_on(
- [
- lambda config: against(config, "postgresql")
- and not against(config, "+pg8000")
- and not against(config, "+zxjdbc")
- ]
- )
- @property
- def json_type(self):
- return only_on(
- [
- lambda config: against(config, "mysql")
- and (
- (
- not config.db.dialect._is_mariadb
- and against(config, "mysql >= 5.7")
- )
- or (
- config.db.dialect._mariadb_normalized_version_info
- >= (10, 2, 7)
- )
- ),
- "postgresql >= 9.3",
- self._sqlite_json,
- ]
- )
- @property
- def json_index_supplementary_unicode_element(self):
- # for sqlite see https://bugs.python.org/issue38749
- return skip_if(
- [
- lambda config: against(config, "mysql")
- and config.db.dialect._is_mariadb,
- "sqlite",
- ]
- )
- def _sqlite_file_db(self, config):
- return against(config, "sqlite") and config.db.dialect._is_url_file_db(
- config.db.url
- )
- def _sqlite_memory_db(self, config):
- return against(
- config, "sqlite"
- ) and not config.db.dialect._is_url_file_db(config.db.url)
- def _sqlite_json(self, config):
- if not against(config, "sqlite >= 3.9"):
- return False
- else:
- with config.db.connect() as conn:
- try:
- return (
- conn.scalar(
- """select json_extract('{"foo": "bar"}', """
- """'$."foo"')"""
- )
- == "bar"
- )
- except exc.DBAPIError:
- return False
- @property
- def reflects_json_type(self):
- return only_on(
- [
- lambda config: against(config, "mysql >= 5.7")
- and not config.db.dialect._is_mariadb,
- "postgresql >= 9.3",
- "sqlite >= 3.9",
- ]
- )
- @property
- def json_array_indexes(self):
- return self.json_type + fails_if("+pg8000")
- @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 fails_on_everything_except("sqlite")
- @property
- def datetime(self):
- """target dialect supports representation of Python
- datetime.datetime() objects."""
- return exclusions.open()
- @property
- def datetime_microseconds(self):
- """target dialect supports representation of Python
- datetime.datetime() with microsecond objects."""
- return skip_if(
- ["mssql", "mysql", "firebird", "+zxjdbc", "oracle", "sybase"]
- )
- @property
- def timestamp_microseconds(self):
- """target dialect supports representation of Python
- datetime.datetime() with microsecond objects but only
- if TIMESTAMP is used."""
- return only_on(["oracle"])
- @property
- def datetime_historic(self):
- """target dialect supports representation of Python
- datetime.datetime() objects with historic (pre 1900) values."""
- return succeeds_if(["sqlite", "postgresql", "firebird"])
- @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."""
- # does not work as of pyodbc 4.0.22
- return fails_on("mysql+mysqlconnector") + skip_if("mssql+pyodbc")
- @property
- def date_historic(self):
- """target dialect supports representation of Python
- datetime.datetime() objects with historic (pre 1900) values."""
- return succeeds_if(["sqlite", "postgresql", "firebird"])
- @property
- def time(self):
- """target dialect supports representation of Python
- datetime.time() objects."""
- return skip_if(["oracle"])
- @property
- def time_microseconds(self):
- """target dialect supports representation of Python
- datetime.time() with microsecond objects."""
- return skip_if(
- ["mssql", "mysql", "firebird", "+zxjdbc", "oracle", "sybase"]
- )
- @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."""
- # NOTE: this exclusion isn't used in current tests.
- return exclusions.open()
- @property
- def precision_numerics_enotation_large(self):
- """target backend supports Decimal() objects using E notation
- to represent very large values."""
- return fails_if(
- [
- (
- "sybase+pyodbc",
- None,
- None,
- "Don't know how do get these values through "
- "FreeTDS + Sybase",
- ),
- ("firebird", None, None, "Precision must be from 1 to 18"),
- ]
- )
- @property
- def precision_numerics_many_significant_digits(self):
- """target backend supports values with many digits on both sides,
- such as 319438950232418390.273596, 87673.594069654243
- """
- def broken_cx_oracle(config):
- return (
- against(config, "oracle+cx_oracle")
- and config.db.dialect.cx_oracle_ver <= (6, 0, 2)
- and config.db.dialect.cx_oracle_ver > (6,)
- )
- return fails_if(
- [
- ("sqlite", None, None, "TODO"),
- ("firebird", None, None, "Precision must be from 1 to 18"),
- ("sybase+pysybase", None, None, "TODO"),
- ]
- )
- @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 fails_if(
- [
- ("oracle", None, None, "driver doesn't do this automatically"),
- (
- "firebird",
- None,
- None,
- "database and/or driver truncates decimal places.",
- ),
- ]
- )
- @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 fails_if(
- [
- (
- "mysql",
- None,
- None,
- "mysql FLOAT type only returns 4 decimals",
- ),
- (
- "firebird",
- None,
- None,
- "firebird FLOAT type isn't high precision",
- ),
- ]
- )
- @property
- def floats_to_four_decimals(self):
- return fails_if(
- [
- ("mysql+oursql", None, None, "Floating point error"),
- (
- "firebird",
- None,
- None,
- "Firebird still has FP inaccuracy even "
- "with only four decimal places",
- ),
- (
- "mssql+pyodbc",
- None,
- None,
- "mssql+pyodbc has FP inaccuracy even with "
- "only four decimal places ",
- ),
- (
- "mssql+pymssql",
- None,
- None,
- "mssql+pymssql has FP inaccuracy even with "
- "only four decimal places ",
- ),
- (
- "postgresql+pg8000",
- None,
- None,
- "postgresql+pg8000 has FP inaccuracy even with "
- "only four decimal places ",
- ),
- (
- "postgresql+psycopg2cffi",
- None,
- None,
- "postgresql+psycopg2cffi has FP inaccuracy even with "
- "only four decimal places ",
- ),
- ]
- )
- @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`
- """
- # fixed for mysqlclient in
- # https://github.com/PyMySQL/mysqlclient-python/commit/68b9662918577fc05be9610ef4824a00f2b051b0
- def check(config):
- if against(config, "mysql+mysqldb"):
- # can remove once post 1.3.13 is released
- try:
- from MySQLdb import converters
- from decimal import Decimal
- return Decimal not in converters.conversions
- except:
- return True
- return against(
- config, "mysql+mysqldb"
- ) and config.db.dialect._mysql_dbapi_version <= (1, 3, 13)
- return exclusions.fails_on(check, "fixed for mysqlclient post 1.3.13")
- @property
- def fetch_null_from_numeric(self):
- return skip_if(("mssql+pyodbc", None, None, "crashes due to bug #351"))
- @property
- def duplicate_key_raises_integrity_error(self):
- return fails_on("postgresql+pg8000")
- def _has_pg_extension(self, name):
- def check(config):
- if not against(config, "postgresql"):
- return False
- count = config.db.scalar(
- "SELECT count(*) FROM pg_extension "
- "WHERE extname='%s'" % name
- )
- return bool(count)
- return only_if(check, "needs %s extension" % name)
- @property
- def hstore(self):
- return self._has_pg_extension("hstore")
- @property
- def btree_gist(self):
- return self._has_pg_extension("btree_gist")
- @property
- def range_types(self):
- def check_range_types(config):
- if not against(
- config, ["postgresql+psycopg2", "postgresql+psycopg2cffi"]
- ):
- return False
- try:
- config.db.scalar("select '[1,2)'::int4range;")
- return True
- except Exception:
- return False
- return only_if(check_range_types)
- @property
- def oracle_test_dblink(self):
- return skip_if(
- lambda config: not config.file_config.has_option(
- "sqla_testing", "oracle_db_link"
- ),
- "oracle_db_link option not specified in config",
- )
- @property
- def postgresql_test_dblink(self):
- return skip_if(
- lambda config: not config.file_config.has_option(
- "sqla_testing", "postgres_test_db_link"
- ),
- "postgres_test_db_link option not specified in config",
- )
- @property
- def postgresql_jsonb(self):
- return only_on("postgresql >= 9.4") + skip_if(
- lambda config: config.db.dialect.driver == "pg8000"
- and config.db.dialect._dbapi_version <= (1, 10, 1)
- )
- @property
- def psycopg2_native_json(self):
- return self.psycopg2_compatibility
- @property
- def psycopg2_native_hstore(self):
- return self.psycopg2_compatibility
- @property
- def psycopg2_compatibility(self):
- return only_on(["postgresql+psycopg2", "postgresql+psycopg2cffi"])
- @property
- def psycopg2_or_pg8000_compatibility(self):
- return only_on(
- [
- "postgresql+psycopg2",
- "postgresql+psycopg2cffi",
- "postgresql+pg8000",
- ]
- )
- @property
- def percent_schema_names(self):
- return skip_if(
- [
- (
- "+psycopg2",
- None,
- None,
- "psycopg2 2.4 no longer accepts percent "
- "sign in bind placeholders",
- ),
- (
- "+psycopg2cffi",
- None,
- None,
- "psycopg2cffi does not accept percent signs in "
- "bind placeholders",
- ),
- ("mysql", None, None, "executemany() doesn't work here"),
- ]
- )
- @property
- def order_by_label_with_expression(self):
- return fails_if(
- [
- (
- "firebird",
- None,
- None,
- "kinterbasdb doesn't send full type information",
- ),
- ("postgresql", None, None, "only simple labels allowed"),
- ("sybase", None, None, "only simple labels allowed"),
- ("mssql", None, None, "only simple labels allowed"),
- ]
- )
- def get_order_by_collation(self, config):
- lookup = {
- # will raise without quoting
- "postgresql": "POSIX",
- # note MySQL databases need to be created w/ utf8mb4 charset
- # for the test suite
- "mysql": "utf8mb4_bin",
- "sqlite": "NOCASE",
- # will raise *with* quoting
- "mssql": "Latin1_General_CI_AS",
- }
- try:
- return lookup[config.db.name]
- except KeyError:
- raise NotImplementedError()
- @property
- def skip_mysql_on_windows(self):
- """Catchall for a large variety of MySQL on Windows failures"""
- return skip_if(
- self._has_mysql_on_windows, "Not supported on MySQL + Windows"
- )
- @property
- def mssql_freetds(self):
- return only_on(["mssql+pymssql"])
- @property
- def ad_hoc_engines(self):
- return exclusions.skip_if(
- ["oracle"],
- "works, but Oracle just gets tired with "
- "this much connection activity",
- )
- @property
- def no_mssql_freetds(self):
- return self.mssql_freetds.not_()
- @property
- def pyodbc_fast_executemany(self):
- def has_fastexecutemany(config):
- if not against(config, "mssql+pyodbc"):
- return False
- if config.db.dialect._dbapi_version() < (4, 0, 19):
- return False
- with config.db.connect() as conn:
- drivername = conn.connection.connection.getinfo(
- config.db.dialect.dbapi.SQL_DRIVER_NAME
- )
- # on linux this is something like 'libmsodbcsql-13.1.so.9.2'.
- # on Windows this is something like 'msodbcsql17.dll'.
- return "msodbc" in drivername
- return only_if(
- has_fastexecutemany, "only on pyodbc > 4.0.19 w/ msodbc driver"
- )
- @property
- def python_fixed_issue_8743(self):
- return exclusions.skip_if(
- lambda: sys.version_info < (2, 7, 8),
- "Python issue 8743 fixed in Python 2.7.8",
- )
- @property
- def granular_timezone(self):
- """the datetime.timezone class, or SQLAlchemy's port, supports
- seconds and microseconds.
- SQLAlchemy ported the Python 3.7 version for Python 2, so
- it passes on that. For Python 3.6 and earlier, it is not supported.
- """
- return exclusions.skip_if(
- lambda: sys.version_info >= (3,) and sys.version_info < (3, 7)
- )
- @property
- def selectone(self):
- """target driver must support the literal statement 'select 1'"""
- return skip_if(
- ["oracle", "firebird"], "non-standard SELECT scalar syntax"
- )
- @property
- def mysql_for_update(self):
- return skip_if(
- "mysql+mysqlconnector",
- "lock-sensitive operations crash on mysqlconnector",
- )
- @property
- def mysql_fsp(self):
- return only_if("mysql >= 5.6.4")
- @property
- def mysql_fully_case_sensitive(self):
- return only_if(self._has_mysql_fully_case_sensitive)
- @property
- def mysql_zero_date(self):
- def check(config):
- if not against(config, "mysql"):
- return False
- row = config.db.execute("show variables like 'sql_mode'").first()
- return not row or "NO_ZERO_DATE" not in row[1]
- return only_if(check)
- @property
- def mysql_non_strict(self):
- def check(config):
- if not against(config, "mysql"):
- return False
- row = config.db.execute("show variables like 'sql_mode'").first()
- return not row or "STRICT_TRANS_TABLES" not in row[1]
- return only_if(check)
- @property
- def mysql_ngram_fulltext(self):
- def check(config):
- return (
- against(config, "mysql")
- and not config.db.dialect._is_mariadb
- and config.db.dialect.server_version_info >= (5, 7)
- )
- return only_if(check)
- def _mariadb_102(self, config):
- return (
- against(config, "mysql")
- and config.db.dialect._is_mariadb
- and config.db.dialect._mariadb_normalized_version_info > (10, 2)
- )
- def _mysql_and_check_constraints_exist(self, config):
- # 1. we have mysql / mariadb and
- # 2. it enforces check constraints
- if exclusions.against(config, "mysql"):
- if config.db.dialect._is_mariadb:
- norm_version_info = (
- config.db.dialect._mariadb_normalized_version_info
- )
- return norm_version_info >= (10, 2)
- else:
- norm_version_info = config.db.dialect.server_version_info
- return norm_version_info >= (8, 0, 16)
- else:
- return False
- def _mysql_check_constraints_exist(self, config):
- # 1. we dont have mysql / mariadb or
- # 2. we have mysql / mariadb that enforces check constraints
- return not exclusions.against(
- config, "mysql"
- ) or self._mysql_and_check_constraints_exist(config)
- def _mysql_check_constraints_dont_exist(self, config):
- # 1. we have mysql / mariadb and
- # 2. they dont enforce check constraints
- return not self._mysql_check_constraints_exist(config)
- def _mysql_not_mariadb_102(self, config):
- return against(config, "mysql") and (
- not config.db.dialect._is_mariadb
- or config.db.dialect._mariadb_normalized_version_info < (10, 2)
- )
- def _mysql_not_mariadb_103(self, config):
- return against(config, "mysql") and (
- not config.db.dialect._is_mariadb
- or config.db.dialect._mariadb_normalized_version_info < (10, 3)
- )
- def _mysql_not_mariadb_104(self, config):
- return against(config, "mysql") and (
- not config.db.dialect._is_mariadb
- or config.db.dialect._mariadb_normalized_version_info < (10, 4)
- )
- def _has_mysql_on_windows(self, config):
- return (
- against(config, "mysql")
- and config.db.dialect._detect_casing(config.db) == 1
- )
- def _has_mysql_fully_case_sensitive(self, config):
- return (
- against(config, "mysql")
- and config.db.dialect._detect_casing(config.db) == 0
- )
- @property
- def postgresql_utf8_server_encoding(self):
- return only_if(
- lambda config: against(config, "postgresql")
- and config.db.scalar("show server_encoding").lower() == "utf8"
- )
- @property
- def cxoracle6_or_greater(self):
- return only_if(
- lambda config: against(config, "oracle+cx_oracle")
- and config.db.dialect.cx_oracle_ver >= (6,)
- )
- @property
- def oracle5x(self):
- return only_if(
- lambda config: against(config, "oracle+cx_oracle")
- and config.db.dialect.cx_oracle_ver < (6,)
- )
- @property
- def computed_columns(self):
- return skip_if(["postgresql < 12", "sqlite", "mysql < 5.7"])
- @property
- def python_profiling_backend(self):
- return only_on([self._sqlite_memory_db])
- @property
- def computed_columns_stored(self):
- return self.computed_columns + skip_if(["oracle", "firebird"])
- @property
- def computed_columns_virtual(self):
- return self.computed_columns + skip_if(["postgresql", "firebird"])
- @property
- def computed_columns_default_persisted(self):
- return self.computed_columns + only_if("postgresql")
- @property
- def computed_columns_reflect_persisted(self):
- return self.computed_columns + skip_if("oracle")
|