1966a21c5SMike Bayer"""Requirements specific to SQLAlchemy's own unit tests.
24a6afd46SMike Bayer
34a6afd46SMike Bayer
44a6afd46SMike Bayer"""
5e41c0f41SJason Kirtland
620cdc645SMike Bayerimport sys
71e278de4SMike Bayer
8c2e8b48aSMike Bayerfrom sqlalchemy import exc
992fd25f3SFederico Casellifrom sqlalchemy.sql import text
1012df8a99SMike Bayerfrom sqlalchemy.testing import exclusions
111e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import against
121e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import fails_if
131e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import fails_on
141e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import fails_on_everything_except
151e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import LambdaPredicate
1692fd25f3SFederico Casellifrom sqlalchemy.testing.exclusions import NotPredicate
171e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import only_if
181e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import only_on
191e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import skip_if
201e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import SpecPredicate
211e278de4SMike Bayerfrom sqlalchemy.testing.exclusions import succeeds_if
221e278de4SMike Bayerfrom sqlalchemy.testing.requirements import SuiteRequirements
239e612f11SMike Bayer
2477237473SKhairi Hafsham
259e612f11SMike Bayerdef no_support(db, reason):
269e612f11SMike Bayer    return SpecPredicate(db, description=reason)
279e612f11SMike Bayer
2877237473SKhairi Hafsham
299e612f11SMike Bayerdef exclude(db, op, spec, description=None):
309e612f11SMike Bayer    return SpecPredicate(db, op, spec, description=description)
319e612f11SMike Bayer
3277237473SKhairi Hafsham
3320cdc645SMike Bayerclass DefaultRequirements(SuiteRequirements):
341a777863SMike Bayer    @property
351a777863SMike Bayer    def deferrable_or_no_constraints(self):
365fd779dfSJon Nelson        """Target database must support deferrable constraints."""
371a777863SMike Bayer
381e1a38e7SMike Bayer        return skip_if(
391e1a38e7SMike Bayer            [
401e1a38e7SMike Bayer                no_support("mysql", "not supported by database"),
41fc97854fSMike Bayer                no_support("mariadb", "not supported by database"),
421e1a38e7SMike Bayer                no_support("mssql", "not supported by database"),
431e1a38e7SMike Bayer            ]
441e1a38e7SMike Bayer        )
451a777863SMike Bayer
469687b272SScott Dugas    @property
479687b272SScott Dugas    def check_constraints(self):
489687b272SScott Dugas        """Target database must support check constraints."""
499687b272SScott Dugas
509687b272SScott Dugas        return exclusions.open()
519687b272SScott Dugas
52c04870baSMike Bayer    @property
53c04870baSMike Bayer    def enforces_check_constraints(self):
54c04870baSMike Bayer        """Target database must also enforce check constraints."""
55c04870baSMike Bayer
56c04870baSMike Bayer        return self.check_constraints + fails_on(
571c3e9262SMike Bayer            self._mysql_check_constraints_dont_exist,
581e1a38e7SMike Bayer            "check constraints don't enforce on MySQL, MariaDB<10.2",
59c04870baSMike Bayer        )
60c04870baSMike Bayer
61f4ba5b85SMike Bayer    @property
62f4ba5b85SMike Bayer    def named_constraints(self):
63f4ba5b85SMike Bayer        """target database must support names for constraints."""
64f4ba5b85SMike Bayer
656b0e12fdSMike Bayer        return exclusions.open()
666b0e12fdSMike Bayer
676b0e12fdSMike Bayer    @property
686b0e12fdSMike Bayer    def implicitly_named_constraints(self):
696b0e12fdSMike Bayer        """target database must apply names to unnamed constraints."""
706b0e12fdSMike Bayer
711e1a38e7SMike Bayer        return skip_if([no_support("sqlite", "not supported by database")])
72f4ba5b85SMike Bayer
731a777863SMike Bayer    @property
741a777863SMike Bayer    def foreign_keys(self):
751a777863SMike Bayer        """Target database must support foreign keys."""
761a777863SMike Bayer
771e1a38e7SMike Bayer        return skip_if(no_support("sqlite", "not supported by database"))
781a777863SMike Bayer
792db1eccbSMike Bayer    @property
802db1eccbSMike Bayer    def foreign_key_constraint_name_reflection(self):
812db1eccbSMike Bayer        return fails_if(
822db1eccbSMike Bayer            lambda config: against(config, ["mysql", "mariadb"])
832db1eccbSMike Bayer            and not self._mysql_80(config)
842db1eccbSMike Bayer            and not self._mariadb_105(config)
852db1eccbSMike Bayer        )
862db1eccbSMike Bayer
870d50b0c7SRamonWill    @property
880d50b0c7SRamonWill    def table_ddl_if_exists(self):
890d50b0c7SRamonWill        """target platform supports IF NOT EXISTS / IF EXISTS for tables."""
900d50b0c7SRamonWill
910d50b0c7SRamonWill        return only_on(["postgresql", "mysql", "mariadb", "sqlite"])
920d50b0c7SRamonWill
930d50b0c7SRamonWill    @property
940d50b0c7SRamonWill    def index_ddl_if_exists(self):
950d50b0c7SRamonWill        """target platform supports IF NOT EXISTS / IF EXISTS for indexes."""
960d50b0c7SRamonWill
970d50b0c7SRamonWill        # mariadb but not mysql, tested up to mysql 8
980d50b0c7SRamonWill        return only_on(["postgresql", "mariadb", "sqlite"])
990d50b0c7SRamonWill
100a0ef9edcSMike Bayer    @property
101a0ef9edcSMike Bayer    def on_update_cascade(self):
102a0ef9edcSMike Bayer        """target database must support ON UPDATE..CASCADE behavior in
103a0ef9edcSMike Bayer        foreign keys."""
104a0ef9edcSMike Bayer
105a0ef9edcSMike Bayer        return skip_if(
1061e1a38e7SMike Bayer            ["sqlite", "oracle"],
1071e1a38e7SMike Bayer            "target backend %(doesnt_support)s ON UPDATE CASCADE",
1081e1a38e7SMike Bayer        )
109a0ef9edcSMike Bayer
110c01558aeSMike Bayer    @property
111c01558aeSMike Bayer    def non_updating_cascade(self):
112c01558aeSMike Bayer        """target database must *not* support ON UPDATE..CASCADE behavior in
113c01558aeSMike Bayer        foreign keys."""
114c01558aeSMike Bayer
1159e31fc74SMike Bayer        return fails_on_everything_except("sqlite", "oracle") + skip_if(
1169e31fc74SMike Bayer            "mssql"
1179e31fc74SMike Bayer        )
118c01558aeSMike Bayer
1192efd89d0SMike Bayer    @property
1202efd89d0SMike Bayer    def recursive_fk_cascade(self):
1212efd89d0SMike Bayer        """target database must support ON DELETE CASCADE on a self-referential
1222efd89d0SMike Bayer        foreign key"""
1232efd89d0SMike Bayer
1242efd89d0SMike Bayer        return skip_if(["mssql"])
1252efd89d0SMike Bayer
126a0ef9edcSMike Bayer    @property
127a0ef9edcSMike Bayer    def deferrable_fks(self):
128a0ef9edcSMike Bayer        """target database must support deferrable fks"""
129a0ef9edcSMike Bayer
130bbb8a382SMike Bayer        return only_on(["oracle", "postgresql"])
131a0ef9edcSMike Bayer
132daf209bdSMike Bayer    @property
133d53533fbSMiroslav Shubernetskiy    def foreign_key_constraint_option_reflection_ondelete(self):
134fa247a04SGord Thompson        return only_on(
135fa247a04SGord Thompson            ["postgresql", "mysql", "mariadb", "sqlite", "oracle", "mssql"]
136fa247a04SGord Thompson        )
137d53533fbSMiroslav Shubernetskiy
1385cf8e14dSMike Bayer    @property
1395cf8e14dSMike Bayer    def fk_constraint_option_reflection_ondelete_restrict(self):
1405cf8e14dSMike Bayer        return only_on(["postgresql", "sqlite", self._mysql_80])
1415cf8e14dSMike Bayer
1425cf8e14dSMike Bayer    @property
1435cf8e14dSMike Bayer    def fk_constraint_option_reflection_ondelete_noaction(self):
144fa247a04SGord Thompson        return only_on(["postgresql", "mysql", "mariadb", "sqlite", "mssql"])
1455cf8e14dSMike Bayer
146d53533fbSMiroslav Shubernetskiy    @property
147d53533fbSMiroslav Shubernetskiy    def foreign_key_constraint_option_reflection_onupdate(self):
148fa247a04SGord Thompson        return only_on(["postgresql", "mysql", "mariadb", "sqlite", "mssql"])
149a0ef9edcSMike Bayer
1505cf8e14dSMike Bayer    @property
1515cf8e14dSMike Bayer    def fk_constraint_option_reflection_onupdate_restrict(self):
1525cf8e14dSMike Bayer        return only_on(["postgresql", "sqlite", self._mysql_80])
1535cf8e14dSMike Bayer
154fadb8d61SFrazer McLean    @property
155fadb8d61SFrazer McLean    def comment_reflection(self):
156fc97854fSMike Bayer        return only_on(["postgresql", "mysql", "mariadb", "oracle"])
157fadb8d61SFrazer McLean
1581a777863SMike Bayer    @property
1591a777863SMike Bayer    def unbounded_varchar(self):
1601a777863SMike Bayer        """Target database must support VARCHAR with no length"""
1611a777863SMike Bayer
1621e1a38e7SMike Bayer        return skip_if(
163ed78e679SFederico Caselli            ["oracle", "mysql", "mariadb"],
164fc97854fSMike Bayer            "not supported by database",
1651e1a38e7SMike Bayer        )
1661a777863SMike Bayer
1671a777863SMike Bayer    @property
1681a777863SMike Bayer    def boolean_col_expressions(self):
1691a777863SMike Bayer        """Target database must support boolean expressions as columns"""
1701e1a38e7SMike Bayer        return skip_if(
1711e1a38e7SMike Bayer            [
1721e1a38e7SMike Bayer                no_support("oracle", "not supported by database"),
1731e1a38e7SMike Bayer                no_support("mssql", "not supported by database"),
1741e1a38e7SMike Bayer            ]
1751e1a38e7SMike Bayer        )
1761a777863SMike Bayer
177d2bacad4SMike Bayer    @property
178d2bacad4SMike Bayer    def non_native_boolean_unconstrained(self):
179d2bacad4SMike Bayer        """target database is not native boolean and allows arbitrary integers
180d2bacad4SMike Bayer        in it's "bool" column"""
181d2bacad4SMike Bayer
1821e1a38e7SMike Bayer        return skip_if(
1831e1a38e7SMike Bayer            [
1841e1a38e7SMike Bayer                LambdaPredicate(
1851e1a38e7SMike Bayer                    lambda config: against(config, "mssql"),
1861e278de4SMike Bayer                    "SQL Server drivers / odbc seem to change "
1871e278de4SMike Bayer                    "their mind on this",
1881e1a38e7SMike Bayer                ),
1891e1a38e7SMike Bayer                LambdaPredicate(
1901e1a38e7SMike Bayer                    lambda config: config.db.dialect.supports_native_boolean,
1911e1a38e7SMike Bayer                    "native boolean dialect",
1921e1a38e7SMike Bayer                ),
1931e1a38e7SMike Bayer            ]
1941e1a38e7SMike Bayer        )
195d2bacad4SMike Bayer
1969ec75882SFederico Caselli    @property
1979ec75882SFederico Caselli    def qmark_paramstyle(self):
198ed78e679SFederico Caselli        return only_on(["sqlite", "+pyodbc"])
1999ec75882SFederico Caselli
2009ec75882SFederico Caselli    @property
2019ec75882SFederico Caselli    def named_paramstyle(self):
2029ec75882SFederico Caselli        return only_on(["sqlite", "oracle+cx_oracle"])
2039ec75882SFederico Caselli
2049ec75882SFederico Caselli    @property
2059ec75882SFederico Caselli    def format_paramstyle(self):
2069ec75882SFederico Caselli        return only_on(
2079ec75882SFederico Caselli            [
2089ec75882SFederico Caselli                "mysql+mysqldb",
2099ec75882SFederico Caselli                "mysql+pymysql",
2109ec75882SFederico Caselli                "mysql+cymysql",
2119ec75882SFederico Caselli                "mysql+mysqlconnector",
212fc97854fSMike Bayer                "mariadb+mysqldb",
213fc97854fSMike Bayer                "mariadb+pymysql",
214fc97854fSMike Bayer                "mariadb+cymysql",
215fc97854fSMike Bayer                "mariadb+mysqlconnector",
2165fb0138aSMike Bayer                "postgresql+pg8000",
2179ec75882SFederico Caselli            ]
2189ec75882SFederico Caselli        )
2199ec75882SFederico Caselli
2209ec75882SFederico Caselli    @property
2219ec75882SFederico Caselli    def pyformat_paramstyle(self):
2229ec75882SFederico Caselli        return only_on(
2239ec75882SFederico Caselli            [
2249ec75882SFederico Caselli                "postgresql+psycopg2",
2259ec75882SFederico Caselli                "postgresql+psycopg2cffi",
2269ec75882SFederico Caselli                "mysql+mysqlconnector",
2279ec75882SFederico Caselli                "mysql+pymysql",
2289ec75882SFederico Caselli                "mysql+cymysql",
229fc97854fSMike Bayer                "mariadb+mysqlconnector",
230fc97854fSMike Bayer                "mariadb+pymysql",
231fc97854fSMike Bayer                "mariadb+cymysql",
2329ec75882SFederico Caselli                "mssql+pymssql",
2339ec75882SFederico Caselli            ]
2349ec75882SFederico Caselli        )
2359ec75882SFederico Caselli
236e447582bSMike Bayer    @property
237e447582bSMike Bayer    def no_quoting_special_bind_names(self):
23866e88d30SLele Gaifax        """Target database will quote bound parameter names, doesn't support
239e447582bSMike Bayer        EXPANDING"""
240e447582bSMike Bayer
241e447582bSMike Bayer        return skip_if(["oracle"])
242e447582bSMike Bayer
2435b3fc874SScott Dugas    @property
244c24423bcSMike Bayer    def temporary_tables(self):
245c24423bcSMike Bayer        """target database supports temporary tables"""
246ed78e679SFederico Caselli        return skip_if([self._sqlite_file_db], "not supported (?)")
247c24423bcSMike Bayer
248c24423bcSMike Bayer    @property
249c24423bcSMike Bayer    def temp_table_reflection(self):
250c24423bcSMike Bayer        return self.temporary_tables
2515b3fc874SScott Dugas
252516131c4SGord Thompson    @property
253516131c4SGord Thompson    def temp_table_reflect_indexes(self):
254ed78e679SFederico Caselli        return skip_if(["mssql", self._sqlite_file_db], "not supported (?)")
255516131c4SGord Thompson
2561a777863SMike Bayer    @property
2571a777863SMike Bayer    def reflectable_autoincrement(self):
2581a777863SMike Bayer        """Target database must support tables that can automatically generate
2591a777863SMike Bayer        PKs assuming they were reflected.
2601a777863SMike Bayer
2618c2c464cSVille Skyttä        this is essentially all the DBs in "identity" plus PostgreSQL, which
262ed78e679SFederico Caselli        has SERIAL support.  Oracle requires the Sequence
26377237473SKhairi Hafsham        to be explicitly added, including if the table was reflected.
2641a777863SMike Bayer        """
265ed78e679SFederico Caselli        return skip_if(["oracle"], "not supported by database")
2660cba61d1SMike Bayer
2672efd89d0SMike Bayer    @property
2682efd89d0SMike Bayer    def non_broken_binary(self):
2692efd89d0SMike Bayer        """target DBAPI must work fully with binary values"""
2702efd89d0SMike Bayer
2712efd89d0SMike Bayer        # see https://github.com/pymssql/pymssql/issues/504
2722efd89d0SMike Bayer        return skip_if(["mssql+pymssql"])
2732efd89d0SMike Bayer
2741a777863SMike Bayer    @property
2751a777863SMike Bayer    def binary_comparisons(self):
2761a777863SMike Bayer        """target database/driver can allow BLOB/BINARY fields to be compared
2771a777863SMike Bayer        against a bound parameter value.
2781a777863SMike Bayer        """
27977237473SKhairi Hafsham        return skip_if(["oracle", "mssql"], "not supported by database/driver")
2801a777863SMike Bayer
28132a1db36SMike Bayer    @property
28232a1db36SMike Bayer    def binary_literals(self):
28332a1db36SMike Bayer        """target backend supports simple binary literals, e.g. an
28432a1db36SMike Bayer        expression like::
28532a1db36SMike Bayer
28632a1db36SMike Bayer            SELECT CAST('foo' AS BINARY)
28732a1db36SMike Bayer
28832a1db36SMike Bayer        Where ``BINARY`` is the type emitted from :class:`.LargeBinary`,
28932a1db36SMike Bayer        e.g. it could be ``BLOB`` or similar.
29032a1db36SMike Bayer
29132a1db36SMike Bayer        Basically fails on Oracle.
29232a1db36SMike Bayer
29332a1db36SMike Bayer        """
29432a1db36SMike Bayer        # adding mssql here since it doesn't support comparisons either,
29532a1db36SMike Bayer        # have observed generally bad behavior with binary / mssql.
29632a1db36SMike Bayer
29777237473SKhairi Hafsham        return skip_if(["oracle", "mssql"], "not supported by database/driver")
29832a1db36SMike Bayer
2997d9f241dSMike Bayer    @property
3007d9f241dSMike Bayer    def tuple_in(self):
30188168db8SMike Bayer        def _sqlite_tuple_in(config):
30288168db8SMike Bayer            return against(
30388168db8SMike Bayer                config, "sqlite"
30488168db8SMike Bayer            ) and config.db.dialect.dbapi.sqlite_version_info >= (3, 15, 0)
30588168db8SMike Bayer
3063710382dSMike Bayer        return only_on(
3073710382dSMike Bayer            ["mysql", "mariadb", "postgresql", _sqlite_tuple_in, "oracle"]
3083710382dSMike Bayer        )
3093710382dSMike Bayer
3103710382dSMike Bayer    @property
3113710382dSMike Bayer    def tuple_in_w_empty(self):
3123710382dSMike Bayer        return self.tuple_in + skip_if(["oracle"])
3137d9f241dSMike Bayer
3141a777863SMike Bayer    @property
3151a777863SMike Bayer    def independent_cursors(self):
3161a777863SMike Bayer        """Target must support simultaneous, independent database cursors
3171a777863SMike Bayer        on a single connection."""
3181a777863SMike Bayer
319fc97854fSMike Bayer        return skip_if(["mssql", "mysql", "mariadb"], "no driver support")
3201a777863SMike Bayer
321e2c82ab3SMike Bayer    @property
322e2c82ab3SMike Bayer    def cursor_works_post_rollback(self):
323e2c82ab3SMike Bayer        """Driver quirk where the cursor.fetchall() will work even if
324e2c82ab3SMike Bayer        the connection has been rolled back.
325e2c82ab3SMike Bayer
326e2c82ab3SMike Bayer        This generally refers to buffered cursors but also seems to work
327e2c82ab3SMike Bayer        with cx_oracle, for example.
328e2c82ab3SMike Bayer
329e2c82ab3SMike Bayer        """
330e2c82ab3SMike Bayer
331e2c82ab3SMike Bayer        return skip_if(["+pyodbc"], "no driver support")
332e2c82ab3SMike Bayer
3331a777863SMike Bayer    @property
3341a777863SMike Bayer    def independent_connections(self):
33577237473SKhairi Hafsham        """
33677237473SKhairi Hafsham        Target must support simultaneous, independent database connections.
33777237473SKhairi Hafsham        """
3381a777863SMike Bayer
33977237473SKhairi Hafsham        # This is also true of some configurations of UnixODBC and probably
34077237473SKhairi Hafsham        # win32 ODBC as well.
3411e1a38e7SMike Bayer        return skip_if(
3421e1a38e7SMike Bayer            [
3431e1a38e7SMike Bayer                no_support(
3441e1a38e7SMike Bayer                    "sqlite",
3451e1a38e7SMike Bayer                    "independent connections disabled "
3461e1a38e7SMike Bayer                    "when :memory: connections are used",
3471e1a38e7SMike Bayer                ),
3481e1a38e7SMike Bayer                exclude(
3491e1a38e7SMike Bayer                    "mssql",
3501e1a38e7SMike Bayer                    "<",
3511e1a38e7SMike Bayer                    (9, 0, 0),
35277237473SKhairi Hafsham                    "SQL Server 2005+ is required for "
3531e1a38e7SMike Bayer                    "independent connections",
3541e1a38e7SMike Bayer                ),
3551e1a38e7SMike Bayer            ]
3561e1a38e7SMike Bayer        )
3571a777863SMike Bayer
3582efd89d0SMike Bayer    @property
3592efd89d0SMike Bayer    def memory_process_intensive(self):
3602efd89d0SMike Bayer        """Driver is able to handle the memory tests which run in a subprocess
3612efd89d0SMike Bayer        and iterate through hundreds of connections
3622efd89d0SMike Bayer
3632efd89d0SMike Bayer        """
3641e1a38e7SMike Bayer        return skip_if(
3651e1a38e7SMike Bayer            [
3661e1a38e7SMike Bayer                no_support("oracle", "Oracle XE usually can't handle these"),
3671e1a38e7SMike Bayer                no_support("mssql+pyodbc", "MS ODBC drivers struggle"),
36812ad9204SMike Bayer                self._running_on_windows(),
3691e1a38e7SMike Bayer            ]
3701e1a38e7SMike Bayer        )
3712efd89d0SMike Bayer
3721a777863SMike Bayer    @property
3731a777863SMike Bayer    def updateable_autoincrement_pks(self):
3741a777863SMike Bayer        """Target must support UPDATE on autoincrement/integer primary key."""
3751a777863SMike Bayer
376ed78e679SFederico Caselli        return skip_if(["mssql"], "IDENTITY columns can't be updated")
3771a777863SMike Bayer
3781a777863SMike Bayer    @property
3791a777863SMike Bayer    def isolation_level(self):
380f4ba5b85SMike Bayer        return only_on(
381fc97854fSMike Bayer            ("postgresql", "sqlite", "mysql", "mariadb", "mssql", "oracle"),
3821e1a38e7SMike Bayer            "DBAPI has no isolation level support",
3831e1a38e7SMike Bayer        )
3841a777863SMike Bayer
38537414a75SMike Bayer    @property
38637414a75SMike Bayer    def legacy_isolation_level(self):
38737414a75SMike Bayer        # refers to the engine isolation_level setting
38837414a75SMike Bayer        return only_on(
38937414a75SMike Bayer            ("postgresql", "sqlite", "mysql", "mariadb", "mssql"),
39037414a75SMike Bayer            "DBAPI has no isolation level support",
39137414a75SMike Bayer        )
39237414a75SMike Bayer
39301299b6bSGord Thompson    def get_isolation_levels(self, config):
39401299b6bSGord Thompson        levels = set(config.db.dialect._isolation_lookup)
39501299b6bSGord Thompson
39601299b6bSGord Thompson        if against(config, "sqlite"):
39701299b6bSGord Thompson            default = "SERIALIZABLE"
39864e8303dSGord Thompson            levels.add("AUTOCOMMIT")
39901299b6bSGord Thompson        elif against(config, "postgresql"):
40001299b6bSGord Thompson            default = "READ COMMITTED"
40101299b6bSGord Thompson            levels.add("AUTOCOMMIT")
40201299b6bSGord Thompson        elif against(config, "mysql"):
40301299b6bSGord Thompson            default = "REPEATABLE READ"
40401299b6bSGord Thompson            levels.add("AUTOCOMMIT")
405fc97854fSMike Bayer        elif against(config, "mariadb"):
406fc97854fSMike Bayer            default = "REPEATABLE READ"
407fc97854fSMike Bayer            levels.add("AUTOCOMMIT")
40801299b6bSGord Thompson        elif against(config, "mssql"):
40901299b6bSGord Thompson            default = "READ COMMITTED"
41001299b6bSGord Thompson            levels.add("AUTOCOMMIT")
41101299b6bSGord Thompson        elif against(config, "oracle"):
41201299b6bSGord Thompson            default = "READ COMMITTED"
41301299b6bSGord Thompson            levels.add("AUTOCOMMIT")
41401299b6bSGord Thompson        else:
41501299b6bSGord Thompson            raise NotImplementedError()
41601299b6bSGord Thompson
41701299b6bSGord Thompson        return {"default": default, "supported": levels}
41801299b6bSGord Thompson
419ec4f567fSMike Bayer    @property
420ec4f567fSMike Bayer    def autocommit(self):
421ec4f567fSMike Bayer        """target dialect supports 'AUTOCOMMIT' as an isolation_level"""
42201299b6bSGord Thompson
42301299b6bSGord Thompson        return self.isolation_level + only_if(
42401299b6bSGord Thompson            lambda config: "AUTOCOMMIT"
42501299b6bSGord Thompson            in self.get_isolation_levels(config)["supported"]
4261e1a38e7SMike Bayer        )
427ec4f567fSMike Bayer
4281a777863SMike Bayer    @property
4291a777863SMike Bayer    def row_triggers(self):
4301a777863SMike Bayer        """Target must support standard statement-running EACH ROW triggers."""
4311a777863SMike Bayer
4321e1a38e7SMike Bayer        return skip_if(
4331e1a38e7SMike Bayer            [
4341e1a38e7SMike Bayer                # no access to same table
4351e1a38e7SMike Bayer                no_support("mysql", "requires SUPER priv"),
436fc97854fSMike Bayer                no_support("mariadb", "requires SUPER priv"),
4371e1a38e7SMike Bayer                exclude("mysql", "<", (5, 0, 10), "not supported by database"),
4381e1a38e7SMike Bayer            ]
4391e1a38e7SMike Bayer        )
4401a777863SMike Bayer
4411a777863SMike Bayer    @property
442532566baSMike Bayer    def sequences_as_server_defaults(self):
443532566baSMike Bayer        """Target database must support SEQUENCE as a server side default."""
444532566baSMike Bayer
4451984b6d3SFederico Caselli        return self.sequences + only_on(
4461984b6d3SFederico Caselli            ["postgresql", "mariadb", "oracle >= 18"],
4471984b6d3SFederico Caselli            "doesn't support sequences as a server side default.",
4481e1a38e7SMike Bayer        )
449532566baSMike Bayer
450c1f310dfSMike Bayer    @property
451c1f310dfSMike Bayer    def sql_expressions_inserted_as_primary_key(self):
452c1f310dfSMike Bayer        return only_if([self.returning, self.sqlite])
453c1f310dfSMike Bayer
4543a0e0531SCaselIT    @property
4553a0e0531SCaselIT    def computed_columns_on_update_returning(self):
4563a0e0531SCaselIT        return self.computed_columns + skip_if("oracle")
4573a0e0531SCaselIT
4581a777863SMike Bayer    @property
4591a777863SMike Bayer    def correlated_outer_joins(self):
4601a777863SMike Bayer        """Target must support an outer join to a subquery which
4611a777863SMike Bayer        correlates to the parent."""
4621a777863SMike Bayer
4631e1a38e7SMike Bayer        return skip_if(
4641e1a38e7SMike Bayer            "oracle",
4651e1a38e7SMike Bayer            'Raises "ORA-01799: a column may not be '
4661e1a38e7SMike Bayer            'outer-joined to a subquery"',
4671e1a38e7SMike Bayer        )
4681a777863SMike Bayer
46918b4a343SMike Bayer    @property
47018b4a343SMike Bayer    def multi_table_update(self):
47118b4a343SMike Bayer        return only_on(["mysql", "mariadb"], "Multi table update")
47218b4a343SMike Bayer
4731a777863SMike Bayer    @property
4741a777863SMike Bayer    def update_from(self):
4751a777863SMike Bayer        """Target must support UPDATE..FROM syntax"""
4761a777863SMike Bayer
4771e1a38e7SMike Bayer        return only_on(
478fc97854fSMike Bayer            ["postgresql", "mssql", "mysql", "mariadb"],
4791e1a38e7SMike Bayer            "Backend does not support UPDATE..FROM",
4801e1a38e7SMike Bayer        )
4811a777863SMike Bayer
482d12b37f9Sinytar    @property
483d12b37f9Sinytar    def delete_from(self):
484d12b37f9Sinytar        """Target must support DELETE FROM..FROM or DELETE..USING syntax"""
4851e1a38e7SMike Bayer        return only_on(
486ed78e679SFederico Caselli            ["postgresql", "mssql", "mysql", "mariadb"],
4871e1a38e7SMike Bayer            "Backend does not support DELETE..FROM",
4881e1a38e7SMike Bayer        )
489d12b37f9Sinytar
49040af03f8SMike Bayer    @property
49140af03f8SMike Bayer    def update_where_target_in_subquery(self):
492081d4275SMike Bayer        """Target must support UPDATE (or DELETE) where the same table is
493081d4275SMike Bayer        present in a subquery in the WHERE clause.
49440af03f8SMike Bayer
49540af03f8SMike Bayer        This is an ANSI-standard syntax that apparently MySQL can't handle,
4961e278de4SMike Bayer        such as::
4971e278de4SMike Bayer
4981e278de4SMike Bayer            UPDATE documents SET flag=1 WHERE documents.title IN
4991e278de4SMike Bayer                (SELECT max(documents.title) AS title
5001e278de4SMike Bayer                    FROM documents GROUP BY documents.user_id
5011e278de4SMike Bayer                )
50240af03f8SMike Bayer
50340af03f8SMike Bayer        """
504081d4275SMike Bayer        return fails_if(
505081d4275SMike Bayer            self._mysql_not_mariadb_103,
506081d4275SMike Bayer            'MySQL error 1093 "Cant specify target table '
5071e1a38e7SMike Bayer            'for update in FROM clause", resolved by MariaDB 10.3',
5081e1a38e7SMike Bayer        )
50940af03f8SMike Bayer
5101a777863SMike Bayer    @property
5111a777863SMike Bayer    def savepoints(self):
5121a777863SMike Bayer        """Target database must support savepoints."""
5131a777863SMike Bayer
5141e1a38e7SMike Bayer        return skip_if(
515ed78e679SFederico Caselli            ["sqlite", ("mysql", "<", (5, 0, 3))],
5161e1a38e7SMike Bayer            "savepoints not supported",
5171e1a38e7SMike Bayer        )
5181a777863SMike Bayer
5198a1e619fSMike Bayer    @property
5208a1e619fSMike Bayer    def savepoints_w_release(self):
5218a1e619fSMike Bayer        return self.savepoints + skip_if(
5222efd89d0SMike Bayer            ["oracle", "mssql"],
5231e1a38e7SMike Bayer            "database doesn't support release of savepoint",
5242efd89d0SMike Bayer        )
5252efd89d0SMike Bayer
5261a777863SMike Bayer    @property
5271a777863SMike Bayer    def schemas(self):
5281a777863SMike Bayer        """Target database must support external schemas, and have one
5291a777863SMike Bayer        named 'test_schema'."""
5301a777863SMike Bayer
531a1430363SGord Thompson        return exclusions.open()
5321a777863SMike Bayer
533f1bdc3e9SMike Bayer    @property
534f1bdc3e9SMike Bayer    def cross_schema_fk_reflection(self):
535c3f102c9SMike Bayer        """target system must support reflection of inter-schema foreign
536c3f102c9SMike Bayer        keys"""
537fc97854fSMike Bayer        return only_on(["postgresql", "mysql", "mariadb", "mssql"])
5381a777863SMike Bayer
5399d5e117fSMike Bayer    @property
5409d5e117fSMike Bayer    def implicit_default_schema(self):
5419d5e117fSMike Bayer        """target system has a strong concept of 'default' schema that can
542c3f102c9SMike Bayer        be referred to implicitly.
5439d5e117fSMike Bayer
544c3f102c9SMike Bayer        basically, PostgreSQL.
5459d5e117fSMike Bayer
5469d5e117fSMike Bayer        """
5471e1a38e7SMike Bayer        return only_on(["postgresql"])
5489d5e117fSMike Bayer
5499b779611SMike Bayer    @property
5509b779611SMike Bayer    def default_schema_name_switch(self):
5519b779611SMike Bayer        return only_on(["postgresql", "oracle"])
5529b779611SMike Bayer
553258d2a83SMike Bayer    @property
554258d2a83SMike Bayer    def unique_constraint_reflection(self):
555258d2a83SMike Bayer        return fails_on_everything_except(
556fc97854fSMike Bayer            "postgresql", "mysql", "mariadb", "sqlite", "oracle"
5571e1a38e7SMike Bayer        )
558258d2a83SMike Bayer
55955ad1037SMike Bayer    @property
56055ad1037SMike Bayer    def unique_constraint_reflection_no_index_overlap(self):
5611e1a38e7SMike Bayer        return (
5621e1a38e7SMike Bayer            self.unique_constraint_reflection
5631e1a38e7SMike Bayer            + skip_if("mysql")
564fc97854fSMike Bayer            + skip_if("mariadb")
5651e1a38e7SMike Bayer            + skip_if("oracle")
5661e1a38e7SMike Bayer        )
56768b52c48SMike Bayer
568a8e7bb87SAlex Grönholm    @property
569a8e7bb87SAlex Grönholm    def check_constraint_reflection(self):
5706ff05daeSMike Bayer        return fails_on_everything_except(
5711c3e9262SMike Bayer            "postgresql",
5721c3e9262SMike Bayer            "sqlite",
5731c3e9262SMike Bayer            "oracle",
5741c3e9262SMike Bayer            self._mysql_and_check_constraints_exist,
57529b752f8SMike Bayer        )
576a8e7bb87SAlex Grönholm
57708994cb9SMike Bayer    @property
57808994cb9SMike Bayer    def indexes_with_expressions(self):
57908994cb9SMike Bayer        return only_on(["postgresql", "sqlite>=3.9.0"])
58008994cb9SMike Bayer
581cb23fa24SMike Bayer    @property
582cb23fa24SMike Bayer    def temp_table_names(self):
583cb23fa24SMike Bayer        """target dialect supports listing of temporary table names"""
584cb23fa24SMike Bayer
58579a53645SGord Thompson        return only_on(["sqlite", "oracle"]) + skip_if(self._sqlite_file_db)
586cb23fa24SMike Bayer
587cb23fa24SMike Bayer    @property
588cb23fa24SMike Bayer    def temporary_views(self):
589cb23fa24SMike Bayer        """target database supports temporary views"""
59079a53645SGord Thompson        return only_on(["sqlite", "postgresql"]) + skip_if(
59179a53645SGord Thompson            self._sqlite_file_db
59279a53645SGord Thompson        )
593cb23fa24SMike Bayer
594afcab5edSMike Bayer    @property
595afcab5edSMike Bayer    def table_value_constructor(self):
596afcab5edSMike Bayer        return only_on(["postgresql", "mssql"])
597afcab5edSMike Bayer
5981a777863SMike Bayer    @property
5991a777863SMike Bayer    def update_nowait(self):
6001a777863SMike Bayer        """Target database must support SELECT...FOR UPDATE NOWAIT"""
6011e1a38e7SMike Bayer        return skip_if(
602ed78e679SFederico Caselli            ["mssql", "mysql", "mariadb<10.3", "sqlite"],
6031e1a38e7SMike Bayer            "no FOR UPDATE NOWAIT support",
6041e1a38e7SMike Bayer        )
6051a777863SMike Bayer
6061a777863SMike Bayer    @property
6071a777863SMike Bayer    def subqueries(self):
6081a777863SMike Bayer        """Target database must support subqueries."""
609fc97854fSMike Bayer        return exclusions.open()
6101a777863SMike Bayer
611e486ef66SMike Bayer    @property
612e486ef66SMike Bayer    def ctes(self):
613e486ef66SMike Bayer        """Target database supports CTEs"""
6141e1a38e7SMike Bayer        return only_on(
6151e1a38e7SMike Bayer            [
6161e1a38e7SMike Bayer                lambda config: against(config, "mysql")
6171e1a38e7SMike Bayer                and (
618e70e8a7fSMike Bayer                    (
619e70e8a7fSMike Bayer                        config.db.dialect._is_mariadb
620e70e8a7fSMike Bayer                        and config.db.dialect._mariadb_normalized_version_info
621e70e8a7fSMike Bayer                        >= (10, 2)
622e70e8a7fSMike Bayer                    )
623e70e8a7fSMike Bayer                    or (
624e70e8a7fSMike Bayer                        not config.db.dialect._is_mariadb
625e70e8a7fSMike Bayer                        and config.db.dialect.server_version_info >= (8,)
626e70e8a7fSMike Bayer                    )
6271e1a38e7SMike Bayer                ),
628fc97854fSMike Bayer                "mariadb>10.2",
6291e1a38e7SMike Bayer                "postgresql",
6301e1a38e7SMike Bayer                "mssql",
6311e1a38e7SMike Bayer                "oracle",
632e70e8a7fSMike Bayer                "sqlite>=3.8.3",
6331e1a38e7SMike Bayer            ]
6341e1a38e7SMike Bayer        )
6353619edcbSMike Bayer
6363619edcbSMike Bayer    @property
6373619edcbSMike Bayer    def ctes_with_update_delete(self):
6383619edcbSMike Bayer        """target database supports CTES that ride on top of a normal UPDATE
6393619edcbSMike Bayer        or DELETE statement which refers to the CTE in a correlated subquery.
6403619edcbSMike Bayer
6413619edcbSMike Bayer        """
6421e1a38e7SMike Bayer        return only_on(
6431e1a38e7SMike Bayer            [
6441e1a38e7SMike Bayer                "postgresql",
6451e1a38e7SMike Bayer                "mssql",
6461e1a38e7SMike Bayer                # "oracle" - oracle can do this but SQLAlchemy doesn't support
6471e1a38e7SMike Bayer                # their syntax yet
6481e1a38e7SMike Bayer            ]
6491e1a38e7SMike Bayer        )
650e486ef66SMike Bayer
6512efd89d0SMike Bayer    @property
6522efd89d0SMike Bayer    def ctes_on_dml(self):
6532efd89d0SMike Bayer        """target database supports CTES which consist of INSERT, UPDATE
6543619edcbSMike Bayer        or DELETE *within* the CTE, e.g. WITH x AS (UPDATE....)"""
6552efd89d0SMike Bayer
6561e1a38e7SMike Bayer        return only_if(["postgresql"])
6572efd89d0SMike Bayer
65847858b85SMike Bayer    @property
65947858b85SMike Bayer    def mod_operator_as_percent_sign(self):
66047858b85SMike Bayer        """target database must use a plain percent '%' as the 'modulus'
66147858b85SMike Bayer        operator."""
66247858b85SMike Bayer
663fc97854fSMike Bayer        return only_if(
664fc97854fSMike Bayer            ["mysql", "mariadb", "sqlite", "postgresql+psycopg2", "mssql"]
665fc97854fSMike Bayer        )
66647858b85SMike Bayer
6671a777863SMike Bayer    @property
6681a777863SMike Bayer    def intersect(self):
6691a777863SMike Bayer        """Target database must support INTERSECT or equivalent."""
6701a777863SMike Bayer
6711e1a38e7SMike Bayer        return fails_if(
672ed78e679SFederico Caselli            [self._mysql_not_mariadb_103],
6731e1a38e7SMike Bayer            "no support for INTERSECT",
6741e1a38e7SMike Bayer        )
6751a777863SMike Bayer
6761a777863SMike Bayer    @property
6771a777863SMike Bayer    def except_(self):
6781a777863SMike Bayer        """Target database must support EXCEPT or equivalent (i.e. MINUS)."""
679ed78e679SFederico Caselli        return fails_if([self._mysql_not_mariadb_103], "no support for EXCEPT")
6801a777863SMike Bayer
681aeee452eSMike Bayer    @property
682aeee452eSMike Bayer    def dupe_order_by_ok(self):
683aeee452eSMike Bayer        """target db wont choke if ORDER BY specifies the same expression
684aeee452eSMike Bayer        more than once
685aeee452eSMike Bayer
686aeee452eSMike Bayer        """
687aeee452eSMike Bayer
688aeee452eSMike Bayer        return skip_if("mssql")
689aeee452eSMike Bayer
6902efd89d0SMike Bayer    @property
6912efd89d0SMike Bayer    def order_by_col_from_union(self):
6922efd89d0SMike Bayer        """target database supports ordering by a column from a SELECT
6932efd89d0SMike Bayer        inside of a UNION
6942efd89d0SMike Bayer
6952efd89d0SMike Bayer        E.g.  (SELECT id, ...) UNION (SELECT id, ...) ORDER BY id
6962efd89d0SMike Bayer
6972efd89d0SMike Bayer        Fails on SQL Server
6982efd89d0SMike Bayer
6992efd89d0SMike Bayer        """
7001e1a38e7SMike Bayer        return fails_if("mssql")
7012efd89d0SMike Bayer
70288749550SMike Bayer    @property
703e1129b2dSMike Bayer    def parens_in_union_contained_select_w_limit_offset(self):
704e1129b2dSMike Bayer        """Target database must support parenthesized SELECT in UNION
705e1129b2dSMike Bayer        when LIMIT/OFFSET is specifically present.
70688749550SMike Bayer
7072efd89d0SMike Bayer        E.g. (SELECT ... LIMIT ..) UNION (SELECT .. OFFSET ..)
70888749550SMike Bayer
709e1129b2dSMike Bayer        This is known to fail on SQLite.
710e1129b2dSMike Bayer
71188749550SMike Bayer        """
7121e1a38e7SMike Bayer        return fails_if("sqlite")
71388749550SMike Bayer
714e1129b2dSMike Bayer    @property
715e1129b2dSMike Bayer    def parens_in_union_contained_select_wo_limit_offset(self):
716e1129b2dSMike Bayer        """Target database must support parenthesized SELECT in UNION
717e1129b2dSMike Bayer        when OFFSET/LIMIT is specifically not present.
718e1129b2dSMike Bayer
7192efd89d0SMike Bayer        E.g. (SELECT ...) UNION (SELECT ..)
720e1129b2dSMike Bayer
721e1129b2dSMike Bayer        This is known to fail on SQLite.  It also fails on Oracle
722e1129b2dSMike Bayer        because without LIMIT/OFFSET, there is currently no step that
723e1129b2dSMike Bayer        creates an additional subquery.
724e1129b2dSMike Bayer
725e1129b2dSMike Bayer        """
7261e1a38e7SMike Bayer        return fails_if(["sqlite", "oracle"])
727e1129b2dSMike Bayer
72897d2a209SMike Bayer    @property
72997d2a209SMike Bayer    def sql_expression_limit_offset(self):
73097d2a209SMike Bayer        return (
73197d2a209SMike Bayer            fails_if(
732fc97854fSMike Bayer                ["mysql", "mariadb"],
733a3466404SMike Bayer                "Target backend can't accommodate full expressions in "
734a3466404SMike Bayer                "OFFSET or LIMIT",
73597d2a209SMike Bayer            )
73697d2a209SMike Bayer            + self.offset
73797d2a209SMike Bayer        )
73897d2a209SMike Bayer
7391a777863SMike Bayer    @property
7401a777863SMike Bayer    def window_functions(self):
7411e1a38e7SMike Bayer        return only_if(
7423fc9f11cSMike Bayer            [
7433fc9f11cSMike Bayer                "postgresql>=8.4",
7443fc9f11cSMike Bayer                "mssql",
7453fc9f11cSMike Bayer                "oracle",
7463fc9f11cSMike Bayer                "sqlite>=3.25.0",
7473fc9f11cSMike Bayer                "mysql>=8",
7483fc9f11cSMike Bayer                "mariadb>=10.2",
7493fc9f11cSMike Bayer            ],
7501e1a38e7SMike Bayer            "Backend does not support window functions",
7511e1a38e7SMike Bayer        )
7521a777863SMike Bayer
7531a777863SMike Bayer    @property
7541a777863SMike Bayer    def two_phase_transactions(self):
7551a777863SMike Bayer        """Target database must support two-phase transactions."""
7561a777863SMike Bayer
75792fd25f3SFederico Caselli        def pg_prepared_transaction(config):
75892fd25f3SFederico Caselli            if not against(config, "postgresql"):
7590e53221eSMike Bayer                return True
76092fd25f3SFederico Caselli
76192fd25f3SFederico Caselli            with config.db.connect() as conn:
76292fd25f3SFederico Caselli                try:
76392fd25f3SFederico Caselli                    num = conn.scalar(
76492fd25f3SFederico Caselli                        text(
76592fd25f3SFederico Caselli                            "select cast(setting AS integer) from pg_settings "
76692fd25f3SFederico Caselli                            "where name = 'max_prepared_transactions'"
76792fd25f3SFederico Caselli                        )
76892fd25f3SFederico Caselli                    )
76992fd25f3SFederico Caselli                except exc.OperationalError:
77092fd25f3SFederico Caselli                    return False
77192fd25f3SFederico Caselli                else:
77292fd25f3SFederico Caselli                    return num > 0
77392fd25f3SFederico Caselli
7741e1a38e7SMike Bayer        return skip_if(
7751e1a38e7SMike Bayer            [
7761e1a38e7SMike Bayer                no_support("mssql", "two-phase xact not supported by drivers"),
7771e1a38e7SMike Bayer                no_support(
7781e1a38e7SMike Bayer                    "sqlite", "two-phase xact not supported by database"
7791e1a38e7SMike Bayer                ),
7800e53221eSMike Bayer                # in Ia3cbbf56d4882fcc7980f90519412f1711fae74d
7810e53221eSMike Bayer                # we are evaluating which modern MySQL / MariaDB versions
7820e53221eSMike Bayer                # can handle two-phase testing without too many problems
7830e53221eSMike Bayer                # no_support(
7840e53221eSMike Bayer                #     "mysql",
7850e53221eSMike Bayer                #    "recent MySQL communiity editions have too many issues "
7860e53221eSMike Bayer                #    "(late 2016), disabling for now",
7870e53221eSMike Bayer                # ),
78892fd25f3SFederico Caselli                NotPredicate(
78992fd25f3SFederico Caselli                    LambdaPredicate(
79092fd25f3SFederico Caselli                        pg_prepared_transaction,
79192fd25f3SFederico Caselli                        "max_prepared_transactions not available or zero",
79292fd25f3SFederico Caselli                    )
79392fd25f3SFederico Caselli                ),
7941e1a38e7SMike Bayer            ]
7951e1a38e7SMike Bayer        )
7961a777863SMike Bayer
79702190234SMike Bayer    @property
79802190234SMike Bayer    def two_phase_recovery(self):
79902190234SMike Bayer        return self.two_phase_transactions + (
8000e53221eSMike Bayer            skip_if(
801fc97854fSMike Bayer                ["mysql", "mariadb"],
802fc97854fSMike Bayer                "still can't get recover to work w/ MariaDB / MySQL",
8030e53221eSMike Bayer            )
8048bf5ca1eSMike Bayer            + skip_if("oracle", "recovery not functional")
80502190234SMike Bayer        )
80602190234SMike Bayer
8071a777863SMike Bayer    @property
8081a777863SMike Bayer    def views(self):
8091a777863SMike Bayer        """Target database must support VIEWs."""
8101a777863SMike Bayer
8111a777863SMike Bayer        return skip_if("drizzle", "no VIEW support")
8121a777863SMike Bayer
813d2410df9SMike Bayer    @property
814675558bfSMike Bayer    def empty_strings_varchar(self):
81577237473SKhairi Hafsham        """
81677237473SKhairi Hafsham        target database can persist/return an empty string with a varchar.
81777237473SKhairi Hafsham        """
818675558bfSMike Bayer
8191e1a38e7SMike Bayer        return fails_if(
8201e1a38e7SMike Bayer            ["oracle"], "oracle converts empty strings to a blank space"
8211e1a38e7SMike Bayer        )
822675558bfSMike Bayer
823675558bfSMike Bayer    @property
824675558bfSMike Bayer    def empty_strings_text(self):
825675558bfSMike Bayer        """target database can persist/return an empty string with an
826675558bfSMike Bayer        unbounded text."""
827d2410df9SMike Bayer
828fb28e40bSMike Bayer        return fails_if(
829fb28e40bSMike Bayer            ["oracle"], "oracle converts empty strings to a blank space"
830fb28e40bSMike Bayer        )
831d2410df9SMike Bayer
83294a1c523SMike Bayer    @property
83394a1c523SMike Bayer    def empty_inserts_executemany(self):
83494a1c523SMike Bayer        # waiting on https://jira.mariadb.org/browse/CONPY-152
83594a1c523SMike Bayer        return skip_if(["mariadb+mariadbconnector"]) + self.empty_inserts
83694a1c523SMike Bayer
837c0e6ebd7SMike Bayer    @property
838c0e6ebd7SMike Bayer    def expressions_against_unbounded_text(self):
839c0e6ebd7SMike Bayer        """target database supports use of an unbounded textual field in a
840c0e6ebd7SMike Bayer        WHERE clause."""
841c0e6ebd7SMike Bayer
842c0e6ebd7SMike Bayer        return fails_if(
843c0e6ebd7SMike Bayer            ["oracle"],
844c0e6ebd7SMike Bayer            "ORA-00932: inconsistent datatypes: expected - got CLOB",
845c0e6ebd7SMike Bayer        )
846c0e6ebd7SMike Bayer
8471a777863SMike Bayer    @property
8481a777863SMike Bayer    def unicode_connections(self):
84977237473SKhairi Hafsham        """
85077237473SKhairi Hafsham        Target driver must support some encoding of Unicode across the wire.
851fc97854fSMike Bayer
85277237473SKhairi Hafsham        """
853fc97854fSMike Bayer        return exclusions.open()
8541a777863SMike Bayer
8551a777863SMike Bayer    @property
8561a777863SMike Bayer    def unicode_ddl(self):
857a0ef9edcSMike Bayer        """Target driver must support some degree of non-ascii symbol names."""
85863c6aa01SMike Bayer
8591e1a38e7SMike Bayer        return skip_if(
8601e1a38e7SMike Bayer            [
8611e1a38e7SMike Bayer                no_support("mssql+pymssql", "no FreeTDS support"),
8621e1a38e7SMike Bayer            ]
8631e1a38e7SMike Bayer        )
8641a777863SMike Bayer
8659d0fb152SMike Bayer    @property
8669d0fb152SMike Bayer    def symbol_names_w_double_quote(self):
867c3f102c9SMike Bayer        """Target driver can create tables with a name like 'some " table'"""
8689d0fb152SMike Bayer
8699d0fb152SMike Bayer        return skip_if(
8709d0fb152SMike Bayer            [no_support("oracle", "ORA-03001: unimplemented feature")]
8719d0fb152SMike Bayer        )
8729d0fb152SMike Bayer
8731a777863SMike Bayer    @property
8741a777863SMike Bayer    def emulated_lastrowid(self):
875c3f102c9SMike Bayer        """ "target dialect retrieves cursor.lastrowid or an equivalent
8761a777863SMike Bayer        after an insert() construct executes.
8771a777863SMike Bayer        """
8781e1a38e7SMike Bayer        return fails_on_everything_except(
879fc97854fSMike Bayer            "mysql",
880fc97854fSMike Bayer            "mariadb",
881502be87aSFederico Caselli            "sqlite+aiosqlite",
882fc97854fSMike Bayer            "sqlite+pysqlite",
883fc97854fSMike Bayer            "sqlite+pysqlcipher",
884fc97854fSMike Bayer            "mssql",
885fc97854fSMike Bayer        )
886fc97854fSMike Bayer
887fc97854fSMike Bayer    @property
888fc97854fSMike Bayer    def emulated_lastrowid_even_with_sequences(self):
889c3f102c9SMike Bayer        """ "target dialect retrieves cursor.lastrowid or an equivalent
890fc97854fSMike Bayer        after an insert() construct executes, even if the table has a
8919ab4da70SFederico Caselli        Sequence on it.
892fc97854fSMike Bayer        """
893fc97854fSMike Bayer        return fails_on_everything_except(
894fc97854fSMike Bayer            "mysql",
895fc97854fSMike Bayer            "mariadb",
896fc97854fSMike Bayer            "sqlite+pysqlite",
897fc97854fSMike Bayer            "sqlite+pysqlcipher",
8981e1a38e7SMike Bayer        )
8991a777863SMike Bayer
9001a777863SMike Bayer    @property
9011a777863SMike Bayer    def dbapi_lastrowid(self):
902c3f102c9SMike Bayer        """ "target backend includes a 'lastrowid' accessor on the DBAPI
9031a777863SMike Bayer        cursor object.
9041a777863SMike Bayer
9051a777863SMike Bayer        """
9063df6a3f1SFederico Caselli        return skip_if("mssql+pymssql", "crashes on pymssql") + only_on(
9073df6a3f1SFederico Caselli            [
9083df6a3f1SFederico Caselli                "mysql",
9093df6a3f1SFederico Caselli                "mariadb",
9103df6a3f1SFederico Caselli                "sqlite+pysqlite",
9113df6a3f1SFederico Caselli                "sqlite+aiosqlite",
9123df6a3f1SFederico Caselli                "sqlite+pysqlcipher",
9133df6a3f1SFederico Caselli                "mssql",
9143df6a3f1SFederico Caselli            ]
9151e1a38e7SMike Bayer        )
9161a777863SMike Bayer
9171a777863SMike Bayer    @property
9181a777863SMike Bayer    def nullsordering(self):
9191a777863SMike Bayer        """Target backends that support nulls ordering."""
9203a0e0531SCaselIT        return fails_on_everything_except(
921ed78e679SFederico Caselli            "postgresql", "oracle", "sqlite >= 3.30.0"
9223a0e0531SCaselIT        )
9231a777863SMike Bayer
9241a777863SMike Bayer    @property
9251a777863SMike Bayer    def reflects_pk_names(self):
9261a777863SMike Bayer        """Target driver reflects the name of primary key constraints."""
927f4ba5b85SMike Bayer
9281e1a38e7SMike Bayer        return fails_on_everything_except(
929ed78e679SFederico Caselli            "postgresql", "oracle", "mssql", "sqlite"
9301e1a38e7SMike Bayer        )
9311a777863SMike Bayer
9322efd89d0SMike Bayer    @property
9332efd89d0SMike Bayer    def nested_aggregates(self):
9342efd89d0SMike Bayer        """target database can select an aggregate from a subquery that's
9352efd89d0SMike Bayer        also using an aggregate"""
9362efd89d0SMike Bayer
9376b5d94daSNils Philippsen        return skip_if(["mssql", "sqlite"])
9382efd89d0SMike Bayer
939afcab5edSMike Bayer    @property
940afcab5edSMike Bayer    def tuple_valued_builtin_functions(self):
941afcab5edSMike Bayer        return only_on(
942afcab5edSMike Bayer            lambda config: self._sqlite_json(config)
943afcab5edSMike Bayer            or against(config, "postgresql")
944afcab5edSMike Bayer        )
945afcab5edSMike Bayer
9460620a76bSJeong YunWon    @property
9470620a76bSJeong YunWon    def array_type(self):
9481e1a38e7SMike Bayer        return only_on(
9491e1a38e7SMike Bayer            [
9501e1a38e7SMike Bayer                lambda config: against(config, "postgresql")
9511e1a38e7SMike Bayer                and not against(config, "+pg8000")
9521e1a38e7SMike Bayer            ]
9531e1a38e7SMike Bayer        )
9540620a76bSJeong YunWon
955a80bb4e5SMike Bayer    @property
956a80bb4e5SMike Bayer    def json_type(self):
9571e1a38e7SMike Bayer        return only_on(
9581e1a38e7SMike Bayer            [
9591e1a38e7SMike Bayer                lambda config: against(config, "mysql")
9601e1a38e7SMike Bayer                and (
961fa2f0c93SMike Bayer                    (
9621e1a38e7SMike Bayer                        not config.db.dialect._is_mariadb
9631e1a38e7SMike Bayer                        and against(config, "mysql >= 5.7")
964fa2f0c93SMike Bayer                    )
965fa2f0c93SMike Bayer                    or (
9661e1a38e7SMike Bayer                        config.db.dialect._mariadb_normalized_version_info
9671e1a38e7SMike Bayer                        >= (10, 2, 7)
968fa2f0c93SMike Bayer                    )
969fa2f0c93SMike Bayer                ),
970fc97854fSMike Bayer                "mariadb>=10.2.7",
9711e1a38e7SMike Bayer                "postgresql >= 9.3",
972c2e8b48aSMike Bayer                self._sqlite_json,
9730c7a867aSGord Thompson                "mssql",
9741e1a38e7SMike Bayer            ]
9751e1a38e7SMike Bayer        )
976fa2f0c93SMike Bayer
97701cbf4d7SMike Bayer    @property
97801cbf4d7SMike Bayer    def json_index_supplementary_unicode_element(self):
97901cbf4d7SMike Bayer        # for sqlite see https://bugs.python.org/issue38749
98001cbf4d7SMike Bayer        return skip_if(
98101cbf4d7SMike Bayer            [
98201cbf4d7SMike Bayer                lambda config: against(config, "mysql")
98301cbf4d7SMike Bayer                and config.db.dialect._is_mariadb,
984fc97854fSMike Bayer                "mariadb",
98501cbf4d7SMike Bayer                "sqlite",
98601cbf4d7SMike Bayer            ]
98701cbf4d7SMike Bayer        )
98801cbf4d7SMike Bayer
9890c7a867aSGord Thompson    @property
9900c7a867aSGord Thompson    def legacy_unconditional_json_extract(self):
9910c7a867aSGord Thompson        """Backend has a JSON_EXTRACT or similar function that returns a
9920c7a867aSGord Thompson        valid JSON string in all cases.
9930c7a867aSGord Thompson
9940c7a867aSGord Thompson        Used to test a legacy feature and is not needed.
9950c7a867aSGord Thompson
9960c7a867aSGord Thompson        """
9970c7a867aSGord Thompson        return self.json_type + only_on(
9980c7a867aSGord Thompson            ["postgresql", "mysql", "mariadb", "sqlite"]
9990c7a867aSGord Thompson        )
10000c7a867aSGord Thompson
100179a53645SGord Thompson    def _sqlite_file_db(self, config):
100279a53645SGord Thompson        return against(config, "sqlite") and config.db.dialect._is_url_file_db(
100379a53645SGord Thompson            config.db.url
100479a53645SGord Thompson        )
100579a53645SGord Thompson
100679a53645SGord Thompson    def _sqlite_memory_db(self, config):
1007379e878cSMike Bayer        return against(
1008379e878cSMike Bayer            config, "sqlite"
1009379e878cSMike Bayer        ) and not config.db.dialect._is_url_file_db(config.db.url)
101079a53645SGord Thompson
1011c2e8b48aSMike Bayer    def _sqlite_json(self, config):
1012c2e8b48aSMike Bayer        if not against(config, "sqlite >= 3.9"):
1013c2e8b48aSMike Bayer            return False
1014c2e8b48aSMike Bayer        else:
1015c2e8b48aSMike Bayer            with config.db.connect() as conn:
1016c2e8b48aSMike Bayer                try:
1017c2e8b48aSMike Bayer                    return (
10189ec75882SFederico Caselli                        conn.exec_driver_sql(
1019c2e8b48aSMike Bayer                            """select json_extract('{"foo": "bar"}', """
1020c2e8b48aSMike Bayer                            """'$."foo"')"""
10219ec75882SFederico Caselli                        ).scalar()
1022c2e8b48aSMike Bayer                        == "bar"
1023c2e8b48aSMike Bayer                    )
1024c2e8b48aSMike Bayer                except exc.DBAPIError:
1025c2e8b48aSMike Bayer                    return False
1026c2e8b48aSMike Bayer
102720c0f774SMike Bayer    @property
102820c0f774SMike Bayer    def sqlite_memory(self):
102920c0f774SMike Bayer        return only_on(self._sqlite_memory_db)
103020c0f774SMike Bayer
1031fa2f0c93SMike Bayer    @property
1032fa2f0c93SMike Bayer    def reflects_json_type(self):
10331e1a38e7SMike Bayer        return only_on(
10341e1a38e7SMike Bayer            [
10351e1a38e7SMike Bayer                lambda config: against(config, "mysql >= 5.7")
10361e1a38e7SMike Bayer                and not config.db.dialect._is_mariadb,
10371e1a38e7SMike Bayer                "postgresql >= 9.3",
10381e1a38e7SMike Bayer                "sqlite >= 3.9",
10391e1a38e7SMike Bayer            ]
10401e1a38e7SMike Bayer        )
1041a80bb4e5SMike Bayer
1042ff803b1aSMike Bayer    @property
1043ff803b1aSMike Bayer    def json_array_indexes(self):
104406f1929bSTony Locke        return self.json_type
1045ff803b1aSMike Bayer
10464663ec98SMike Bayer    @property
10474663ec98SMike Bayer    def datetime_literals(self):
10484663ec98SMike Bayer        """target dialect supports rendering of a date, time, or datetime as a
10494663ec98SMike Bayer        literal string, e.g. via the TypeEngine.literal_processor() method.
10504663ec98SMike Bayer
10514663ec98SMike Bayer        """
10524663ec98SMike Bayer
10534663ec98SMike Bayer        return fails_on_everything_except("sqlite")
10544663ec98SMike Bayer
105512df8a99SMike Bayer    @property
105612df8a99SMike Bayer    def datetime(self):
105712df8a99SMike Bayer        """target dialect supports representation of Python
105812df8a99SMike Bayer        datetime.datetime() objects."""
105912df8a99SMike Bayer
106012df8a99SMike Bayer        return exclusions.open()
106112df8a99SMike Bayer
106212df8a99SMike Bayer    @property
106312df8a99SMike Bayer    def datetime_microseconds(self):
106412df8a99SMike Bayer        """target dialect supports representation of Python
106512df8a99SMike Bayer        datetime.datetime() with microsecond objects."""
106612df8a99SMike Bayer
1067ed78e679SFederico Caselli        return skip_if(["mssql", "mysql", "mariadb", "oracle"])
106812df8a99SMike Bayer
106980a2241eSMike Bayer    @property
107080a2241eSMike Bayer    def timestamp_microseconds(self):
107180a2241eSMike Bayer        """target dialect supports representation of Python
107280a2241eSMike Bayer        datetime.datetime() with microsecond objects but only
107380a2241eSMike Bayer        if TIMESTAMP is used."""
107480a2241eSMike Bayer
10751e1a38e7SMike Bayer        return only_on(["oracle"])
107680a2241eSMike Bayer
107712df8a99SMike Bayer    @property
107812df8a99SMike Bayer    def datetime_historic(self):
107912df8a99SMike Bayer        """target dialect supports representation of Python
108012df8a99SMike Bayer        datetime.datetime() objects with historic (pre 1900) values."""
108112df8a99SMike Bayer
1082ed78e679SFederico Caselli        return succeeds_if(["sqlite", "postgresql"])
108312df8a99SMike Bayer
108412df8a99SMike Bayer    @property
108512df8a99SMike Bayer    def date(self):
108612df8a99SMike Bayer        """target dialect supports representation of Python
108712df8a99SMike Bayer        datetime.date() objects."""
108812df8a99SMike Bayer
108912df8a99SMike Bayer        return exclusions.open()
109012df8a99SMike Bayer
109150576a01SMike Bayer    @property
109250576a01SMike Bayer    def date_coerces_from_datetime(self):
109350576a01SMike Bayer        """target dialect accepts a datetime object as the target
109450576a01SMike Bayer        of a date column."""
109550576a01SMike Bayer
1096907c3dc1SMike Bayer        # does not work as of pyodbc 4.0.22
10971e1a38e7SMike Bayer        return fails_on("mysql+mysqlconnector") + skip_if("mssql+pyodbc")
109850576a01SMike Bayer
109912df8a99SMike Bayer    @property
110012df8a99SMike Bayer    def date_historic(self):
110112df8a99SMike Bayer        """target dialect supports representation of Python
110212df8a99SMike Bayer        datetime.datetime() objects with historic (pre 1900) values."""
110312df8a99SMike Bayer
1104ed78e679SFederico Caselli        return succeeds_if(["sqlite", "postgresql"])
110512df8a99SMike Bayer
110612df8a99SMike Bayer    @property
110712df8a99SMike Bayer    def time(self):
110812df8a99SMike Bayer        """target dialect supports representation of Python
110912df8a99SMike Bayer        datetime.time() objects."""
111012df8a99SMike Bayer
11111e1a38e7SMike Bayer        return skip_if(["oracle"])
111212df8a99SMike Bayer
111312df8a99SMike Bayer    @property
111412df8a99SMike Bayer    def time_microseconds(self):
111512df8a99SMike Bayer        """target dialect supports representation of Python
111612df8a99SMike Bayer        datetime.time() with microsecond objects."""
111712df8a99SMike Bayer
1118ed78e679SFederico Caselli        return skip_if(["mssql", "mysql", "mariadb", "oracle"])
111932a1db36SMike Bayer
11208550a4c3SMike Bayer    @property
11218550a4c3SMike Bayer    def precision_numerics_general(self):
11228550a4c3SMike Bayer        """target backend has general support for moderately high-precision
11238550a4c3SMike Bayer        numerics."""
11244b923d37SMike Bayer        return exclusions.open()
11258550a4c3SMike Bayer
11268550a4c3SMike Bayer    @property
11278550a4c3SMike Bayer    def precision_numerics_enotation_small(self):
11288550a4c3SMike Bayer        """target backend supports Decimal() objects using E notation
11298550a4c3SMike Bayer        to represent very small values."""
11304b923d37SMike Bayer        # NOTE: this exclusion isn't used in current tests.
11314b923d37SMike Bayer        return exclusions.open()
11328550a4c3SMike Bayer
11338550a4c3SMike Bayer    @property
11348550a4c3SMike Bayer    def precision_numerics_many_significant_digits(self):
11358550a4c3SMike Bayer        """target backend supports values with many digits on both sides,
11368550a4c3SMike Bayer        such as 319438950232418390.273596, 87673.594069654243
11378550a4c3SMike Bayer
11388550a4c3SMike Bayer        """
113931f80b9eSMike Bayer
114031f80b9eSMike Bayer        def broken_cx_oracle(config):
11411e1a38e7SMike Bayer            return (
11421e1a38e7SMike Bayer                against(config, "oracle+cx_oracle")
11431e1a38e7SMike Bayer                and config.db.dialect.cx_oracle_ver <= (6, 0, 2)
11441e1a38e7SMike Bayer                and config.db.dialect.cx_oracle_ver > (6,)
11451e1a38e7SMike Bayer            )
114603255a5aSMike Bayer
11478550a4c3SMike Bayer        return fails_if(
114831f80b9eSMike Bayer            [
11491e1a38e7SMike Bayer                ("sqlite", None, None, "TODO"),
11502efd89d0SMike Bayer            ]
115177237473SKhairi Hafsham        )
11528550a4c3SMike Bayer
11533b8a1415SGord Thompson    @property
11543b8a1415SGord Thompson    def cast_precision_numerics_many_significant_digits(self):
11553b8a1415SGord Thompson        """same as precision_numerics_many_significant_digits but within the
11563b8a1415SGord Thompson        context of a CAST statement (hello MySQL)
11573b8a1415SGord Thompson
11583b8a1415SGord Thompson        """
11593b8a1415SGord Thompson        return self.precision_numerics_many_significant_digits + fails_if(
11603b8a1415SGord Thompson            "mysql"
11613b8a1415SGord Thompson        )
11623b8a1415SGord Thompson
11638550a4c3SMike Bayer    @property
11648550a4c3SMike Bayer    def precision_numerics_retains_significant_digits(self):
11658550a4c3SMike Bayer        """A precision numeric type will return empty significant digits,
11668550a4c3SMike Bayer        i.e. a value such as 10.000 will come back in Decimal form with
11678550a4c3SMike Bayer        the .000 maintained."""
11688550a4c3SMike Bayer
11698550a4c3SMike Bayer        return fails_if(
117077237473SKhairi Hafsham            [
117131f80b9eSMike Bayer                ("oracle", None, None, "driver doesn't do this automatically"),
117277237473SKhairi Hafsham            ]
117377237473SKhairi Hafsham        )
11748550a4c3SMike Bayer
11756b79d2eaSMike Bayer    @property
11766b79d2eaSMike Bayer    def precision_generic_float_type(self):
11776b79d2eaSMike Bayer        """target backend will return native floating point numbers with at
11786b79d2eaSMike Bayer        least seven decimal places when using the generic Float type."""
11796b79d2eaSMike Bayer
11801e1a38e7SMike Bayer        return fails_if(
11811e1a38e7SMike Bayer            [
11821e1a38e7SMike Bayer                (
11831e1a38e7SMike Bayer                    "mysql",
11841e1a38e7SMike Bayer                    None,
11851e1a38e7SMike Bayer                    None,
11861e1a38e7SMike Bayer                    "mysql FLOAT type only returns 4 decimals",
11871e1a38e7SMike Bayer                ),
1188fc97854fSMike Bayer                (
1189fc97854fSMike Bayer                    "mariadb",
1190fc97854fSMike Bayer                    None,
1191fc97854fSMike Bayer                    None,
1192fc97854fSMike Bayer                    "mysql FLOAT type only returns 4 decimals",
1193fc97854fSMike Bayer                ),
11941e1a38e7SMike Bayer            ]
11951e1a38e7SMike Bayer        )
11968550a4c3SMike Bayer
11979a5be7bbSMike Bayer    @property
11989a5be7bbSMike Bayer    def implicit_decimal_binds(self):
11999a5be7bbSMike Bayer        """target backend will return a selected Decimal as a Decimal, not
12009a5be7bbSMike Bayer        a string.
12019a5be7bbSMike Bayer
12029a5be7bbSMike Bayer        e.g.::
12039a5be7bbSMike Bayer
12049a5be7bbSMike Bayer            expr = decimal.Decimal("15.7563")
12059a5be7bbSMike Bayer
12069a5be7bbSMike Bayer            value = e.scalar(
1207e8600608SFederico Caselli                select(literal(expr))
12089a5be7bbSMike Bayer            )
12099a5be7bbSMike Bayer
12109a5be7bbSMike Bayer            assert value == expr
12119a5be7bbSMike Bayer
12129a5be7bbSMike Bayer        See :ticket:`4036`
12139a5be7bbSMike Bayer
12149a5be7bbSMike Bayer        """
12159a5be7bbSMike Bayer
1216fc97854fSMike Bayer        return exclusions.open()
12179a5be7bbSMike Bayer
12181a777863SMike Bayer    @property
12191eb92e50SMike Bayer    def fetch_null_from_numeric(self):
12201e1a38e7SMike Bayer        return skip_if(("mssql+pyodbc", None, None, "crashes due to bug #351"))
12211eb92e50SMike Bayer
12225ca14954SMike Bayer    @property
12235ca14954SMike Bayer    def duplicate_key_raises_integrity_error(self):
122406f1929bSTony Locke        return exclusions.open()
12255ca14954SMike Bayer
12268d318deeSMike Bayer    def _has_pg_extension(self, name):
12278d318deeSMike Bayer        def check(config):
1228ea05a232SMike Bayer            if not against(config, "postgresql"):
12294356741cSMike Bayer                return False
1230aa026c30SMike Bayer            with config.db.connect() as conn:
1231aa026c30SMike Bayer                count = conn.exec_driver_sql(
12329ec75882SFederico Caselli                    "SELECT count(*) FROM pg_extension "
12339ec75882SFederico Caselli                    "WHERE extname='%s'" % name
1234aa026c30SMike Bayer                ).scalar()
12358d318deeSMike Bayer            return bool(count)
12361e1a38e7SMike Bayer
12378d318deeSMike Bayer        return only_if(check, "needs %s extension" % name)
12384356741cSMike Bayer
12398d318deeSMike Bayer    @property
12408d318deeSMike Bayer    def hstore(self):
12418d318deeSMike Bayer        return self._has_pg_extension("hstore")
12428d318deeSMike Bayer
12438d318deeSMike Bayer    @property
12448d318deeSMike Bayer    def btree_gist(self):
12458d318deeSMike Bayer        return self._has_pg_extension("btree_gist")
12464356741cSMike Bayer
124770edfa22SChris Withers    @property
124870edfa22SChris Withers    def range_types(self):
1249ea05a232SMike Bayer        def check_range_types(config):
12504e6ec9eeSMike Bayer            if not against(
12511e1a38e7SMike Bayer                config, ["postgresql+psycopg2", "postgresql+psycopg2cffi"]
12521e1a38e7SMike Bayer            ):
125370edfa22SChris Withers                return False
125470edfa22SChris Withers            try:
1255aa026c30SMike Bayer                with config.db.connect() as conn:
1256aa026c30SMike Bayer                    conn.exec_driver_sql("select '[1,2)'::int4range;").scalar()
125770edfa22SChris Withers                return True
125877237473SKhairi Hafsham            except Exception:
125970edfa22SChris Withers                return False
126070edfa22SChris Withers
126170edfa22SChris Withers        return only_if(check_range_types)
126270edfa22SChris Withers
12635fb0138aSMike Bayer    @property
12645fb0138aSMike Bayer    def async_dialect(self):
12655fb0138aSMike Bayer        """dialect makes use of await_() to invoke operations on the DBAPI."""
12665fb0138aSMike Bayer
12675f333762SMike Bayer        return only_on(
12682581655cSFederico Caselli            LambdaPredicate(
12692581655cSFederico Caselli                lambda config: config.db.dialect.is_async,
12702581655cSFederico Caselli                "Async dialect required",
12712581655cSFederico Caselli            )
12725f333762SMike Bayer        )
12735fb0138aSMike Bayer
127470d38af4SMike Bayer    @property
127570d38af4SMike Bayer    def oracle_test_dblink(self):
127670d38af4SMike Bayer        return skip_if(
12771e1a38e7SMike Bayer            lambda config: not config.file_config.has_option(
12781e1a38e7SMike Bayer                "sqla_testing", "oracle_db_link"
12791e1a38e7SMike Bayer            ),
12801e1a38e7SMike Bayer            "oracle_db_link option not specified in config",
12811e1a38e7SMike Bayer        )
128270d38af4SMike Bayer
1283649f0675SRodrigo Menezes    @property
1284649f0675SRodrigo Menezes    def postgresql_test_dblink(self):
1285649f0675SRodrigo Menezes        return skip_if(
12861e1a38e7SMike Bayer            lambda config: not config.file_config.has_option(
12871e1a38e7SMike Bayer                "sqla_testing", "postgres_test_db_link"
12881e1a38e7SMike Bayer            ),
12891e1a38e7SMike Bayer            "postgres_test_db_link option not specified in config",
12901e1a38e7SMike Bayer        )
1291fbd2d70aSRodrigo Menezes
1292cfb2eee0SMike Bayer    @property
1293cfb2eee0SMike Bayer    def postgresql_jsonb(self):
1294b653fedcSMike Bayer        return only_on("postgresql >= 9.4") + skip_if(
12951e1a38e7SMike Bayer            lambda config: config.db.dialect.driver == "pg8000"
12961e1a38e7SMike Bayer            and config.db.dialect._dbapi_version <= (1, 10, 1)
1297cfb2eee0SMike Bayer        )
1298cfb2eee0SMike Bayer
12994e6ec9eeSMike Bayer    @property
13004e6ec9eeSMike Bayer    def psycopg2_native_hstore(self):
13014e6ec9eeSMike Bayer        return self.psycopg2_compatibility
13024e6ec9eeSMike Bayer
13034e6ec9eeSMike Bayer    @property
13044e6ec9eeSMike Bayer    def psycopg2_compatibility(self):
13051e1a38e7SMike Bayer        return only_on(["postgresql+psycopg2", "postgresql+psycopg2cffi"])
13064e6ec9eeSMike Bayer
13074e6ec9eeSMike Bayer    @property
13084e6ec9eeSMike Bayer    def psycopg2_or_pg8000_compatibility(self):
13094e6ec9eeSMike Bayer        return only_on(
13101e1a38e7SMike Bayer            [
13111e1a38e7SMike Bayer                "postgresql+psycopg2",
13121e1a38e7SMike Bayer                "postgresql+psycopg2cffi",
13131e1a38e7SMike Bayer                "postgresql+pg8000",
13141e1a38e7SMike Bayer            ]
13154e6ec9eeSMike Bayer        )
13164e6ec9eeSMike Bayer
13178433a48fSMike Bayer    @property
13188433a48fSMike Bayer    def percent_schema_names(self):
13195f333762SMike Bayer        return skip_if(
13205f333762SMike Bayer            ["mysql+aiomysql", "mariadb+aiomysql"],
13215f333762SMike Bayer            "see pr https://github.com/aio-libs/aiomysql/pull/545",
13225f333762SMike Bayer        )
13238433a48fSMike Bayer
13248433a48fSMike Bayer    @property
13258433a48fSMike Bayer    def order_by_label_with_expression(self):
13261e1a38e7SMike Bayer        return fails_if(
13271e1a38e7SMike Bayer            [
13281e1a38e7SMike Bayer                ("postgresql", None, None, "only simple labels allowed"),
13291e1a38e7SMike Bayer                ("mssql", None, None, "only simple labels allowed"),
13301e1a38e7SMike Bayer            ]
13311e1a38e7SMike Bayer        )
13321a777863SMike Bayer
13337402987fSMike Bayer    def get_order_by_collation(self, config):
13347402987fSMike Bayer        lookup = {
13357402987fSMike Bayer            # will raise without quoting
13367402987fSMike Bayer            "postgresql": "POSIX",
1337c0e6ebd7SMike Bayer            # note MySQL databases need to be created w/ utf8mb4 charset
1338c99345eeSMike Bayer            # for the test suite
1339c0e6ebd7SMike Bayer            "mysql": "utf8mb4_bin",
1340fc97854fSMike Bayer            "mariadb": "utf8mb4_bin",
13417402987fSMike Bayer            "sqlite": "NOCASE",
13427402987fSMike Bayer            # will raise *with* quoting
13431e1a38e7SMike Bayer            "mssql": "Latin1_General_CI_AS",
13447402987fSMike Bayer        }
13457402987fSMike Bayer        try:
13467402987fSMike Bayer            return lookup[config.db.name]
13477402987fSMike Bayer        except KeyError:
13487402987fSMike Bayer            raise NotImplementedError()
13497402987fSMike Bayer
13501a777863SMike Bayer    @property
13511a777863SMike Bayer    def skip_mysql_on_windows(self):
13521a777863SMike Bayer        """Catchall for a large variety of MySQL on Windows failures"""
13531a777863SMike Bayer
13541e1a38e7SMike Bayer        return skip_if(
13551e1a38e7SMike Bayer            self._has_mysql_on_windows, "Not supported on MySQL + Windows"
13561e1a38e7SMike Bayer        )
13571a777863SMike Bayer
1358c8817e60SMike Bayer    @property
1359c8817e60SMike Bayer    def mssql_freetds(self):
13602efd89d0SMike Bayer        return only_on(["mssql+pymssql"])
136138c5e870SMike Bayer
13622f617f56SMike Bayer    @property
13632f617f56SMike Bayer    def legacy_engine(self):
13642f617f56SMike Bayer        return exclusions.skip_if(lambda config: config.db._is_future)
13652f617f56SMike Bayer
13664a886e51SMike Bayer    @property
13674a886e51SMike Bayer    def ad_hoc_engines(self):
136837414a75SMike Bayer        return skip_if(self._sqlite_file_db)
1369cf64e257SMike Bayer
1370346e2bc0SMike Bayer    @property
1371346e2bc0SMike Bayer    def no_asyncio(self):
1372346e2bc0SMike Bayer        def go(config):
1373346e2bc0SMike Bayer            return config.db.dialect.is_async
1374346e2bc0SMike Bayer
1375346e2bc0SMike Bayer        return skip_if(go)
1376346e2bc0SMike Bayer
13770198d9aaSMike Bayer    @property
13780198d9aaSMike Bayer    def no_mssql_freetds(self):
13790198d9aaSMike Bayer        return self.mssql_freetds.not_()
13800198d9aaSMike Bayer
1381aaa60cd1SMike Bayer    @property
1382aaa60cd1SMike Bayer    def pyodbc_fast_executemany(self):
1383aaa60cd1SMike Bayer        def has_fastexecutemany(config):
1384aaa60cd1SMike Bayer            if not against(config, "mssql+pyodbc"):
1385aaa60cd1SMike Bayer                return False
1386cfe0bfd9SMike Bayer            if config.db.dialect._dbapi_version() < (4, 0, 19):
1387cfe0bfd9SMike Bayer                return False
1388aaa60cd1SMike Bayer            with config.db.connect() as conn:
1389aaa60cd1SMike Bayer                drivername = conn.connection.connection.getinfo(
13901e1a38e7SMike Bayer                    config.db.dialect.dbapi.SQL_DRIVER_NAME
13911e1a38e7SMike Bayer                )
139257fa3c06SGord Thompson                # on linux this is something like 'libmsodbcsql-13.1.so.9.2'.
139357fa3c06SGord Thompson                # on Windows this is something like 'msodbcsql17.dll'.
1394aaa60cd1SMike Bayer                return "msodbc" in drivername
13951e1a38e7SMike Bayer
1396cfe0bfd9SMike Bayer        return only_if(
13971e1a38e7SMike Bayer            has_fastexecutemany, "only on pyodbc > 4.0.19 w/ msodbc driver"
13981e1a38e7SMike Bayer        )
1399aaa60cd1SMike Bayer
14003d60b415SMike Bayer    @property
14013d60b415SMike Bayer    def python_fixed_issue_8743(self):
14023d60b415SMike Bayer        return exclusions.skip_if(
14033d60b415SMike Bayer            lambda: sys.version_info < (2, 7, 8),
14041e1a38e7SMike Bayer            "Python issue 8743 fixed in Python 2.7.8",
14053d60b415SMike Bayer        )
14063d60b415SMike Bayer
1407380f4389SMike Bayer    @property
1408380f4389SMike Bayer    def granular_timezone(self):
1409380f4389SMike Bayer        """the datetime.timezone class, or SQLAlchemy's port, supports
1410380f4389SMike Bayer        seconds and microseconds.
1411380f4389SMike Bayer
1412380f4389SMike Bayer        SQLAlchemy ported the Python 3.7 version for Python 2, so
1413380f4389SMike Bayer        it passes on that.  For Python 3.6 and earlier, it is not supported.
1414380f4389SMike Bayer
1415380f4389SMike Bayer        """
1416380f4389SMike Bayer        return exclusions.skip_if(
1417380f4389SMike Bayer            lambda: sys.version_info >= (3,) and sys.version_info < (3, 7)
1418380f4389SMike Bayer        )
1419380f4389SMike Bayer
14201a777863SMike Bayer    @property
14211a777863SMike Bayer    def selectone(self):
14221a777863SMike Bayer        """target driver must support the literal statement 'select 1'"""
1423ed78e679SFederico Caselli        return skip_if(["oracle"], "non-standard SELECT scalar syntax")
14241a777863SMike Bayer
142583750628SMike Bayer    @property
142683750628SMike Bayer    def mysql_for_update(self):
142783750628SMike Bayer        return skip_if(
142883750628SMike Bayer            "mysql+mysqlconnector",
14291e1a38e7SMike Bayer            "lock-sensitive operations crash on mysqlconnector",
143083750628SMike Bayer        )
143183750628SMike Bayer
1432f4a1129eSMike Bayer    @property
1433f4a1129eSMike Bayer    def mysql_fsp(self):
1434fc97854fSMike Bayer        return only_if(["mysql >= 5.6.4", "mariadb"])
1435f4a1129eSMike Bayer
1436ea05a232SMike Bayer    @property
1437ea05a232SMike Bayer    def mysql_fully_case_sensitive(self):
1438ea05a232SMike Bayer        return only_if(self._has_mysql_fully_case_sensitive)
1439ea05a232SMike Bayer
144002190234SMike Bayer    @property
144102190234SMike Bayer    def mysql_zero_date(self):
144202190234SMike Bayer        def check(config):
14431e1a38e7SMike Bayer            if not against(config, "mysql"):
1444ed197d4cSMike Bayer                return False
1445ed197d4cSMike Bayer
1446aa026c30SMike Bayer            with config.db.connect() as conn:
1447aa026c30SMike Bayer                row = conn.exec_driver_sql(
1448aa026c30SMike Bayer                    "show variables like 'sql_mode'"
1449aa026c30SMike Bayer                ).first()
145077237473SKhairi Hafsham            return not row or "NO_ZERO_DATE" not in row[1]
145102190234SMike Bayer
145202190234SMike Bayer        return only_if(check)
145302190234SMike Bayer
145402190234SMike Bayer    @property
145502190234SMike Bayer    def mysql_non_strict(self):
145602190234SMike Bayer        def check(config):
14571e1a38e7SMike Bayer            if not against(config, "mysql"):
1458ed197d4cSMike Bayer                return False
1459ed197d4cSMike Bayer
1460aa026c30SMike Bayer            with config.db.connect() as conn:
1461aa026c30SMike Bayer                row = conn.exec_driver_sql(
1462aa026c30SMike Bayer                    "show variables like 'sql_mode'"
1463aa026c30SMike Bayer                ).first()
146454cdda03SMike Bayer            return not row or "STRICT_TRANS_TABLES" not in row[1]
146502190234SMike Bayer
146602190234SMike Bayer        return only_if(check)
146702190234SMike Bayer
14686c2a1e17SMike Bayer    @property
14696c2a1e17SMike Bayer    def mysql_ngram_fulltext(self):
14706c2a1e17SMike Bayer        def check(config):
14711e1a38e7SMike Bayer            return (
14721e1a38e7SMike Bayer                against(config, "mysql")
14731e1a38e7SMike Bayer                and not config.db.dialect._is_mariadb
14741e1a38e7SMike Bayer                and config.db.dialect.server_version_info >= (5, 7)
14751e1a38e7SMike Bayer            )
14761e1a38e7SMike Bayer
14776c2a1e17SMike Bayer        return only_if(check)
14786c2a1e17SMike Bayer
14795cf8e14dSMike Bayer    def _mysql_80(self, config):
14805cf8e14dSMike Bayer        return (
14815cf8e14dSMike Bayer            against(config, "mysql")
14825cf8e14dSMike Bayer            and config.db.dialect._is_mysql
14835cf8e14dSMike Bayer            and config.db.dialect.server_version_info >= (8,)
14845cf8e14dSMike Bayer        )
14855cf8e14dSMike Bayer
148629b752f8SMike Bayer    def _mariadb_102(self, config):
14871e1a38e7SMike Bayer        return (
14882db1eccbSMike Bayer            against(config, ["mysql", "mariadb"])
14892db1eccbSMike Bayer            and config.db.dialect._is_mariadb
14902db1eccbSMike Bayer            and config.db.dialect._mariadb_normalized_version_info >= (10, 2)
14912db1eccbSMike Bayer        )
14922db1eccbSMike Bayer
14932db1eccbSMike Bayer    def _mariadb_105(self, config):
14942db1eccbSMike Bayer        return (
14952db1eccbSMike Bayer            against(config, ["mysql", "mariadb"])
14961e1a38e7SMike Bayer            and config.db.dialect._is_mariadb
14972db1eccbSMike Bayer            and config.db.dialect._mariadb_normalized_version_info >= (10, 5)
14981e1a38e7SMike Bayer        )
149929b752f8SMike Bayer
15001c3e9262SMike Bayer    def _mysql_and_check_constraints_exist(self, config):
15011c3e9262SMike Bayer        # 1. we have mysql / mariadb and
15021c3e9262SMike Bayer        # 2. it enforces check constraints
1503fc97854fSMike Bayer        if exclusions.against(config, ["mysql", "mariadb"]):
15041c3e9262SMike Bayer            if config.db.dialect._is_mariadb:
15051c3e9262SMike Bayer                norm_version_info = (
15061c3e9262SMike Bayer                    config.db.dialect._mariadb_normalized_version_info
15071c3e9262SMike Bayer                )
15081c3e9262SMike Bayer                return norm_version_info >= (10, 2)
15091c3e9262SMike Bayer            else:
15101c3e9262SMike Bayer                norm_version_info = config.db.dialect.server_version_info
15111c3e9262SMike Bayer                return norm_version_info >= (8, 0, 16)
15121c3e9262SMike Bayer        else:
15131c3e9262SMike Bayer            return False
15141c3e9262SMike Bayer
15151c3e9262SMike Bayer    def _mysql_check_constraints_exist(self, config):
15161c3e9262SMike Bayer        # 1. we dont have mysql / mariadb or
15171c3e9262SMike Bayer        # 2. we have mysql / mariadb that enforces check constraints
15181c3e9262SMike Bayer        return not exclusions.against(
1519fc97854fSMike Bayer            config, ["mysql", "mariadb"]
15201c3e9262SMike Bayer        ) or self._mysql_and_check_constraints_exist(config)
15211c3e9262SMike Bayer
15221c3e9262SMike Bayer    def _mysql_check_constraints_dont_exist(self, config):
15231c3e9262SMike Bayer        # 1. we have mysql / mariadb and
15241c3e9262SMike Bayer        # 2. they dont enforce check constraints
15251c3e9262SMike Bayer        return not self._mysql_check_constraints_exist(config)
15261c3e9262SMike Bayer
152729b752f8SMike Bayer    def _mysql_not_mariadb_102(self, config):
1528fc97854fSMike Bayer        return (against(config, ["mysql", "mariadb"])) and (
15291e1a38e7SMike Bayer            not config.db.dialect._is_mariadb
15301e1a38e7SMike Bayer            or config.db.dialect._mariadb_normalized_version_info < (10, 2)
153129b752f8SMike Bayer        )
153229b752f8SMike Bayer
1533081d4275SMike Bayer    def _mysql_not_mariadb_103(self, config):
1534fc97854fSMike Bayer        return (against(config, ["mysql", "mariadb"])) and (
15351e1a38e7SMike Bayer            not config.db.dialect._is_mariadb
15361e1a38e7SMike Bayer            or config.db.dialect._mariadb_normalized_version_info < (10, 3)
1537081d4275SMike Bayer        )
1538081d4275SMike Bayer
15392f27dd35SMike Bayer    def _mysql_not_mariadb_104(self, config):
1540fc97854fSMike Bayer        return (against(config, ["mysql", "mariadb"])) and (
15412f27dd35SMike Bayer            not config.db.dialect._is_mariadb
15422f27dd35SMike Bayer            or config.db.dialect._mariadb_normalized_version_info < (10, 4)
15432f27dd35SMike Bayer        )
15442f27dd35SMike Bayer
1545ea05a232SMike Bayer    def _has_mysql_on_windows(self, config):
1546b920869eSGord Thompson        with config.db.connect() as conn:
1547b920869eSGord Thompson            return (
1548b920869eSGord Thompson                against(config, ["mysql", "mariadb"])
1549b920869eSGord Thompson            ) and config.db.dialect._detect_casing(conn) == 1
15501a777863SMike Bayer
1551ea05a232SMike Bayer    def _has_mysql_fully_case_sensitive(self, config):
1552b920869eSGord Thompson        with config.db.connect() as conn:
1553b920869eSGord Thompson            return (
1554b920869eSGord Thompson                against(config, "mysql")
1555b920869eSGord Thompson                and config.db.dialect._detect_casing(conn) == 0
1556b920869eSGord Thompson            )
15579e612f11SMike Bayer
1558bf70f556SMike Bayer    @property
1559bf70f556SMike Bayer    def postgresql_utf8_server_encoding(self):
1560f1e96cb0SMike Bayer        def go(config):
1561f1e96cb0SMike Bayer            if not against(config, "postgresql"):
1562f1e96cb0SMike Bayer                return False
15639ec75882SFederico Caselli
1564f1e96cb0SMike Bayer            with config.db.connect() as conn:
1565f1e96cb0SMike Bayer                enc = conn.exec_driver_sql("show server_encoding").scalar()
1566f1e96cb0SMike Bayer                return enc.lower() == "utf8"
1567f1e96cb0SMike Bayer
1568f1e96cb0SMike Bayer        return only_if(go)
156931f80b9eSMike Bayer
1570e0e6fe44SMike Bayer    @property
1571e0e6fe44SMike Bayer    def cxoracle6_or_greater(self):
1572e0e6fe44SMike Bayer        return only_if(
15731e1a38e7SMike Bayer            lambda config: against(config, "oracle+cx_oracle")
15741e1a38e7SMike Bayer            and config.db.dialect.cx_oracle_ver >= (6,)
1575e0e6fe44SMike Bayer        )
1576e0e6fe44SMike Bayer
157731f80b9eSMike Bayer    @property
157831f80b9eSMike Bayer    def oracle5x(self):
157931f80b9eSMike Bayer        return only_if(
15801e1a38e7SMike Bayer            lambda config: against(config, "oracle+cx_oracle")
15811e1a38e7SMike Bayer            and config.db.dialect.cx_oracle_ver < (6,)
158280a2241eSMike Bayer        )
15833a0e0531SCaselIT
15843a0e0531SCaselIT    @property
15853a0e0531SCaselIT    def computed_columns(self):
158687949debSFederico Caselli        return skip_if(["postgresql < 12", "sqlite < 3.31", "mysql < 5.7"])
158738601259SMike Bayer
158838601259SMike Bayer    @property
158938601259SMike Bayer    def python_profiling_backend(self):
159079a53645SGord Thompson        return only_on([self._sqlite_memory_db])
159162b7daceSFederico Caselli
159262b7daceSFederico Caselli    @property
159362b7daceSFederico Caselli    def computed_columns_stored(self):
1594ed78e679SFederico Caselli        return self.computed_columns + skip_if(["oracle"])
159562b7daceSFederico Caselli
159662b7daceSFederico Caselli    @property
159762b7daceSFederico Caselli    def computed_columns_virtual(self):
1598ed78e679SFederico Caselli        return self.computed_columns + skip_if(["postgresql"])
159962b7daceSFederico Caselli
160062b7daceSFederico Caselli    @property
160162b7daceSFederico Caselli    def computed_columns_default_persisted(self):
160262b7daceSFederico Caselli        return self.computed_columns + only_if("postgresql")
160362b7daceSFederico Caselli
160462b7daceSFederico Caselli    @property
160562b7daceSFederico Caselli    def computed_columns_reflect_persisted(self):
160662b7daceSFederico Caselli        return self.computed_columns + skip_if("oracle")
160707d6d211SFederico Caselli
1608b1b97ed1SFederico Caselli    @property
1609b1b97ed1SFederico Caselli    def regexp_match(self):
1610b1b97ed1SFederico Caselli        return only_on(["postgresql", "mysql", "mariadb", "oracle", "sqlite"])
1611b1b97ed1SFederico Caselli
1612b1b97ed1SFederico Caselli    @property
1613b1b97ed1SFederico Caselli    def regexp_replace(self):
1614b1b97ed1SFederico Caselli        return only_on(["postgresql", "mysql>=8", "mariadb", "oracle"])
1615b1b97ed1SFederico Caselli
161607d6d211SFederico Caselli    @property
161707d6d211SFederico Caselli    def supports_distinct_on(self):
161807d6d211SFederico Caselli        """If a backend supports the DISTINCT ON in a select"""
161907d6d211SFederico Caselli        return only_if(["postgresql"])
1620103260ddSRobotScribe
1621103260ddSRobotScribe    @property
1622103260ddSRobotScribe    def supports_for_update_of(self):
1623103260ddSRobotScribe        return only_if(lambda config: config.db.dialect.supports_for_update_of)
1624668872feSGord Thompson
1625668872feSGord Thompson    @property
1626668872feSGord Thompson    def sequences_in_other_clauses(self):
1627668872feSGord Thompson        """sequences allowed in WHERE, GROUP BY, HAVING, etc."""
1628668872feSGord Thompson        return skip_if(["mssql", "oracle"])
1629668872feSGord Thompson
1630668872feSGord Thompson    @property
1631668872feSGord Thompson    def supports_lastrowid_for_expressions(self):
16329ab4da70SFederico Caselli        """cursor.lastrowid works if an explicit SQL expression was used."""
16339ab4da70SFederico Caselli        return only_on(["sqlite", "mysql", "mariadb"])
1634668872feSGord Thompson
1635668872feSGord Thompson    @property
1636668872feSGord Thompson    def supports_sequence_for_autoincrement_column(self):
1637668872feSGord Thompson        """for mssql, autoincrement means IDENTITY, not sequence"""
1638668872feSGord Thompson        return skip_if("mssql")
163926e8d3b5SFederico Caselli
164026e8d3b5SFederico Caselli    @property
164126e8d3b5SFederico Caselli    def identity_columns(self):
164226e8d3b5SFederico Caselli        return only_if(["postgresql >= 10", "oracle >= 12", "mssql"])
164326e8d3b5SFederico Caselli
164426e8d3b5SFederico Caselli    @property
164526e8d3b5SFederico Caselli    def identity_columns_standard(self):
164626e8d3b5SFederico Caselli        return self.identity_columns + skip_if("mssql")
16471a08d1aaSGord Thompson
16481a08d1aaSGord Thompson    @property
16491a08d1aaSGord Thompson    def index_reflects_included_columns(self):
16501a08d1aaSGord Thompson        return only_on(["postgresql >= 11", "mssql"])
165134e6b732SFederico Caselli
165234e6b732SFederico Caselli    # mssql>= 11 -> >= MS_2012_VERSION
165334e6b732SFederico Caselli
165434e6b732SFederico Caselli    @property
165534e6b732SFederico Caselli    def fetch_first(self):
16566d581161SFederico Caselli        return only_on(
16576d581161SFederico Caselli            ["postgresql", "mssql >= 11", "oracle >= 12", "mariadb >= 10.6"]
16586d581161SFederico Caselli        )
165934e6b732SFederico Caselli
166034e6b732SFederico Caselli    @property
166134e6b732SFederico Caselli    def fetch_percent(self):
166234e6b732SFederico Caselli        return only_on(["mssql >= 11", "oracle >= 12"])
166334e6b732SFederico Caselli
166434e6b732SFederico Caselli    @property
166534e6b732SFederico Caselli    def fetch_ties(self):
16666d581161SFederico Caselli        return only_on(
16676d581161SFederico Caselli            [
16686d581161SFederico Caselli                "postgresql >= 13",
16696d581161SFederico Caselli                "mssql >= 11",
16706d581161SFederico Caselli                "oracle >= 12",
16716d581161SFederico Caselli                "mariadb >= 10.6",
16726d581161SFederico Caselli            ]
16736d581161SFederico Caselli        )
167434e6b732SFederico Caselli
167534e6b732SFederico Caselli    @property
167634e6b732SFederico Caselli    def fetch_no_order_by(self):
16776d581161SFederico Caselli        return only_on(["postgresql", "oracle >= 12", "mariadb >= 10.6"])
167834e6b732SFederico Caselli
167934e6b732SFederico Caselli    @property
168034e6b732SFederico Caselli    def fetch_offset_with_options(self):
16816d581161SFederico Caselli        # use together with fetch_first
168234e6b732SFederico Caselli        return skip_if("mssql")
1683d3c73ad8SFederico Caselli
16846d581161SFederico Caselli    @property
16856d581161SFederico Caselli    def fetch_expression(self):
16866d581161SFederico Caselli        # use together with fetch_first
16876d581161SFederico Caselli        return skip_if("mariadb")
16886d581161SFederico Caselli
1689d3c73ad8SFederico Caselli    @property
1690d3c73ad8SFederico Caselli    def autoincrement_without_sequence(self):
1691d3c73ad8SFederico Caselli        return skip_if("oracle")
16927f25fcadSSumit Khanna
16937f25fcadSSumit Khanna    @property
16947f25fcadSSumit Khanna    def reflect_tables_no_columns(self):
16957f25fcadSSumit Khanna        # so far sqlite, mariadb, mysql don't support this
16967f25fcadSSumit Khanna        return only_on(["postgresql"])
1697