]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
liberalize pep695 matching
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 4 Sep 2025 01:44:33 +0000 (21:44 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 6 Sep 2025 16:13:49 +0000 (12:13 -0400)
after many days of discussion we are moving to liberalize the
matching rules used for pep695 to the simple rule that we will resolve
a pep695 type to its immediate ``__value__`` without requiring that
it be present in the type map, however without any further recursive
checks (that is, we will not resolve ``__value__`` of ``__value__``).
This allows the vast majority of simple uses of pep695 types to not
require entries in the type map, including when the type points
to a simple Python type or any type that is present in the type_map.
Also supported is resolution of generic pep695 types against the
right side, including for Annotated types.

The change here in 2.1 will form the base for a revised approach
to the RegistryEvents patch for #9832, which will still provide
the RegistryEvents.resolve_type_annotation hook.   In 2.0, we need
to scale back the warnings that are emitted so portions of this patch
will also be backported including similar changes to the test suite.

Fixes: #12829
Change-Id: Ib6e379793335da3f33f6ca2cd6874a6eaf1e36f4
(cherry picked from commit f44a361d18c57167622c34c04c6e2e1b67b1c9f2)

doc/build/changelog/changelog_20.rst
doc/build/changelog/unreleased_20/12829.rst [new file with mode: 0644]
doc/build/orm/declarative_tables.rst
lib/sqlalchemy/orm/decl_api.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/util/typing.py
test/orm/declarative/test_tm_future_annotations_sync.py
test/orm/declarative/test_typed_mapping.py

index c3ec1ed152bc88ca219455cc5d6f1e17a6006e27..aa01eeabc9adbad69bf3e79775fc184364587eb4 100644 (file)
         :tags: bug, orm
         :tickets: 11955
 
+        .. note:: this change has been revised in version 2.0.44.  Simple matches
+           of ``TypeAliasType`` without a type map entry are no longer deprecated.
+
         Consistently handle ``TypeAliasType`` (defined in PEP 695) obtained with
         the ``type X = int`` syntax introduced in python 3.12. Now in all cases one
         such alias must be explicitly added to the type map for it to be usable
diff --git a/doc/build/changelog/unreleased_20/12829.rst b/doc/build/changelog/unreleased_20/12829.rst
new file mode 100644 (file)
index 0000000..f307545
--- /dev/null
@@ -0,0 +1,27 @@
+.. change::
+    :tags: usecase, orm
+    :tickets: 12829
+
+    The way ORM Annotated Declarative interprets Python :pep:`695` type aliases
+    in ``Mapped[]`` annotations has been refined to expand the lookup scheme. A
+    PEP 695 type can now be resolved based on either its direct presence in
+    :paramref:`_orm.registry.type_annotation_map` or its immediate resolved
+    value, as long as a recursive lookup across multiple pep-695 types is not
+    required for it to resolve. This change reverses part of the restrictions
+    introduced in 2.0.37 as part of :ticket:`11955`, which deprecated (and
+    disallowed in 2.1) the ability to resolve any PEP 695 type that was not
+    explicitly present in :paramref:`_orm.registry.type_annotation_map`.
+    Recursive lookups of PEP 695 types remains deprecated in 2.0 and disallowed
+    in version 2.1, as do implicit lookups of ``NewType`` types without an
+    entry in :paramref:`_orm.registry.type_annotation_map`.
+
+    Additionally, new support has been added for generic PEP 695 aliases that
+    refer to PEP 593 ``Annotated`` constructs containing
+    :func:`_orm.mapped_column` configurations. See the sections below for
+    examples.
+
+    .. seealso::
+
+        :ref:`orm_declarative_type_map_pep695_types`
+
+        :ref:`orm_declarative_mapped_column_generic_pep593`
index e549dc7162d5909cda0e0b4753e364fd1c42f9c1..5761d8ab29c08055669b5060f1a42dd4eb82d8ae 100644 (file)
@@ -814,7 +814,6 @@ is described in the next section, :ref:`orm_declarative_type_map_pep695_types`.
 Support for Type Alias Types (defined by PEP 695) and NewType
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-
 In contrast to the typing lookup described in
 :ref:`orm_declarative_type_map_union_types`, Python typing also includes two
 ways to create a composed type in a more formal way, using ``typing.NewType`` as
@@ -823,13 +822,20 @@ differently from ordinary type aliases (i.e. assigning a type to a variable
 name), and this difference is honored in how SQLAlchemy resolves these
 types from the type map.
 
-.. versionchanged:: 2.0.37  The behaviors described in this section for ``typing.NewType``
-   as well as :pep:`695` ``type`` have been formalized and corrected.
-   Deprecation warnings are now emitted for "loose matching" patterns that have
-   worked in some 2.0 releases, but are to be removed in SQLAlchemy 2.1.
+.. versionchanged:: 2.0.44  Support for resolving pep-695 types without a
+   corresponding entry in :paramref:`_orm.registry.type_annotation_map`
+   has been expanded, reversing part of the restrictions introduced in 2.0.37.
    Please ensure SQLAlchemy is up to date before attempting to use the features
    described in this section.
 
+.. versionchanged:: 2.0.37  The behaviors described in this section for ``typing.NewType``
+   as well as :pep:`695` ``type`` were formalized to disallow these types
+   from being implicitly resolvable without entries in
+   :paramref:`_orm.registry.type_annotation_map`, with deprecation warnings
+   emitted when these patterns were detected. As of 2.0.44, a pep-695 type
+   is implicitly resolvable as long as the type it resolves to is present
+   in the type map.
+
 The typing module allows the creation of "new types" using ``typing.NewType``::
 
     from typing import NewType
@@ -837,110 +843,115 @@ The typing module allows the creation of "new types" using ``typing.NewType``::
     nstr30 = NewType("nstr30", str)
     nstr50 = NewType("nstr50", str)
 
-Additionally, in Python 3.12, a new feature defined by :pep:`695` was introduced which
-provides the ``type`` keyword to accomplish a similar task; using
-``type`` produces an object that is similar in many ways to ``typing.NewType``
-which is internally referred to as ``typing.TypeAliasType``::
+The ``NewType`` construct creates types that are analogous to creating a
+subclass of the referenced type.
+
+Additionally, :pep:`695` introduced in Python 3.12 provides a new ``type``
+keyword for creating type aliases with greater separation of concerns from plain
+aliases, as well as succinct support for generics without requiring explicit
+use of ``TypeVar`` or ``Generic`` elements. Types created by the ``type``
+keyword are represented at runtime by ``typing.TypeAliasType``::
 
     type SmallInt = int
     type BigInt = int
     type JsonScalar = str | float | bool | None
 
-For the purposes of how SQLAlchemy treats these type objects when used
-for SQL type lookup inside of :class:`_orm.Mapped`, it's important to note
-that Python does not consider two equivalent ``typing.TypeAliasType``
-or ``typing.NewType`` objects to be equal::
-
-    # two typing.NewType objects are not equal even if they are both str
-    >>> nstr50 == nstr30
-    False
-
-    # two TypeAliasType objects are not equal even if they are both int
-    >>> SmallInt == BigInt
-    False
-
-    # an equivalent union is not equal to JsonScalar
-    >>> JsonScalar == str | float | bool | None
-    False
-
-This is the opposite behavior from how ordinary unions are compared, and
-informs the correct behavior for SQLAlchemy's ``type_annotation_map``. When
-using ``typing.NewType`` or :pep:`695` ``type`` objects, the type object is
-expected to be explicit within the ``type_annotation_map`` for it to be matched
-from a :class:`_orm.Mapped` type, where the same object must be stated in order
-for a match to be made (excluding whether or not the type inside of
-:class:`_orm.Mapped` also unions on ``None``). This is distinct from the
-behavior described at :ref:`orm_declarative_type_map_union_types`, where a
-plain ``Union`` that is referenced directly will match to other ``Unions``
-based on the composition, rather than the object identity, of a particular type
-in ``type_annotation_map``.
-
-In the example below, the composed types for ``nstr30``, ``nstr50``,
-``SmallInt``, ``BigInt``, and ``JsonScalar`` have no overlap with each other
-and can be named distinctly within each :class:`_orm.Mapped` construct, and
-are also all explicit in ``type_annotation_map``.   Any of these types may
-also be unioned with ``None`` or declared as ``Optional[]`` without affecting
-the lookup, only deriving column nullability::
+Both ``NewType`` and pep-695 ``type`` constructs may be used as arguments
+within :class:`_orm.Mapped` annotations, where they will be resolved to Python
+types using the following rules:
 
-    from typing import NewType
+* When a ``TypeAliasType`` or ``NewType`` object is present in the
+  :paramref:`_orm.registry.type_annotation_map`, it will resolve directly::
 
-    from sqlalchemy import SmallInteger, BigInteger, JSON, String
-    from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
-    from sqlalchemy.schema import CreateTable
+    from typing import NewType
+    from sqlalchemy import String, BigInteger
 
     nstr30 = NewType("nstr30", str)
-    nstr50 = NewType("nstr50", str)
-    type SmallInt = int
     type BigInt = int
-    type JsonScalar = str | float | bool | None
 
 
-    class TABase(DeclarativeBase):
-        type_annotation_map = {
-            nstr30: String(30),
-            nstr50: String(50),
-            SmallInt: SmallInteger,
-            BigInteger: BigInteger,
-            JsonScalar: JSON,
-        }
+    class Base(DeclarativeBase):
+        type_annotation_map = {nstr30: String(30), BigInt: BigInteger}
 
 
-    class SomeClass(TABase):
+    class SomeClass(Base):
         __tablename__ = "some_table"
 
-        id: Mapped[int] = mapped_column(primary_key=True)
-        normal_str: Mapped[str]
+        # BigInt is in the type_annotation_map.  So this
+        # will resolve to sqlalchemy.BigInteger
+        id: Mapped[BigInt] = mapped_column(primary_key=True)
 
-        short_str: Mapped[nstr30]
-        long_str_nullable: Mapped[nstr50 | None]
+        # nstr30 is in the type_annotation_map.  So this
+        # will resolve to sqlalchemy.String(30)
+        data: Mapped[nstr30]
 
-        small_int: Mapped[SmallInt]
-        big_int: Mapped[BigInteger]
-        scalar_col: Mapped[JsonScalar]
+* A ``TypeAliasType`` that refers **directly** to another type present
+  in the type map will resolve against that type::
 
-a CREATE TABLE for the above mapping will illustrate the different variants
-of integer and string we've configured, and looks like:
+    type PlainInt = int
 
-.. sourcecode:: pycon+sql
 
-    >>> print(CreateTable(SomeClass.__table__))
-    {printsql}CREATE TABLE some_table (
-        id INTEGER NOT NULL,
-        normal_str VARCHAR NOT NULL,
-        short_str VARCHAR(30) NOT NULL,
-        long_str_nullable VARCHAR(50),
-        small_int SMALLINT NOT NULL,
-        big_int BIGINT NOT NULL,
-        scalar_col JSON,
-        PRIMARY KEY (id)
-    )
+    class Base(DeclarativeBase):
+        pass
+
+
+    class SomeClass(Base):
+        __tablename__ = "some_table"
+
+        # PlainInt refers to int, which is one of the default types
+        # already in the type_annotation_map.   So this
+        # will resolve to sqlalchemy.Integer via the int type
+        id: Mapped[PlainInt] = mapped_column(primary_key=True)
+
+* A ``TypeAliasType`` that refers to another pep-695 ``TypeAliasType``
+  not present in the type map will not resolve (emits a deprecation
+  warning in 2.0), as this would involve a recursive lookup::
+
+    type PlainInt = int
+    type AlsoAnInt = PlainInt
+
+
+    class Base(DeclarativeBase):
+        pass
+
+
+    class SomeClass(Base):
+        __tablename__ = "some_table"
+
+        # AlsoAnInt refers to PlainInt, which is not in the type_annotation_map.
+        # This will emit a deprecation warning in 2.0, will fail in 2.1
+        id: Mapped[AlsoAnInt] = mapped_column(primary_key=True)
 
-Regarding nullability, the ``JsonScalar`` type includes ``None`` in its
-definition, which indicates a nullable column.   Similarly the
-``long_str_nullable`` column applies a union of ``None`` to ``nstr50``,
-which matches to the ``nstr50`` type in the ``type_annotation_map`` while
-also applying nullability to the mapped column.  The other columns all remain
-NOT NULL as they are not indicated as optional.
+* A ``NewType`` that is not in the type map will not resolve (emits a
+  deprecation warning in 2.0). Since ``NewType`` is analogous to creating an
+  entirely new type with different semantics than the type it extends, these
+  must be explicitly matched in the type map::
+
+
+    from typing import NewType
+
+    nstr30 = NewType("nstr30", str)
+
+
+    class Base(DeclarativeBase):
+        pass
+
+
+    class SomeClass(Base):
+        __tablename__ = "some_table"
+
+        # a NewType is a new kind of type, so this will emit a deprecation
+        # warning in 2.0 and fail in 2.1, as nstr30 is not present
+        # in the type_annotation_map.
+        id: Mapped[nstr30] = mapped_column(primary_key=True)
+
+For all of the above examples, any type that is combined with ``Optional[]``
+or ``| None`` will consider this to indicate the column is nullable, if
+no other directive for nullability is present.
+
+.. seealso::
+
+    :ref:`orm_declarative_mapped_column_generic_pep593`
 
 
 .. _orm_declarative_mapped_column_type_map_pep593:
@@ -1214,6 +1225,57 @@ adding a ``FOREIGN KEY`` constraint as well as substituting
    will raise a ``NotImplementedError`` exception at runtime, but
    may be implemented in future releases.
 
+
+.. _orm_declarative_mapped_column_generic_pep593:
+
+Mapping Whole Column Declarations to Generic Python Types
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Using the ``Annotated`` approach from the previous section, we may also
+create a generic version that will apply particular :func:`_orm.mapped_column`
+elements across many different Python/SQL types in one step.  Below
+illustrates a plain alias against a generic form of ``Annotated`` that
+will apply the ``primary_key=True`` option to any column to which it's applied::
+
+    from typing import Annotated
+    from typing import TypeVar
+
+    T = TypeVar("T", bound=Any)
+
+    PrimaryKey = Annotated[T, mapped_column(primary_key=True)]
+
+The above type can now apply ``primary_key=True`` to any Python type::
+
+    import uuid
+
+
+    class Base(DeclarativeBase):
+        pass
+
+
+    class A(Base):
+        __tablename__ = "a"
+
+        # will create an Integer primary key
+        id: Mapped[PrimaryKey[int]]
+
+
+    class B(Base):
+        __tablename__ = "b"
+
+        # will create a UUID primary key
+        id: Mapped[PrimaryKey[uuid.UUID]]
+
+The type alias may also be defined equivalently using the pep-695 ``type``
+keyword in Python 3.12 or above::
+
+    type PrimaryKey[T] = Annotated[T, mapped_column(primary_key=True)]
+
+.. versionadded:: 2.0.44 Generic pep-695 types may be used with pep-593
+    ``Annotated`` elements to create generic types that automatically
+    deliver :func:`_orm.mapped_column` arguments.
+
+
 .. _orm_declarative_mapped_column_enums:
 
 Using Python ``Enum`` or pep-586 ``Literal`` types in the type map
index 8d2e90f44157c65aae93223284abc2936f7120e7..908ba04b71ab5744dee048db552bf0ee18164926 100644 (file)
@@ -78,6 +78,7 @@ from ..util.typing import flatten_newtype
 from ..util.typing import is_generic
 from ..util.typing import is_literal
 from ..util.typing import is_newtype
+from ..util.typing import is_pep593
 from ..util.typing import is_pep695
 from ..util.typing import Literal
 from ..util.typing import LITERAL_TYPES
@@ -1236,7 +1237,7 @@ class registry:
         )
 
     def _resolve_type(
-        self, python_type: _MatchedOnType, _do_fallbacks: bool = True
+        self, python_type: _MatchedOnType, _do_fallbacks: bool = False
     ) -> Optional[sqltypes.TypeEngine[Any]]:
         python_type_type: Type[Any]
         search: Iterable[Tuple[_MatchedOnType, Type[Any]]]
@@ -1288,12 +1289,14 @@ class registry:
             if is_pep695(python_type):
                 # NOTE: assume there aren't type alias types of new types.
                 python_type_to_check = python_type
-                while is_pep695(python_type_to_check):
+                while is_pep695(python_type_to_check) and not is_pep593(
+                    python_type_to_check
+                ):
                     python_type_to_check = python_type_to_check.__value__
                 python_type_to_check = de_optionalize_union_types(
                     python_type_to_check
                 )
-                kind = "TypeAliasType"
+                kind = "pep-695 type"
             if is_newtype(python_type):
                 python_type_to_check = flatten_newtype(python_type)
                 kind = "NewType"
@@ -1304,14 +1307,27 @@ class registry:
                 )
                 if res_after_fallback is not None:
                     assert kind is not None
-                    warn_deprecated(
-                        f"Matching the provided {kind} '{python_type}' on "
-                        "its resolved value without matching it in the "
-                        "type_annotation_map is deprecated; add this type to "
-                        "the type_annotation_map to allow it to match "
-                        "explicitly.",
-                        "2.0",
-                    )
+                    if kind == "pep-695 type":
+                        warn_deprecated(
+                            f"Matching to {kind} '{python_type}' in "
+                            "a recursive "
+                            "fashion without the recursed type being present "
+                            "in the type_annotation_map is deprecated; add "
+                            "this type or its recursed value to "
+                            "the type_annotation_map to allow it to match "
+                            "explicitly.",
+                            "2.0",
+                        )
+                    else:
+                        warn_deprecated(
+                            f"Matching the provided {kind} '{python_type}' on "
+                            "its resolved value without matching it in the "
+                            "type_annotation_map is deprecated; add this "
+                            "type to "
+                            "the type_annotation_map to allow it to match "
+                            "explicitly.",
+                            "2.0",
+                        )
                     return res_after_fallback
 
         return None
index 88540be6d9eb417d17f093cd0edddd96e7531e62..1d17f9e6ebb3ced46235d61fa205319b4889fcb0 100644 (file)
@@ -769,11 +769,19 @@ class MappedColumn(
         if not self._has_nullable:
             self.column.nullable = nullable
 
-        our_type = de_optionalize_union_types(argument)
-
         find_mapped_in: Tuple[Any, ...] = ()
         our_type_is_pep593 = False
         raw_pep_593_type = None
+        raw_pep_695_type = None
+
+        our_type: Any = de_optionalize_union_types(argument)
+
+        if is_pep695(our_type):
+            raw_pep_695_type = our_type
+            our_type = de_optionalize_union_types(raw_pep_695_type.__value__)
+            our_args = get_args(raw_pep_695_type)
+            if our_args:
+                our_type = our_type[our_args]
 
         if is_pep593(our_type):
             our_type_is_pep593 = True
@@ -783,9 +791,6 @@ class MappedColumn(
             if nullable:
                 raw_pep_593_type = de_optionalize_union_types(raw_pep_593_type)
             find_mapped_in = pep_593_components[1:]
-        elif is_pep695(argument) and is_pep593(argument.__value__):
-            # do not support nested annotation inside unions ets
-            find_mapped_in = get_args(argument.__value__)[1:]
 
         use_args_from: Optional[MappedColumn[Any]]
         for elem in find_mapped_in:
@@ -876,8 +881,13 @@ class MappedColumn(
             else:
                 checks = [our_type]
 
+            if raw_pep_695_type is not None:
+                checks.insert(0, raw_pep_695_type)
+
             for check_type in checks:
-                new_sqltype = registry._resolve_type(check_type)
+                new_sqltype = registry._resolve_type(
+                    check_type, _do_fallbacks=check_type is our_type
+                )
                 if new_sqltype is not None:
                     break
             else:
@@ -890,17 +900,35 @@ class MappedColumn(
                         "attribute Mapped annotation is the SQLAlchemy type "
                         f"{our_type}. Expected a Python type instead"
                     )
-                elif is_a_type(our_type):
+                elif is_a_type(checks[0]):
+                    if len(checks) == 1:
+                        detail = (
+                            "the type object is not resolvable by the registry"
+                        )
+                    elif len(checks) == 2:
+                        detail = (
+                            f"neither '{checks[0]}' nor '{checks[1]}' "
+                            "are resolvable by the registry"
+                        )
+                    else:
+                        detail = (
+                            f"""none of {
+                                ", ".join(f"'{t}'" for t in checks)
+                            } """
+                            "are resolvable by the registry"
+                        )
                     raise orm_exc.MappedAnnotationError(
-                        "Could not locate SQLAlchemy Core type for Python "
-                        f"type {our_type} inside the {self.column.key!r} "
-                        "attribute Mapped annotation"
+                        "Could not locate SQLAlchemy Core type when resolving "
+                        f"for Python type indicated by '{checks[0]}' inside "
+                        "the "
+                        f"Mapped[] annotation for the {self.column.key!r} "
+                        f"attribute; {detail}"
                     )
                 else:
                     raise orm_exc.MappedAnnotationError(
                         f"The object provided inside the {self.column.key!r} "
                         "attribute Mapped annotation is not a Python type, "
-                        f"it's the object {our_type!r}. Expected a Python "
+                        f"it's the object {argument!r}. Expected a Python "
                         "type."
                     )
 
index 794dd18591c8ac9a293202a05f8a3975f6412ca8..1157be9dd5b75cff1f7b1d826ebd6a95575ab8db 100644 (file)
@@ -585,8 +585,9 @@ def includes_none(type_: Any) -> bool:
 def is_a_type(type_: Any) -> bool:
     return (
         isinstance(type_, type)
-        or hasattr(type_, "__origin__")
-        or type_.__module__ in ("typing", "typing_extensions")
+        or get_origin(type_) is not None
+        or getattr(type_, "__module__", None)
+        in ("typing", "typing_extensions")
         or type(type_).__mro__[0].__module__ in ("typing", "typing_extensions")
     )
 
index d789c90b0408c69d27e98931e57f61cdbc92edf2..e4b4435e9e4090e63125b2d28b53bc762c5f1585 100644 (file)
@@ -168,6 +168,19 @@ _TypingLiteral695 = TypingTypeAliasType(
 )
 _RecursiveLiteral695 = TypeAliasType("_RecursiveLiteral695", _Literal695)
 
+_GenericPep593TypeAlias = Annotated[TV, mapped_column(info={"hi": "there"})]
+
+_GenericPep593Pep695 = TypingTypeAliasType(
+    "_GenericPep593Pep695",
+    Annotated[TV, mapped_column(info={"hi": "there"})],
+    type_params=(TV,),
+)
+
+_RecursivePep695Pep593 = TypingTypeAliasType(
+    "_RecursivePep695Pep593",
+    Annotated[_TypingStrPep695, mapped_column(info={"hi": "there"})],
+)
+
 
 def expect_annotation_syntax_error(name):
     return expect_raises_message(
@@ -332,31 +345,6 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
 
         assert Child.__mapper__.attrs.parent.strategy.use_get
 
-    @testing.combinations(
-        (BIGINT(),),
-        (BIGINT,),
-        (Integer().with_variant(BIGINT, "default")),
-        (Integer().with_variant(BIGINT(), "default")),
-        (BIGINT().with_variant(String(), "some_other_dialect")),
-    )
-    def test_type_map_varieties(self, typ):
-        Base = declarative_base(type_annotation_map={int: typ})
-
-        class MyClass(Base):
-            __tablename__ = "mytable"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
-            x: Mapped[int]
-            y: Mapped[int] = mapped_column()
-            z: Mapped[int] = mapped_column(typ)
-
-        self.assert_compile(
-            CreateTable(MyClass.__table__),
-            "CREATE TABLE mytable (id BIGINT NOT NULL, "
-            "x BIGINT NOT NULL, y BIGINT NOT NULL, z BIGINT NOT NULL, "
-            "PRIMARY KEY (id))",
-        )
-
     def test_required_no_arg(self, decl_base):
         with expect_raises_message(
             sa_exc.ArgumentError,
@@ -612,198 +600,6 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         is_true(User.__table__.c.data.nullable)
         assert isinstance(User.__table__.c.created_at.type, DateTime)
 
-    def test_construct_lhs_type_missing(self, decl_base):
-        global MyClass
-
-        class MyClass:
-            pass
-
-        with expect_raises_message(
-            sa_exc.ArgumentError,
-            "Could not locate SQLAlchemy Core type for Python type "
-            ".*MyClass.* inside the 'data' attribute Mapped annotation",
-        ):
-
-            class User(decl_base):
-                __tablename__ = "users"
-
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped[MyClass] = mapped_column()
-
-    @testing.variation(
-        "argtype",
-        [
-            "type",
-            ("column", testing.requires.python310),
-            ("mapped_column", testing.requires.python310),
-            "column_class",
-            "ref_to_type",
-            ("ref_to_column", testing.requires.python310),
-        ],
-    )
-    def test_construct_lhs_sqlalchemy_type(self, decl_base, argtype):
-        """test for #12329.
-
-        of note here are all the different messages we have for when the
-        wrong thing is put into Mapped[], and in fact in #12329 we added
-        another one.
-
-        This is a lot of different messages, but at the same time they
-        occur at different places in the interpretation of types.   If
-        we were to centralize all these messages, we'd still likely end up
-        doing distinct messages for each scenario, so instead we added
-        a new ArgumentError subclass MappedAnnotationError that provides
-        some commonality to all of these cases.
-
-
-        """
-        expect_future_annotations = "annotations" in globals()
-
-        if argtype.type:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # properties.py -> _init_column_for_annotation, type is
-                # a SQL type
-                "The type provided inside the 'data' attribute Mapped "
-                "annotation is the SQLAlchemy type .*BigInteger.*. Expected "
-                "a Python type instead",
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[BigInteger] = mapped_column()
-
-        elif argtype.column:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # util.py -> _extract_mapped_subtype
-                (
-                    re.escape(
-                        "Could not interpret annotation "
-                        "Mapped[Column('q', BigInteger)]."
-                    )
-                    if expect_future_annotations
-                    # properties.py -> _init_column_for_annotation, object is
-                    # not a SQL type or a python type, it's just some object
-                    else re.escape(
-                        "The object provided inside the 'data' attribute "
-                        "Mapped annotation is not a Python type, it's the "
-                        "object Column('q', BigInteger(), table=None). "
-                        "Expected a Python type."
-                    )
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[Column("q", BigInteger)] = (  # noqa: F821
-                        mapped_column()
-                    )
-
-        elif argtype.mapped_column:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # properties.py -> _init_column_for_annotation, object is
-                # not a SQL type or a python type, it's just some object
-                # interestingly, this raises at the same point for both
-                # future annotations mode and legacy annotations mode
-                r"The object provided inside the 'data' attribute "
-                "Mapped annotation is not a Python type, it's the object "
-                r"\<sqlalchemy.orm.properties.MappedColumn.*\>. "
-                "Expected a Python type.",
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    big_integer: Mapped[int] = mapped_column()
-                    data: Mapped[big_integer] = mapped_column()
-
-        elif argtype.column_class:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # properties.py -> _init_column_for_annotation, type is not
-                # a SQL type
-                re.escape(
-                    "Could not locate SQLAlchemy Core type for Python type "
-                    "<class 'sqlalchemy.sql.schema.Column'> inside the "
-                    "'data' attribute Mapped annotation"
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[Column] = mapped_column()
-
-        elif argtype.ref_to_type:
-            mytype = BigInteger
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                (
-                    # decl_base.py -> _exract_mappable_attributes
-                    re.escape(
-                        "Could not resolve all types within mapped "
-                        'annotation: "Mapped[mytype]"'
-                    )
-                    if expect_future_annotations
-                    # properties.py -> _init_column_for_annotation, type is
-                    # a SQL type
-                    else re.escape(
-                        "The type provided inside the 'data' attribute Mapped "
-                        "annotation is the SQLAlchemy type "
-                        "<class 'sqlalchemy.sql.sqltypes.BigInteger'>. "
-                        "Expected a Python type instead"
-                    )
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[mytype] = mapped_column()
-
-        elif argtype.ref_to_column:
-            mycol = Column("q", BigInteger)
-
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # decl_base.py -> _exract_mappable_attributes
-                (
-                    re.escape(
-                        "Could not resolve all types within mapped "
-                        'annotation: "Mapped[mycol]"'
-                    )
-                    if expect_future_annotations
-                    else
-                    # properties.py -> _init_column_for_annotation, object is
-                    # not a SQL type or a python type, it's just some object
-                    re.escape(
-                        "The object provided inside the 'data' attribute "
-                        "Mapped "
-                        "annotation is not a Python type, it's the object "
-                        "Column('q', BigInteger(), table=None). "
-                        "Expected a Python type."
-                    )
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[mycol] = mapped_column()
-
-        else:
-            argtype.fail()
-
     def test_construct_rhs_type_override_lhs(self, decl_base):
         class Element(decl_base):
             __tablename__ = "element"
@@ -975,668 +771,752 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
             id: Mapped["int"] = mapped_column(primary_key=True)
             data_one: Mapped["str"]
 
-    def test_pep593_types_as_typemap_keys(
-        self, decl_base: Type[DeclarativeBase]
-    ):
-        """neat!!!"""
-        global str50, str30, opt_str50, opt_str30
+    @testing.requires.python38
+    def test_typing_literal_identity(self, decl_base):
+        """See issue #11820"""
 
-        str50 = Annotated[str, 50]
-        str30 = Annotated[str, 30]
-        opt_str50 = Optional[str50]
-        opt_str30 = Optional[str30]
+        class Foo(decl_base):
+            __tablename__ = "footable"
 
-        decl_base.registry.update_type_annotation_map(
-            {str50: String(50), str30: String(30)}
-        )
+            id: Mapped[int] = mapped_column(primary_key=True)
+            t: Mapped[_TypingLiteral]
+            te: Mapped[_TypingExtensionsLiteral]
 
-        class MyClass(decl_base):
-            __tablename__ = "my_table"
+        for col in (Foo.__table__.c.t, Foo.__table__.c.te):
+            is_true(isinstance(col.type, Enum))
+            eq_(col.type.enums, ["a", "b"])
+            is_(col.type.native_enum, False)
 
-            id: Mapped[str50] = mapped_column(primary_key=True)
-            data_one: Mapped[str30]
-            data_two: Mapped[opt_str30]
-            data_three: Mapped[str50]
-            data_four: Mapped[opt_str50]
-            data_five: Mapped[str]
-            data_six: Mapped[Optional[str]]
+    @testing.requires.python310
+    def test_we_got_all_attrs_test_annotated(self):
+        argnames = _py_inspect.getfullargspec(mapped_column)
+        assert _annotated_names_tested.issuperset(argnames.kwonlyargs), (
+            f"annotated attributes were not tested: "
+            f"{set(argnames.kwonlyargs).difference(_annotated_names_tested)}"
+        )
 
-        eq_(MyClass.__table__.c.data_one.type.length, 30)
-        is_false(MyClass.__table__.c.data_one.nullable)
-        eq_(MyClass.__table__.c.data_two.type.length, 30)
-        is_true(MyClass.__table__.c.data_two.nullable)
-        eq_(MyClass.__table__.c.data_three.type.length, 50)
-
-    def test_plain_typealias_as_typemap_keys(
-        self, decl_base: Type[DeclarativeBase]
+    @annotated_name_test_cases(
+        ("sort_order", 100, lambda sort_order: sort_order == 100),
+        ("nullable", False, lambda column: column.nullable is False),
+        (
+            "active_history",
+            True,
+            lambda column_property: column_property.active_history is True,
+        ),
+        (
+            "deferred",
+            True,
+            lambda column_property: column_property.deferred is True,
+        ),
+        (
+            "deferred",
+            _NoArg.NO_ARG,
+            lambda column_property: column_property is None,
+        ),
+        (
+            "deferred_group",
+            "mygroup",
+            lambda column_property: column_property.deferred is True
+            and column_property.group == "mygroup",
+        ),
+        (
+            "deferred_raiseload",
+            True,
+            lambda column_property: column_property.deferred is True
+            and column_property.raiseload is True,
+        ),
+        (
+            "server_default",
+            "25",
+            lambda column: column.server_default.arg == "25",
+        ),
+        (
+            "server_onupdate",
+            "25",
+            lambda column: column.server_onupdate.arg == "25",
+        ),
+        (
+            "default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "insert_default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "onupdate",
+            25,
+            lambda column: column.onupdate.arg == 25,
+        ),
+        ("doc", "some doc", lambda column: column.doc == "some doc"),
+        (
+            "comment",
+            "some comment",
+            lambda column: column.comment == "some comment",
+        ),
+        ("index", True, lambda column: column.index is True),
+        ("index", _NoArg.NO_ARG, lambda column: column.index is None),
+        ("index", False, lambda column: column.index is False),
+        ("unique", True, lambda column: column.unique is True),
+        ("unique", False, lambda column: column.unique is False),
+        ("autoincrement", True, lambda column: column.autoincrement is True),
+        ("system", True, lambda column: column.system is True),
+        ("primary_key", True, lambda column: column.primary_key is True),
+        ("type_", BIGINT, lambda column: isinstance(column.type, BIGINT)),
+        ("info", {"foo": "bar"}, lambda column: column.info == {"foo": "bar"}),
+        (
+            "use_existing_column",
+            True,
+            lambda mc: mc._use_existing_column is True,
+        ),
+        (
+            "quote",
+            True,
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "key",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "name",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "kw_only",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'kw_only' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "compare",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'compare' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "default_factory",
+            lambda: 25,
+            exc.SADeprecationWarning(
+                "Argument 'default_factory' is a dataclass argument "
+            ),
+        ),
+        (
+            "repr",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'repr' is a dataclass argument "
+            ),
+        ),
+        (
+            "init",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'init' is a dataclass argument"
+            ),
+        ),
+        (
+            "hash",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'hash' is a dataclass argument"
+            ),
+        ),
+        (
+            "dataclass_metadata",
+            {},
+            exc.SADeprecationWarning(
+                "Argument 'dataclass_metadata' is a dataclass argument"
+            ),
+        ),
+        argnames="argname, argument, assertion",
+    )
+    @testing.variation("use_annotated", [True, False, "control"])
+    def test_names_encountered_for_annotated(
+        self, argname, argument, assertion, use_annotated, decl_base
     ):
-        decl_base.registry.update_type_annotation_map(
-            {_UnionTypeAlias: JSON, _StrTypeAlias: String(30)}
+        global myint
+
+        if argument is not _NoArg.NO_ARG:
+            kw = {argname: argument}
+
+            if argname == "quote":
+                kw["name"] = "somename"
+        else:
+            kw = {}
+
+        is_warning = isinstance(assertion, exc.SADeprecationWarning)
+        is_dataclass = argname in (
+            "kw_only",
+            "init",
+            "repr",
+            "compare",
+            "default_factory",
+            "hash",
+            "dataclass_metadata",
         )
 
-        class Test(decl_base):
-            __tablename__ = "test"
+        if is_dataclass:
+
+            class Base(MappedAsDataclass, decl_base):
+                __abstract__ = True
+
+        else:
+            Base = decl_base
+
+        if use_annotated.control:
+            # test in reverse; that kw set on the main mapped_column() takes
+            # effect when the Annotated is there also and does not have the
+            # kw
+            amc = mapped_column()
+            myint = Annotated[int, amc]
+
+            mc = mapped_column(**kw)
+
+            class User(Base):
+                __tablename__ = "user"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                myname: Mapped[myint] = mc
+
+        elif use_annotated:
+            amc = mapped_column(**kw)
+            myint = Annotated[int, amc]
+
+            mc = mapped_column()
+
+            if is_warning:
+                with expect_deprecated(assertion.args[0]):
+
+                    class User(Base):
+                        __tablename__ = "user"
+                        id: Mapped[int] = mapped_column(primary_key=True)
+                        myname: Mapped[myint] = mc
+
+            else:
+
+                class User(Base):
+                    __tablename__ = "user"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    myname: Mapped[myint] = mc
+
+        else:
+            mc = cast(MappedColumn, mapped_column(**kw))
+
+        mapper_prop = mc.mapper_property_to_assign
+        column_to_assign, sort_order = mc.columns_to_assign[0]
+
+        if not is_warning:
+            assert_result = testing.resolve_lambda(
+                assertion,
+                sort_order=sort_order,
+                column_property=mapper_prop,
+                column=column_to_assign,
+                mc=mc,
+            )
+            assert assert_result
+        elif is_dataclass and (not use_annotated or use_annotated.control):
+            eq_(
+                getattr(mc._attribute_options, f"dataclasses_{argname}"),
+                argument,
+            )
+
+    @testing.combinations(("index",), ("unique",), argnames="paramname")
+    @testing.combinations((True,), (False,), (None,), argnames="orig")
+    @testing.combinations((True,), (False,), (None,), argnames="merging")
+    def test_index_unique_combinations(
+        self, paramname, orig, merging, decl_base
+    ):
+        """test #11091"""
+
+        global myint
+
+        amc = mapped_column(**{paramname: merging})
+        myint = Annotated[int, amc]
+
+        mc = mapped_column(**{paramname: orig})
+
+        class User(decl_base):
+            __tablename__ = "user"
             id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[_StrTypeAlias]
-            structure: Mapped[_UnionTypeAlias]
+            myname: Mapped[myint] = mc
 
-        eq_(Test.__table__.c.data.type.length, 30)
-        is_(Test.__table__.c.structure.type._type_affinity, JSON)
+        result = getattr(User.__table__.c.myname, paramname)
+        if orig is None:
+            is_(result, merging)
+        else:
+            is_(result, orig)
 
     @testing.variation(
-        "option",
+        "union",
         [
-            "plain",
             "union",
-            "union_604",
+            ("pep604", requires.python310),
             "union_null",
-            "union_null_604",
-            "optional",
-            "optional_union",
-            "optional_union_604",
-            "union_newtype",
-            "union_null_newtype",
-            "union_695",
-            "union_null_695",
+            ("pep604_null", requires.python310),
         ],
     )
-    @testing.variation("in_map", ["yes", "no", "value"])
-    @testing.requires.python312
-    def test_pep695_behavior(self, decl_base, in_map, option):
-        """Issue #11955"""
-        global tat
+    def test_unions(self, union):
+        global UnionType
+        our_type = Numeric(10, 2)
 
-        if option.plain:
-            tat = TypeAliasType("tat", str)
-        elif option.union:
-            tat = TypeAliasType("tat", Union[str, int])
-        elif option.union_604:
-            tat = TypeAliasType("tat", str | int)
-        elif option.union_null:
-            tat = TypeAliasType("tat", Union[str, int, None])
-        elif option.union_null_604:
-            tat = TypeAliasType("tat", str | int | None)
-        elif option.optional:
-            tat = TypeAliasType("tat", Optional[str])
-        elif option.optional_union:
-            tat = TypeAliasType("tat", Optional[Union[str, int]])
-        elif option.optional_union_604:
-            tat = TypeAliasType("tat", Optional[str | int])
-        elif option.union_newtype:
-            # this seems to be illegal for typing but "works"
-            tat = NewType("tat", Union[str, int])
-        elif option.union_null_newtype:
-            # this seems to be illegal for typing but "works"
-            tat = NewType("tat", Union[str, int, None])
-        elif option.union_695:
-            tat = TypeAliasType("tat", str | int)
-        elif option.union_null_695:
-            tat = TypeAliasType("tat", str | int | None)
+        if union.union:
+            UnionType = Union[float, Decimal]
+        elif union.union_null:
+            UnionType = Union[float, Decimal, None]
+        elif union.pep604:
+            UnionType = float | Decimal
+        elif union.pep604_null:
+            UnionType = float | Decimal | None
         else:
-            option.fail()
+            union.fail()
 
-        if in_map.yes:
-            decl_base.registry.update_type_annotation_map({tat: String(99)})
-        elif in_map.value and "newtype" not in option.name:
-            decl_base.registry.update_type_annotation_map(
-                {tat.__value__: String(99)}
+        class Base(DeclarativeBase):
+            type_annotation_map = {UnionType: our_type}
+
+        class User(Base):
+            __tablename__ = "users"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+
+            data: Mapped[Union[float, Decimal]]
+            reverse_data: Mapped[Union[Decimal, float]]
+
+            optional_data: Mapped[Optional[Union[float, Decimal]]] = (
+                mapped_column()
             )
 
-        def declare():
-            class Test(decl_base):
-                __tablename__ = "test"
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped[tat]
+            # use Optional directly
+            reverse_optional_data: Mapped[Optional[Union[Decimal, float]]] = (
+                mapped_column()
+            )
 
-            return Test.__table__.c.data
+            # use Union with None, same as Optional but presents differently
+            # (Optional object with __origin__ Union vs. Union)
+            reverse_u_optional_data: Mapped[Union[Decimal, float, None]] = (
+                mapped_column()
+            )
 
-        if in_map.yes:
-            col = declare()
-            length = 99
-        elif (
-            in_map.value
-            and "newtype" not in option.name
-            or option.optional
-            or option.plain
-        ):
-            with expect_deprecated(
-                "Matching the provided TypeAliasType 'tat' on its "
-                "resolved value without matching it in the "
-                "type_annotation_map is deprecated; add this type to the "
-                "type_annotation_map to allow it to match explicitly.",
-            ):
-                col = declare()
-            length = 99 if in_map.value else None
-        else:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                r"Could not locate SQLAlchemy Core type for Python type .*tat "
-                "inside the 'data' attribute Mapped annotation",
-            ):
-                declare()
-            return
+            refer_union: Mapped[UnionType]
+            refer_union_optional: Mapped[Optional[UnionType]]
 
-        is_true(isinstance(col.type, String))
-        eq_(col.type.length, length)
-        nullable = "null" in option.name or "optional" in option.name
-        eq_(col.nullable, nullable)
+            # py38, 37 does not automatically flatten unions, add extra tests
+            # for this.  maintain these in order to catch future regressions
+            # in the behavior of ``Union``
+            unflat_union_optional_data: Mapped[
+                Union[Union[Decimal, float, None], None]
+            ] = mapped_column()
 
-    @testing.variation(
-        "type_",
-        [
-            "str_extension",
-            "str_typing",
-            "generic_extension",
-            "generic_typing",
-            "generic_typed_extension",
-            "generic_typed_typing",
-        ],
-    )
-    @testing.requires.python312
-    def test_pep695_typealias_as_typemap_keys(
-        self, decl_base: Type[DeclarativeBase], type_
-    ):
-        """test #10807"""
+            float_data: Mapped[float] = mapped_column()
+            decimal_data: Mapped[Decimal] = mapped_column()
 
-        decl_base.registry.update_type_annotation_map(
-            {
-                _UnionPep695: JSON,
-                _StrPep695: String(30),
-                _TypingStrPep695: String(30),
-                _GenericPep695: String(30),
-                _TypingGenericPep695: String(30),
-                _GenericPep695Typed: String(30),
-                _TypingGenericPep695Typed: String(30),
-            }
-        )
+            if compat.py310:
+                pep604_data: Mapped[float | Decimal] = mapped_column()
+                pep604_reverse: Mapped[Decimal | float] = mapped_column()
+                pep604_optional: Mapped[Decimal | float | None] = (
+                    mapped_column()
+                )
+                pep604_data_fwd: Mapped["float | Decimal"] = mapped_column()
+                pep604_reverse_fwd: Mapped["Decimal | float"] = mapped_column()
+                pep604_optional_fwd: Mapped["Decimal | float | None"] = (
+                    mapped_column()
+                )
 
-        class Test(decl_base):
-            __tablename__ = "test"
-            id: Mapped[int] = mapped_column(primary_key=True)
-            if type_.str_extension:
-                data: Mapped[_StrPep695]
-            elif type_.str_typing:
-                data: Mapped[_TypingStrPep695]
-            elif type_.generic_extension:
-                data: Mapped[_GenericPep695]
-            elif type_.generic_typing:
-                data: Mapped[_TypingGenericPep695]
-            elif type_.generic_typed_extension:
-                data: Mapped[_GenericPep695Typed]
-            elif type_.generic_typed_typing:
-                data: Mapped[_TypingGenericPep695Typed]
-            else:
-                type_.fail()
-            structure: Mapped[_UnionPep695]
+        info = [
+            ("data", False),
+            ("reverse_data", False),
+            ("optional_data", True),
+            ("reverse_optional_data", True),
+            ("reverse_u_optional_data", True),
+            ("refer_union", "null" in union.name),
+            ("refer_union_optional", True),
+            ("unflat_union_optional_data", True),
+        ]
+        if compat.py310:
+            info += [
+                ("pep604_data", False),
+                ("pep604_reverse", False),
+                ("pep604_optional", True),
+                ("pep604_data_fwd", False),
+                ("pep604_reverse_fwd", False),
+                ("pep604_optional_fwd", True),
+            ]
+
+        for name, nullable in info:
+            col = User.__table__.c[name]
+            is_(col.type, our_type, name)
+            is_(col.nullable, nullable, name)
 
-        eq_(Test.__table__.c.data.type._type_affinity, String)
-        eq_(Test.__table__.c.data.type.length, 30)
-        is_(Test.__table__.c.structure.type._type_affinity, JSON)
+        is_true(isinstance(User.__table__.c.float_data.type, Float))
+        ne_(User.__table__.c.float_data.type, our_type)
+
+        is_true(isinstance(User.__table__.c.decimal_data.type, Numeric))
+        ne_(User.__table__.c.decimal_data.type, our_type)
 
     @testing.variation(
-        "alias_type",
-        ["none", "typekeyword", "typealias", "typekeyword_nested"],
+        "union",
+        [
+            "union",
+            ("pep604", requires.python310),
+            ("pep695", requires.python312),
+        ],
     )
-    @testing.requires.python312
-    def test_extract_pep593_from_pep695(
-        self, decl_base: Type[DeclarativeBase], alias_type
-    ):
-        """test #11130"""
-        if alias_type.typekeyword:
-            decl_base.registry.update_type_annotation_map(
-                {strtypalias_keyword: VARCHAR(33)}  # noqa: F821
-            )
-        if alias_type.typekeyword_nested:
-            decl_base.registry.update_type_annotation_map(
-                {strtypalias_keyword_nested: VARCHAR(42)}  # noqa: F821
-            )
-
-        class MyClass(decl_base):
-            __tablename__ = "my_table"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
+    def test_optional_in_annotation_map(self, union):
+        """See issue #11370"""
 
-            if alias_type.typekeyword:
-                data_one: Mapped[strtypalias_keyword]  # noqa: F821
-            elif alias_type.typealias:
-                data_one: Mapped[strtypalias_ta]  # noqa: F821
-            elif alias_type.none:
-                data_one: Mapped[strtypalias_plain]  # noqa: F821
-            elif alias_type.typekeyword_nested:
-                data_one: Mapped[strtypalias_keyword_nested]  # noqa: F821
+        class Base(DeclarativeBase):
+            if union.union:
+                type_annotation_map = {_Json: JSON}
+            elif union.pep604:
+                type_annotation_map = {_JsonPep604: JSON}
+            elif union.pep695:
+                type_annotation_map = {_JsonPep695: JSON}  # noqa: F821
             else:
-                alias_type.fail()
+                union.fail()
 
-        table = MyClass.__table__
-        assert table is not None
+        class A(Base):
+            __tablename__ = "a"
 
-        if alias_type.typekeyword_nested:
-            # a nested annotation is not supported
-            eq_(MyClass.data_one.expression.info, {})
-        else:
-            eq_(MyClass.data_one.expression.info, {"hi": "there"})
+            id: Mapped[int] = mapped_column(primary_key=True)
+            if union.union:
+                json1: Mapped[_Json]
+                json2: Mapped[_Json] = mapped_column(nullable=False)
+            elif union.pep604:
+                json1: Mapped[_JsonPep604]
+                json2: Mapped[_JsonPep604] = mapped_column(nullable=False)
+            elif union.pep695:
+                json1: Mapped[_JsonPep695]  # noqa: F821
+                json2: Mapped[_JsonPep695] = mapped_column(  # noqa: F821
+                    nullable=False
+                )
+            else:
+                union.fail()
 
-        if alias_type.typekeyword:
-            eq_(MyClass.data_one.type.length, 33)
-        elif alias_type.typekeyword_nested:
-            eq_(MyClass.data_one.type.length, 42)
-        else:
-            eq_(MyClass.data_one.type.length, None)
+        is_(A.__table__.c.json1.type._type_affinity, JSON)
+        is_(A.__table__.c.json2.type._type_affinity, JSON)
+        is_true(A.__table__.c.json1.nullable)
+        is_false(A.__table__.c.json2.nullable)
 
     @testing.variation(
-        "type_",
+        "option",
         [
-            "literal",
-            "literal_typing",
-            "recursive",
-            "not_literal",
-            "not_literal_typing",
-            "generic",
-            "generic_typing",
-            "generic_typed",
-            "generic_typed_typing",
+            "not_optional",
+            "optional",
+            "optional_fwd_ref",
+            "union_none",
+            ("pep604", testing.requires.python310),
+            ("pep604_fwd_ref", testing.requires.python310),
         ],
     )
-    @testing.combinations(True, False, argnames="in_map")
-    @testing.requires.python312
-    def test_pep695_literal_defaults_to_enum(self, decl_base, type_, in_map):
-        """test #11305."""
+    @testing.variation("brackets", ["oneset", "twosets"])
+    @testing.combinations(
+        "include_mc_type", "derive_from_anno", argnames="include_mc_type"
+    )
+    def test_optional_styles_nested_brackets(
+        self, option, brackets, include_mc_type
+    ):
+        """composed types test, includes tests that were added later for
+        #12207"""
 
-        def declare():
-            class Foo(decl_base):
-                __tablename__ = "footable"
+        class Base(DeclarativeBase):
+            if testing.requires.python310.enabled:
+                type_annotation_map = {
+                    Dict[str, Decimal]: JSON,
+                    dict[str, Decimal]: JSON,
+                    Union[List[int], List[str]]: JSON,
+                    list[int] | list[str]: JSON,
+                }
+            else:
+                type_annotation_map = {
+                    Dict[str, Decimal]: JSON,
+                    Union[List[int], List[str]]: JSON,
+                }
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                if type_.recursive:
-                    status: Mapped[_RecursiveLiteral695]  # noqa: F821
-                elif type_.literal:
-                    status: Mapped[_Literal695]  # noqa: F821
-                elif type_.literal_typing:
-                    status: Mapped[_TypingLiteral695]  # noqa: F821
-                elif type_.not_literal:
-                    status: Mapped[_StrPep695]  # noqa: F821
-                elif type_.not_literal_typing:
-                    status: Mapped[_TypingStrPep695]  # noqa: F821
-                elif type_.generic:
-                    status: Mapped[_GenericPep695]  # noqa: F821
-                elif type_.generic_typing:
-                    status: Mapped[_TypingGenericPep695]  # noqa: F821
-                elif type_.generic_typed:
-                    status: Mapped[_GenericPep695Typed]  # noqa: F821
-                elif type_.generic_typed_typing:
-                    status: Mapped[_TypingGenericPep695Typed]  # noqa: F821
-                else:
-                    type_.fail()
+        if include_mc_type == "include_mc_type":
+            mc = mapped_column(JSON)
+            mc2 = mapped_column(JSON)
+        else:
+            mc = mapped_column()
+            mc2 = mapped_column()
 
-            return Foo
+        class A(Base):
+            __tablename__ = "a"
 
-        if in_map:
-            decl_base.registry.update_type_annotation_map(
-                {
-                    _Literal695: Enum(enum.Enum),  # noqa: F821
-                    _TypingLiteral695: Enum(enum.Enum),  # noqa: F821
-                    _RecursiveLiteral695: Enum(enum.Enum),  # noqa: F821
-                    _StrPep695: Enum(enum.Enum),  # noqa: F821
-                    _TypingStrPep695: Enum(enum.Enum),  # noqa: F821
-                    _GenericPep695: Enum(enum.Enum),  # noqa: F821
-                    _TypingGenericPep695: Enum(enum.Enum),  # noqa: F821
-                    _GenericPep695Typed: Enum(enum.Enum),  # noqa: F821
-                    _TypingGenericPep695Typed: Enum(enum.Enum),  # noqa: F821
-                }
-            )
-            if type_.recursive:
-                with expect_deprecated(
-                    "Mapping recursive TypeAliasType '.+' that resolve to "
-                    "literal to generate an Enum is deprecated. SQLAlchemy "
-                    "2.1 will not support this use case. Please avoid using "
-                    "recursing TypeAliasType",
-                ):
-                    Foo = declare()
-            elif type_.literal or type_.literal_typing:
-                Foo = declare()
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str] = mapped_column()
+
+            if brackets.oneset:
+                if option.not_optional:
+                    json: Mapped[Dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
+                elif option.optional:
+                    json: Mapped[Optional[Dict[str, Decimal]]] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[Optional[dict[str, Decimal]]] = mc2
+                elif option.optional_fwd_ref:
+                    json: Mapped["Optional[Dict[str, Decimal]]"] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped["Optional[dict[str, Decimal]]"] = mc2
+                elif option.union_none:
+                    json: Mapped[Union[Dict[str, Decimal], None]] = mc
+                    json2: Mapped[Union[None, Dict[str, Decimal]]] = mc2
+                elif option.pep604:
+                    json: Mapped[dict[str, Decimal] | None] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[None | dict[str, Decimal]] = mc2
+                elif option.pep604_fwd_ref:
+                    json: Mapped["dict[str, Decimal] | None"] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped["None | dict[str, Decimal]"] = mc2
+            elif brackets.twosets:
+                if option.not_optional:
+                    json: Mapped[Union[List[int], List[str]]] = mapped_column()  # type: ignore  # noqa: E501
+                elif option.optional:
+                    json: Mapped[Optional[Union[List[int], List[str]]]] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[
+                            Optional[Union[list[int], list[str]]]
+                        ] = mc2
+                elif option.optional_fwd_ref:
+                    json: Mapped["Optional[Union[List[int], List[str]]]"] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[
+                            "Optional[Union[list[int], list[str]]]"
+                        ] = mc2
+                elif option.union_none:
+                    json: Mapped[Union[List[int], List[str], None]] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[Union[None, list[int], list[str]]] = mc2
+                elif option.pep604:
+                    json: Mapped[list[int] | list[str] | None] = mc
+                    json2: Mapped[None | list[int] | list[str]] = mc2
+                elif option.pep604_fwd_ref:
+                    json: Mapped["list[int] | list[str] | None"] = mc
+                    json2: Mapped["None | list[int] | list[str]"] = mc2
             else:
-                with expect_raises_message(
-                    exc.ArgumentError,
-                    "Can't associate TypeAliasType '.+' to an Enum "
-                    "since it's not a direct alias of a Literal. Only "
-                    "aliases in this form `type my_alias = Literal.'a', "
-                    "'b'.` are supported when generating Enums.",
-                ):
-                    declare()
-                return
-        elif (
-            type_.generic
-            or type_.generic_typing
-            or type_.generic_typed
-            or type_.generic_typed_typing
-        ):
-            # This behaves like 2.1 -> rationale is that no-one asked to
-            # support such types and in 2.1 will already be like this
-            # so it makes little sense to add support this late in the 2.0
-            # series
+                brackets.fail()
+
+        is_(A.__table__.c.json.type._type_affinity, JSON)
+        if hasattr(A, "json2"):
+            is_(A.__table__.c.json2.type._type_affinity, JSON)
+            if option.not_optional:
+                is_false(A.__table__.c.json2.nullable)
+            else:
+                is_true(A.__table__.c.json2.nullable)
+
+        if option.not_optional:
+            is_false(A.__table__.c.json.nullable)
+        else:
+            is_true(A.__table__.c.json.nullable)
+
+    @testing.variation("optional", [True, False])
+    @testing.variation("provide_type", [True, False])
+    @testing.variation("add_to_type_map", [True, False])
+    def test_recursive_type(
+        self, decl_base, optional, provide_type, add_to_type_map
+    ):
+        """test #9553"""
+
+        global T
+
+        T = Dict[str, Optional["T"]]
+
+        if not provide_type and not add_to_type_map:
             with expect_raises_message(
-                exc.ArgumentError,
-                "Could not locate SQLAlchemy Core type for Python type "
-                ".+ inside the 'status' attribute Mapped annotation",
+                sa_exc.ArgumentError,
+                r"Could not locate SQLAlchemy.*" r".*ForwardRef\('T'\).*",
             ):
-                declare()
+
+                class TypeTest(decl_base):
+                    __tablename__ = "my_table"
+
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    if optional:
+                        type_test: Mapped[Optional[T]] = mapped_column()
+                    else:
+                        type_test: Mapped[T] = mapped_column()
+
             return
+
         else:
-            with expect_deprecated(
-                "Matching the provided TypeAliasType '.*' on its "
-                "resolved value without matching it in the "
-                "type_annotation_map is deprecated; add this type to the "
-                "type_annotation_map to allow it to match explicitly.",
-            ):
-                Foo = declare()
-        col = Foo.__table__.c.status
-        if in_map and not type_.not_literal:
-            is_true(isinstance(col.type, Enum))
-            eq_(col.type.enums, ["to-do", "in-progress", "done"])
-            is_(col.type.native_enum, False)
+            if add_to_type_map:
+                decl_base.registry.update_type_annotation_map({T: JSON()})
+
+            class TypeTest(decl_base):
+                __tablename__ = "my_table"
+
+                id: Mapped[int] = mapped_column(primary_key=True)
+
+                if add_to_type_map:
+                    if optional:
+                        type_test: Mapped[Optional[T]] = mapped_column()
+                    else:
+                        type_test: Mapped[T] = mapped_column()
+                else:
+                    if optional:
+                        type_test: Mapped[Optional[T]] = mapped_column(JSON())
+                    else:
+                        type_test: Mapped[T] = mapped_column(JSON())
+
+        if optional:
+            is_(TypeTest.__table__.c.type_test.nullable, True)
         else:
-            is_true(isinstance(col.type, String))
+            is_(TypeTest.__table__.c.type_test.nullable, False)
 
-    @testing.requires.python38
-    def test_typing_literal_identity(self, decl_base):
-        """See issue #11820"""
+        self.assert_compile(
+            select(TypeTest),
+            "SELECT my_table.id, my_table.type_test FROM my_table",
+        )
 
-        class Foo(decl_base):
-            __tablename__ = "footable"
+    def test_missing_mapped_lhs(self, decl_base):
+        with expect_annotation_syntax_error("User.name"):
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            t: Mapped[_TypingLiteral]
-            te: Mapped[_TypingExtensionsLiteral]
+            class User(decl_base):
+                __tablename__ = "users"
 
-        for col in (Foo.__table__.c.t, Foo.__table__.c.te):
-            is_true(isinstance(col.type, Enum))
-            eq_(col.type.enums, ["a", "b"])
-            is_(col.type.native_enum, False)
+                id: Mapped[int] = mapped_column(primary_key=True)
+                name: str = mapped_column()  # type: ignore
 
-    @testing.requires.python310
-    def test_we_got_all_attrs_test_annotated(self):
-        argnames = _py_inspect.getfullargspec(mapped_column)
-        assert _annotated_names_tested.issuperset(argnames.kwonlyargs), (
-            f"annotated attributes were not tested: "
-            f"{set(argnames.kwonlyargs).difference(_annotated_names_tested)}"
+    def test_construct_lhs_separate_name(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+            name: Mapped[str] = mapped_column()
+            data: Mapped[Optional[str]] = mapped_column("the_data")
+
+        self.assert_compile(
+            select(User.data), "SELECT users.the_data FROM users"
         )
+        is_true(User.__table__.c.the_data.nullable)
 
-    @annotated_name_test_cases(
-        ("sort_order", 100, lambda sort_order: sort_order == 100),
-        ("nullable", False, lambda column: column.nullable is False),
-        (
-            "active_history",
-            True,
-            lambda column_property: column_property.active_history is True,
-        ),
-        (
-            "deferred",
-            True,
-            lambda column_property: column_property.deferred is True,
-        ),
-        (
-            "deferred",
-            _NoArg.NO_ARG,
-            lambda column_property: column_property is None,
-        ),
-        (
-            "deferred_group",
-            "mygroup",
-            lambda column_property: column_property.deferred is True
-            and column_property.group == "mygroup",
-        ),
-        (
-            "deferred_raiseload",
-            True,
-            lambda column_property: column_property.deferred is True
-            and column_property.raiseload is True,
-        ),
-        (
-            "server_default",
-            "25",
-            lambda column: column.server_default.arg == "25",
-        ),
-        (
-            "server_onupdate",
-            "25",
-            lambda column: column.server_onupdate.arg == "25",
-        ),
-        (
-            "default",
-            25,
-            lambda column: column.default.arg == 25,
-        ),
-        (
-            "insert_default",
-            25,
-            lambda column: column.default.arg == 25,
-        ),
-        (
-            "onupdate",
-            25,
-            lambda column: column.onupdate.arg == 25,
-        ),
-        ("doc", "some doc", lambda column: column.doc == "some doc"),
-        (
-            "comment",
-            "some comment",
-            lambda column: column.comment == "some comment",
-        ),
-        ("index", True, lambda column: column.index is True),
-        ("index", _NoArg.NO_ARG, lambda column: column.index is None),
-        ("index", False, lambda column: column.index is False),
-        ("unique", True, lambda column: column.unique is True),
-        ("unique", False, lambda column: column.unique is False),
-        ("autoincrement", True, lambda column: column.autoincrement is True),
-        ("system", True, lambda column: column.system is True),
-        ("primary_key", True, lambda column: column.primary_key is True),
-        ("type_", BIGINT, lambda column: isinstance(column.type, BIGINT)),
-        ("info", {"foo": "bar"}, lambda column: column.info == {"foo": "bar"}),
-        (
-            "use_existing_column",
-            True,
-            lambda mc: mc._use_existing_column is True,
-        ),
-        (
-            "quote",
-            True,
-            exc.SADeprecationWarning(
-                "Can't use the 'key' or 'name' arguments in Annotated "
-            ),
-        ),
-        (
-            "key",
-            "mykey",
-            exc.SADeprecationWarning(
-                "Can't use the 'key' or 'name' arguments in Annotated "
-            ),
-        ),
-        (
-            "name",
-            "mykey",
-            exc.SADeprecationWarning(
-                "Can't use the 'key' or 'name' arguments in Annotated "
-            ),
-        ),
-        (
-            "kw_only",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'kw_only' is a dataclass argument "
-            ),
-            testing.requires.python310,
-        ),
-        (
-            "compare",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'compare' is a dataclass argument "
-            ),
-            testing.requires.python310,
-        ),
-        (
-            "default_factory",
-            lambda: 25,
-            exc.SADeprecationWarning(
-                "Argument 'default_factory' is a dataclass argument "
-            ),
-        ),
-        (
-            "repr",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'repr' is a dataclass argument "
-            ),
-        ),
-        (
-            "init",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'init' is a dataclass argument"
-            ),
-        ),
-        (
-            "hash",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'hash' is a dataclass argument"
-            ),
-        ),
-        (
-            "dataclass_metadata",
-            {},
-            exc.SADeprecationWarning(
-                "Argument 'dataclass_metadata' is a dataclass argument"
-            ),
-        ),
-        argnames="argname, argument, assertion",
-    )
-    @testing.variation("use_annotated", [True, False, "control"])
-    def test_names_encountered_for_annotated(
-        self, argname, argument, assertion, use_annotated, decl_base
-    ):
-        global myint
+    def test_construct_works_in_expr(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-        if argument is not _NoArg.NO_ARG:
-            kw = {argname: argument}
+            id: Mapped[int] = mapped_column(primary_key=True)
 
-            if argname == "quote":
-                kw["name"] = "somename"
-        else:
-            kw = {}
+        class Address(decl_base):
+            __tablename__ = "addresses"
 
-        is_warning = isinstance(assertion, exc.SADeprecationWarning)
-        is_dataclass = argname in (
-            "kw_only",
-            "init",
-            "repr",
-            "compare",
-            "default_factory",
-            "hash",
-            "dataclass_metadata",
-        )
+            id: Mapped[int] = mapped_column(primary_key=True)
+            user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
 
-        if is_dataclass:
+            user = relationship(User, primaryjoin=user_id == User.id)
 
-            class Base(MappedAsDataclass, decl_base):
-                __abstract__ = True
+        self.assert_compile(
+            select(Address.user_id, User.id).join(Address.user),
+            "SELECT addresses.user_id, users.id FROM addresses "
+            "JOIN users ON addresses.user_id = users.id",
+        )
 
-        else:
-            Base = decl_base
+    def test_construct_works_as_polymorphic_on(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-        if use_annotated.control:
-            # test in reverse; that kw set on the main mapped_column() takes
-            # effect when the Annotated is there also and does not have the
-            # kw
-            amc = mapped_column()
-            myint = Annotated[int, amc]
+            id: Mapped[int] = mapped_column(primary_key=True)
+            type: Mapped[str] = mapped_column()
 
-            mc = mapped_column(**kw)
+            __mapper_args__ = {"polymorphic_on": type}
 
-            class User(Base):
-                __tablename__ = "user"
-                id: Mapped[int] = mapped_column(primary_key=True)
-                myname: Mapped[myint] = mc
+        decl_base.registry.configure()
+        is_(User.__table__.c.type, User.__mapper__.polymorphic_on)
 
-        elif use_annotated:
-            amc = mapped_column(**kw)
-            myint = Annotated[int, amc]
+    def test_construct_works_as_version_id_col(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-            mc = mapped_column()
+            id: Mapped[int] = mapped_column(primary_key=True)
+            version_id: Mapped[int] = mapped_column()
 
-            if is_warning:
-                with expect_deprecated(assertion.args[0]):
+            __mapper_args__ = {"version_id_col": version_id}
 
-                    class User(Base):
-                        __tablename__ = "user"
-                        id: Mapped[int] = mapped_column(primary_key=True)
-                        myname: Mapped[myint] = mc
+        decl_base.registry.configure()
+        is_(User.__table__.c.version_id, User.__mapper__.version_id_col)
 
-            else:
+    def test_construct_works_in_deferred(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-                class User(Base):
-                    __tablename__ = "user"
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    myname: Mapped[myint] = mc
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str] = deferred(mapped_column())
 
-        else:
-            mc = cast(MappedColumn, mapped_column(**kw))
+        self.assert_compile(select(User), "SELECT users.id FROM users")
+        self.assert_compile(
+            select(User).options(undefer(User.data)),
+            "SELECT users.id, users.data FROM users",
+        )
 
-        mapper_prop = mc.mapper_property_to_assign
-        column_to_assign, sort_order = mc.columns_to_assign[0]
+    def test_deferred_kw(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-        if not is_warning:
-            assert_result = testing.resolve_lambda(
-                assertion,
-                sort_order=sort_order,
-                column_property=mapper_prop,
-                column=column_to_assign,
-                mc=mc,
-            )
-            assert assert_result
-        elif is_dataclass and (not use_annotated or use_annotated.control):
-            eq_(
-                getattr(mc._attribute_options, f"dataclasses_{argname}"),
-                argument,
-            )
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str] = mapped_column(deferred=True)
 
-    @testing.combinations(("index",), ("unique",), argnames="paramname")
-    @testing.combinations((True,), (False,), (None,), argnames="orig")
-    @testing.combinations((True,), (False,), (None,), argnames="merging")
-    def test_index_unique_combinations(
-        self, paramname, orig, merging, decl_base
-    ):
-        """test #11091"""
+        self.assert_compile(select(User), "SELECT users.id FROM users")
+        self.assert_compile(
+            select(User).options(undefer(User.data)),
+            "SELECT users.id, users.data FROM users",
+        )
 
-        global myint
 
-        amc = mapped_column(**{paramname: merging})
-        myint = Annotated[int, amc]
+class Pep593InterpretationTests(fixtures.TestBase, testing.AssertsCompiledSQL):
+    __dialect__ = "default"
 
-        mc = mapped_column(**{paramname: orig})
+    def test_extract_from_pep593(self, decl_base):
+        global Address
+
+        @dataclasses.dataclass
+        class Address:
+            street: str
+            state: str
+            zip_: str
 
         class User(decl_base):
             __tablename__ = "user"
+
             id: Mapped[int] = mapped_column(primary_key=True)
-            myname: Mapped[myint] = mc
+            name: Mapped[str] = mapped_column()
 
-        result = getattr(User.__table__.c.myname, paramname)
-        if orig is None:
-            is_(result, merging)
-        else:
-            is_(result, orig)
+            address: Mapped[Annotated[Address, "foo"]] = composite(
+                mapped_column(), mapped_column(), mapped_column("zip")
+            )
 
-    def test_pep484_newtypes_as_typemap_keys(
+        self.assert_compile(
+            select(User),
+            'SELECT "user".id, "user".name, "user".street, '
+            '"user".state, "user".zip FROM "user"',
+            dialect="default",
+        )
+
+    def test_pep593_types_as_typemap_keys(
         self, decl_base: Type[DeclarativeBase]
     ):
-        global str50, str30, str3050
+        """neat!!!"""
+        global str50, str30, opt_str50, opt_str30
 
-        str50 = NewType("str50", str)
-        str30 = NewType("str30", str)
-        str3050 = NewType("str30", str50)
+        str50 = Annotated[str, 50]
+        str30 = Annotated[str, 30]
+        opt_str50 = Optional[str50]
+        opt_str30 = Optional[str30]
 
         decl_base.registry.update_type_annotation_map(
-            {str50: String(50), str30: String(30), str3050: String(150)}
+            {str50: String(50), str30: String(30)}
         )
 
         class MyClass(decl_base):
@@ -1644,48 +1524,130 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
 
             id: Mapped[str50] = mapped_column(primary_key=True)
             data_one: Mapped[str30]
-            data_two: Mapped[str50]
-            data_three: Mapped[Optional[str30]]
-            data_four: Mapped[str3050]
+            data_two: Mapped[opt_str30]
+            data_three: Mapped[str50]
+            data_four: Mapped[opt_str50]
+            data_five: Mapped[str]
+            data_six: Mapped[Optional[str]]
 
         eq_(MyClass.__table__.c.data_one.type.length, 30)
         is_false(MyClass.__table__.c.data_one.nullable)
+        eq_(MyClass.__table__.c.data_two.type.length, 30)
+        is_true(MyClass.__table__.c.data_two.nullable)
+        eq_(MyClass.__table__.c.data_three.type.length, 50)
 
-        eq_(MyClass.__table__.c.data_two.type.length, 50)
-        is_false(MyClass.__table__.c.data_two.nullable)
+    @testing.variation(
+        "alias_type",
+        [
+            "none",
+            "typekeyword",
+            "typekeyword_unpopulated",
+            "typealias",
+            "typekeyword_nested",
+        ],
+    )
+    @testing.requires.python312
+    def test_extract_pep593_from_pep695(
+        self, decl_base: Type[DeclarativeBase], alias_type
+    ):
+        """test #11130"""
+        if alias_type.typekeyword:
+            decl_base.registry.update_type_annotation_map(
+                {strtypalias_keyword: VARCHAR(33)}  # noqa: F821
+            )
+        if alias_type.typekeyword_nested:
+            decl_base.registry.update_type_annotation_map(
+                {strtypalias_keyword_nested: VARCHAR(42)}  # noqa: F821
+            )
 
-        eq_(MyClass.__table__.c.data_three.type.length, 30)
-        is_true(MyClass.__table__.c.data_three.nullable)
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
 
-        eq_(MyClass.__table__.c.data_four.type.length, 150)
-        is_false(MyClass.__table__.c.data_four.nullable)
+            id: Mapped[int] = mapped_column(primary_key=True)
 
-    def test_newtype_missing_from_map(self, decl_base):
-        global str50
+            if alias_type.typekeyword or alias_type.typekeyword_unpopulated:
+                data_one: Mapped[strtypalias_keyword]  # noqa: F821
+            elif alias_type.typealias:
+                data_one: Mapped[strtypalias_ta]  # noqa: F821
+            elif alias_type.none:
+                data_one: Mapped[strtypalias_plain]  # noqa: F821
+            elif alias_type.typekeyword_nested:
+                data_one: Mapped[strtypalias_keyword_nested]  # noqa: F821
+            else:
+                alias_type.fail()
 
-        str50 = NewType("str50", str)
+        table = MyClass.__table__
+        assert table is not None
 
-        if compat.py310:
-            text = ".*str50"
+        if alias_type.typekeyword_nested:
+            # a nested annotation is not supported
+            eq_(MyClass.data_one.expression.info, {})
         else:
-            # NewTypes before 3.10 had a very bad repr
-            # <function NewType.<locals>.new_type at 0x...>
-            text = ".*NewType.*"
+            eq_(MyClass.data_one.expression.info, {"hi": "there"})
 
-        with expect_deprecated(
-            f"Matching the provided NewType '{text}' on its "
-            "resolved value without matching it in the "
-            "type_annotation_map is deprecated; add this type to the "
-            "type_annotation_map to allow it to match explicitly.",
+        if alias_type.typekeyword:
+            eq_(MyClass.data_one.type.length, 33)
+        elif alias_type.typekeyword_nested:
+            eq_(MyClass.data_one.type.length, 42)
+        else:
+            eq_(MyClass.data_one.type.length, None)
+
+    @testing.requires.python312
+    def test_no_recursive_pep593_from_pep695(
+        self, decl_base: Type[DeclarativeBase]
+    ):
+        def declare():
+            class MyClass(decl_base):
+                __tablename__ = "my_table"
+
+                id: Mapped[int] = mapped_column(primary_key=True)
+
+                data_one: Mapped[_RecursivePep695Pep593]  # noqa: F821
+
+        with expect_raises_message(
+            orm_exc.MappedAnnotationError,
+            r"Could not locate SQLAlchemy Core type when resolving for Python "
+            r"type "
+            r"indicated by '_RecursivePep695Pep593' inside the Mapped\[\] "
+            r"annotation for the 'data_one' attribute; none of "
+            r"'_RecursivePep695Pep593', "
+            r"'typing.Annotated\[_TypingStrPep695, .*\]', '_TypingStrPep695' "
+            r"are resolvable by the registry",
         ):
+            declare()
+
+    @testing.variation("in_map", [True, False])
+    @testing.variation("alias_type", ["plain", "pep695"])
+    @testing.requires.python312
+    def test_generic_typealias_pep593(
+        self, decl_base: Type[DeclarativeBase], alias_type: Variation, in_map
+    ):
+
+        if in_map:
+            decl_base.registry.update_type_annotation_map(
+                {
+                    _GenericPep593TypeAlias[str]: VARCHAR(33),
+                    _GenericPep593Pep695[str]: VARCHAR(33),
+                }
+            )
 
-            class MyClass(decl_base):
-                __tablename__ = "my_table"
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data_one: Mapped[str50]
+            id: Mapped[int] = mapped_column(primary_key=True)
 
-        is_true(isinstance(MyClass.data_one.type, String))
+            if alias_type.plain:
+                data_one: Mapped[_GenericPep593TypeAlias[str]]  # noqa: F821
+            elif alias_type.pep695:
+                data_one: Mapped[_GenericPep593Pep695[str]]  # noqa: F821
+            else:
+                alias_type.fail()
+
+        eq_(MyClass.data_one.expression.info, {"hi": "there"})
+        if in_map:
+            eq_(MyClass.data_one.expression.type.length, 33)
+        else:
+            eq_(MyClass.data_one.expression.type.length, None)
 
     def test_extract_base_type_from_pep593(
         self, decl_base: Type[DeclarativeBase]
@@ -2212,536 +2174,617 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         eq_(A_1.label.property.columns[0].table, A.__table__)
         eq_(A_2.label.property.columns[0].table, A.__table__)
 
-    @testing.variation(
-        "union",
-        [
-            "union",
-            ("pep604", requires.python310),
-            "union_null",
-            ("pep604_null", requires.python310),
-        ],
-    )
-    def test_unions(self, union):
-        global UnionType
-        our_type = Numeric(10, 2)
-
-        if union.union:
-            UnionType = Union[float, Decimal]
-        elif union.union_null:
-            UnionType = Union[float, Decimal, None]
-        elif union.pep604:
-            UnionType = float | Decimal
-        elif union.pep604_null:
-            UnionType = float | Decimal | None
-        else:
-            union.fail()
 
-        class Base(DeclarativeBase):
-            type_annotation_map = {UnionType: our_type}
+class TypeResolutionTests(fixtures.TestBase, testing.AssertsCompiledSQL):
+    __dialect__ = "default"
 
-        class User(Base):
-            __tablename__ = "users"
+    @testing.combinations(
+        (str, types.String),
+        (Decimal, types.Numeric),
+        (float, types.Float),
+        (datetime.datetime, types.DateTime),
+        (uuid.UUID, types.Uuid),
+        argnames="pytype_arg,sqltype",
+    )
+    def test_datatype_lookups(self, decl_base, pytype_arg, sqltype):
+        global pytype
+        pytype = pytype_arg
 
+        class MyClass(decl_base):
+            __tablename__ = "mytable"
             id: Mapped[int] = mapped_column(primary_key=True)
 
-            data: Mapped[Union[float, Decimal]]
-            reverse_data: Mapped[Union[Decimal, float]]
-
-            optional_data: Mapped[Optional[Union[float, Decimal]]] = (
-                mapped_column()
-            )
-
-            # use Optional directly
-            reverse_optional_data: Mapped[Optional[Union[Decimal, float]]] = (
-                mapped_column()
-            )
-
-            # use Union with None, same as Optional but presents differently
-            # (Optional object with __origin__ Union vs. Union)
-            reverse_u_optional_data: Mapped[Union[Decimal, float, None]] = (
-                mapped_column()
-            )
-
-            refer_union: Mapped[UnionType]
-            refer_union_optional: Mapped[Optional[UnionType]]
-
-            # py38, 37 does not automatically flatten unions, add extra tests
-            # for this.  maintain these in order to catch future regressions
-            # in the behavior of ``Union``
-            unflat_union_optional_data: Mapped[
-                Union[Union[Decimal, float, None], None]
-            ] = mapped_column()
+            data: Mapped[pytype]
 
-            float_data: Mapped[float] = mapped_column()
-            decimal_data: Mapped[Decimal] = mapped_column()
+        assert isinstance(MyClass.__table__.c.data.type, sqltype)
 
-            if compat.py310:
-                pep604_data: Mapped[float | Decimal] = mapped_column()
-                pep604_reverse: Mapped[Decimal | float] = mapped_column()
-                pep604_optional: Mapped[Decimal | float | None] = (
-                    mapped_column()
-                )
-                pep604_data_fwd: Mapped["float | Decimal"] = mapped_column()
-                pep604_reverse_fwd: Mapped["Decimal | float"] = mapped_column()
-                pep604_optional_fwd: Mapped["Decimal | float | None"] = (
-                    mapped_column()
-                )
+    @testing.combinations(
+        (BIGINT(),),
+        (BIGINT,),
+        (Integer().with_variant(BIGINT, "default")),
+        (Integer().with_variant(BIGINT(), "default")),
+        (BIGINT().with_variant(String(), "some_other_dialect")),
+    )
+    def test_type_map_varieties(self, typ):
+        Base = declarative_base(type_annotation_map={int: typ})
 
-        info = [
-            ("data", False),
-            ("reverse_data", False),
-            ("optional_data", True),
-            ("reverse_optional_data", True),
-            ("reverse_u_optional_data", True),
-            ("refer_union", "null" in union.name),
-            ("refer_union_optional", True),
-            ("unflat_union_optional_data", True),
-        ]
-        if compat.py310:
-            info += [
-                ("pep604_data", False),
-                ("pep604_reverse", False),
-                ("pep604_optional", True),
-                ("pep604_data_fwd", False),
-                ("pep604_reverse_fwd", False),
-                ("pep604_optional_fwd", True),
-            ]
+        class MyClass(Base):
+            __tablename__ = "mytable"
 
-        for name, nullable in info:
-            col = User.__table__.c[name]
-            is_(col.type, our_type, name)
-            is_(col.nullable, nullable, name)
+            id: Mapped[int] = mapped_column(primary_key=True)
+            x: Mapped[int]
+            y: Mapped[int] = mapped_column()
+            z: Mapped[int] = mapped_column(typ)
 
-        is_true(isinstance(User.__table__.c.float_data.type, Float))
-        ne_(User.__table__.c.float_data.type, our_type)
+        self.assert_compile(
+            CreateTable(MyClass.__table__),
+            "CREATE TABLE mytable (id BIGINT NOT NULL, "
+            "x BIGINT NOT NULL, y BIGINT NOT NULL, z BIGINT NOT NULL, "
+            "PRIMARY KEY (id))",
+        )
 
-        is_true(isinstance(User.__table__.c.decimal_data.type, Numeric))
-        ne_(User.__table__.c.decimal_data.type, our_type)
+    def test_dont_ignore_unresolvable(self, decl_base):
+        """test #8888"""
 
-    @testing.variation(
-        "union",
-        [
-            "union",
-            ("pep604", requires.python310),
-            ("pep695", requires.python312),
-        ],
-    )
-    def test_optional_in_annotation_map(self, union):
-        """See issue #11370"""
+        with expect_raises_message(
+            sa_exc.ArgumentError,
+            r"Could not resolve all types within mapped annotation: "
+            r"\".*Mapped\[.*fake.*\]\".  Ensure all types are written "
+            r"correctly and are imported within the module in use.",
+        ):
 
-        class Base(DeclarativeBase):
-            if union.union:
-                type_annotation_map = {_Json: JSON}
-            elif union.pep604:
-                type_annotation_map = {_JsonPep604: JSON}
-            elif union.pep695:
-                type_annotation_map = {_JsonPep695: JSON}  # noqa: F821
-            else:
-                union.fail()
+            class A(decl_base):
+                __tablename__ = "a"
 
-        class A(Base):
-            __tablename__ = "a"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped["fake"]  # noqa
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            if union.union:
-                json1: Mapped[_Json]
-                json2: Mapped[_Json] = mapped_column(nullable=False)
-            elif union.pep604:
-                json1: Mapped[_JsonPep604]
-                json2: Mapped[_JsonPep604] = mapped_column(nullable=False)
-            elif union.pep695:
-                json1: Mapped[_JsonPep695]  # noqa: F821
-                json2: Mapped[_JsonPep695] = mapped_column(  # noqa: F821
-                    nullable=False
-                )
-            else:
-                union.fail()
+    def test_type_dont_mis_resolve_on_superclass(self):
+        """test for #8859.
 
-        is_(A.__table__.c.json1.type._type_affinity, JSON)
-        is_(A.__table__.c.json2.type._type_affinity, JSON)
-        is_true(A.__table__.c.json1.nullable)
-        is_false(A.__table__.c.json2.nullable)
+        For subclasses of a type that's in the map, don't resolve this
+        by default, even though we do a search through __mro__.
 
-    @testing.variation(
-        "option",
-        [
-            "not_optional",
-            "optional",
-            "optional_fwd_ref",
-            "union_none",
-            ("pep604", testing.requires.python310),
-            ("pep604_fwd_ref", testing.requires.python310),
-        ],
-    )
-    @testing.variation("brackets", ["oneset", "twosets"])
-    @testing.combinations(
-        "include_mc_type", "derive_from_anno", argnames="include_mc_type"
-    )
-    def test_optional_styles_nested_brackets(
-        self, option, brackets, include_mc_type
-    ):
-        """composed types test, includes tests that were added later for
-        #12207"""
+        """
+        global int_sub
 
-        class Base(DeclarativeBase):
-            if testing.requires.python310.enabled:
-                type_annotation_map = {
-                    Dict[str, Decimal]: JSON,
-                    dict[str, Decimal]: JSON,
-                    Union[List[int], List[str]]: JSON,
-                    list[int] | list[str]: JSON,
-                }
-            else:
-                type_annotation_map = {
-                    Dict[str, Decimal]: JSON,
-                    Union[List[int], List[str]]: JSON,
-                }
+        class int_sub(int):
+            pass
 
-        if include_mc_type == "include_mc_type":
-            mc = mapped_column(JSON)
-            mc2 = mapped_column(JSON)
-        else:
-            mc = mapped_column()
-            mc2 = mapped_column()
+        Base = declarative_base(
+            type_annotation_map={
+                int: Integer,
+            }
+        )
 
-        class A(Base):
-            __tablename__ = "a"
+        with expect_raises_message(
+            orm_exc.MappedAnnotationError,
+            "Could not locate SQLAlchemy Core type",
+        ):
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str] = mapped_column()
+            class MyClass(Base):
+                __tablename__ = "mytable"
 
-            if brackets.oneset:
-                if option.not_optional:
-                    json: Mapped[Dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
-                elif option.optional:
-                    json: Mapped[Optional[Dict[str, Decimal]]] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[Optional[dict[str, Decimal]]] = mc2
-                elif option.optional_fwd_ref:
-                    json: Mapped["Optional[Dict[str, Decimal]]"] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped["Optional[dict[str, Decimal]]"] = mc2
-                elif option.union_none:
-                    json: Mapped[Union[Dict[str, Decimal], None]] = mc
-                    json2: Mapped[Union[None, Dict[str, Decimal]]] = mc2
-                elif option.pep604:
-                    json: Mapped[dict[str, Decimal] | None] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[None | dict[str, Decimal]] = mc2
-                elif option.pep604_fwd_ref:
-                    json: Mapped["dict[str, Decimal] | None"] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped["None | dict[str, Decimal]"] = mc2
-            elif brackets.twosets:
-                if option.not_optional:
-                    json: Mapped[Union[List[int], List[str]]] = mapped_column()  # type: ignore  # noqa: E501
-                elif option.optional:
-                    json: Mapped[Optional[Union[List[int], List[str]]]] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[
-                            Optional[Union[list[int], list[str]]]
-                        ] = mc2
-                elif option.optional_fwd_ref:
-                    json: Mapped["Optional[Union[List[int], List[str]]]"] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[
-                            "Optional[Union[list[int], list[str]]]"
-                        ] = mc2
-                elif option.union_none:
-                    json: Mapped[Union[List[int], List[str], None]] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[Union[None, list[int], list[str]]] = mc2
-                elif option.pep604:
-                    json: Mapped[list[int] | list[str] | None] = mc
-                    json2: Mapped[None | list[int] | list[str]] = mc2
-                elif option.pep604_fwd_ref:
-                    json: Mapped["list[int] | list[str] | None"] = mc
-                    json2: Mapped["None | list[int] | list[str]"] = mc2
-            else:
-                brackets.fail()
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[int_sub]
 
-        is_(A.__table__.c.json.type._type_affinity, JSON)
-        if hasattr(A, "json2"):
-            is_(A.__table__.c.json2.type._type_affinity, JSON)
-            if option.not_optional:
-                is_false(A.__table__.c.json2.nullable)
-            else:
-                is_true(A.__table__.c.json2.nullable)
+    @testing.variation("in_map", ["yes", "no", "value"])
+    @testing.variation("lookup", ["A", "B", "value"])
+    def test_recursive_pep695_cases(
+        self, decl_base, in_map: Variation, lookup: Variation
+    ):
+        global A, B
+        A = TypingTypeAliasType("A", Union[int, float])
+        B = TypingTypeAliasType("B", A)
 
-        if option.not_optional:
-            is_false(A.__table__.c.json.nullable)
-        else:
-            is_true(A.__table__.c.json.nullable)
+        if in_map.yes:
+            decl_base.registry.update_type_annotation_map({A: Numeric(10, 5)})
+        elif in_map.value:
+            decl_base.registry.update_type_annotation_map(
+                {A.__value__: Numeric(10, 5)}
+            )
 
-    @testing.variation("optional", [True, False])
-    @testing.variation("provide_type", [True, False])
-    @testing.variation("add_to_type_map", [True, False])
-    def test_recursive_type(
-        self, decl_base, optional, provide_type, add_to_type_map
-    ):
-        """test #9553"""
+        def declare():
+            class MyClass(decl_base):
+                __tablename__ = "my_table"
+                id: Mapped[int] = mapped_column(primary_key=True)
 
-        global T
+                if lookup.A:
+                    data: Mapped[A]
+                elif lookup.B:
+                    data: Mapped[B]
+                elif lookup.value:
+                    data: Mapped[Union[int, float]]
+                else:
+                    lookup.fail()
 
-        T = Dict[str, Optional["T"]]
+            return MyClass
 
-        if not provide_type and not add_to_type_map:
+        if in_map.value and lookup.B:
+            with expect_deprecated(
+                "Matching to pep-695 type 'A' in a recursive fashion"
+            ):
+                MyClass = declare()
+                eq_(MyClass.data.expression.type.precision, 10)
+        elif in_map.no or (in_map.yes and lookup.value):
             with expect_raises_message(
-                sa_exc.ArgumentError,
-                r"Could not locate SQLAlchemy.*" r".*ForwardRef\('T'\).*",
+                orm_exc.MappedAnnotationError,
+                "Could not locate SQLAlchemy Core type when resolving "
+                "for Python type indicated by",
             ):
+                declare()
+        else:
+            MyClass = declare()
+            eq_(MyClass.data.expression.type.precision, 10)
 
-                class TypeTest(decl_base):
-                    __tablename__ = "my_table"
+    @testing.variation(
+        "dict_key", ["typing", ("plain", testing.requires.python310)]
+    )
+    def test_type_dont_mis_resolve_on_non_generic(self, dict_key):
+        """test for #8859.
 
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    if optional:
-                        type_test: Mapped[Optional[T]] = mapped_column()
-                    else:
-                        type_test: Mapped[T] = mapped_column()
+        For a specific generic type with arguments, don't do any MRO
+        lookup.
 
-            return
+        """
 
-        else:
-            if add_to_type_map:
-                decl_base.registry.update_type_annotation_map({T: JSON()})
+        Base = declarative_base(
+            type_annotation_map={
+                dict: String,
+            }
+        )
 
-            class TypeTest(decl_base):
-                __tablename__ = "my_table"
+        with expect_raises_message(
+            sa_exc.ArgumentError, "Could not locate SQLAlchemy Core type"
+        ):
+
+            class MyClass(Base):
+                __tablename__ = "mytable"
 
                 id: Mapped[int] = mapped_column(primary_key=True)
 
-                if add_to_type_map:
-                    if optional:
-                        type_test: Mapped[Optional[T]] = mapped_column()
-                    else:
-                        type_test: Mapped[T] = mapped_column()
-                else:
-                    if optional:
-                        type_test: Mapped[Optional[T]] = mapped_column(JSON())
-                    else:
-                        type_test: Mapped[T] = mapped_column(JSON())
+                if dict_key.plain:
+                    data: Mapped[dict[str, str]]
+                elif dict_key.typing:
+                    data: Mapped[Dict[str, str]]
 
-        if optional:
-            is_(TypeTest.__table__.c.type_test.nullable, True)
-        else:
-            is_(TypeTest.__table__.c.type_test.nullable, False)
+    def test_type_secondary_resolution(self):
+        class MyString(String):
+            def _resolve_for_python_type(
+                self, python_type, matched_type, matched_on_flattened
+            ):
+                return String(length=42)
 
-        self.assert_compile(
-            select(TypeTest),
-            "SELECT my_table.id, my_table.type_test FROM my_table",
-        )
+        Base = declarative_base(type_annotation_map={str: MyString})
 
-    def test_missing_mapped_lhs(self, decl_base):
-        with expect_annotation_syntax_error("User.name"):
+        class MyClass(Base):
+            __tablename__ = "mytable"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str]
+
+        is_true(isinstance(MyClass.__table__.c.data.type, String))
+        eq_(MyClass.__table__.c.data.type.length, 42)
+
+    def test_construct_lhs_type_missing(self, decl_base):
+        global MyClass
+
+        class MyClass:
+            pass
+
+        with expect_raises_message(
+            orm_exc.MappedAnnotationError,
+            "Could not locate SQLAlchemy Core type when resolving for Python "
+            r"type indicated by '.*class .*MyClass.*' inside the "
+            r"Mapped\[\] annotation for the 'data' attribute; the type "
+            "object is not resolvable by the registry",
+        ):
 
             class User(decl_base):
                 __tablename__ = "users"
 
                 id: Mapped[int] = mapped_column(primary_key=True)
-                name: str = mapped_column()  # type: ignore
+                data: Mapped[MyClass] = mapped_column()
 
-    def test_construct_lhs_separate_name(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+    @testing.variation(
+        "argtype",
+        [
+            "type",
+            ("column", testing.requires.python310),
+            ("mapped_column", testing.requires.python310),
+            "column_class",
+            "ref_to_type",
+            ("ref_to_column", testing.requires.python310),
+        ],
+    )
+    def test_construct_lhs_sqlalchemy_type(self, decl_base, argtype):
+        """test for #12329.
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            name: Mapped[str] = mapped_column()
-            data: Mapped[Optional[str]] = mapped_column("the_data")
+        of note here are all the different messages we have for when the
+        wrong thing is put into Mapped[], and in fact in #12329 we added
+        another one.
 
-        self.assert_compile(
-            select(User.data), "SELECT users.the_data FROM users"
-        )
-        is_true(User.__table__.c.the_data.nullable)
+        This is a lot of different messages, but at the same time they
+        occur at different places in the interpretation of types.   If
+        we were to centralize all these messages, we'd still likely end up
+        doing distinct messages for each scenario, so instead we added
+        a new ArgumentError subclass MappedAnnotationError that provides
+        some commonality to all of these cases.
 
-    def test_construct_works_in_expr(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
 
-            id: Mapped[int] = mapped_column(primary_key=True)
+        """
+        expect_future_annotations = "annotations" in globals()
+
+        if argtype.type:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # properties.py -> _init_column_for_annotation, type is
+                # a SQL type
+                "The type provided inside the 'data' attribute Mapped "
+                "annotation is the SQLAlchemy type .*BigInteger.*. Expected "
+                "a Python type instead",
+            ):
+
+                class User(decl_base):
+                    __tablename__ = "users"
+
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[BigInteger] = mapped_column()
+
+        elif argtype.column:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # util.py -> _extract_mapped_subtype
+                (
+                    re.escape(
+                        "Could not interpret annotation "
+                        "Mapped[Column('q', BigInteger)]."
+                    )
+                    if expect_future_annotations
+                    # properties.py -> _init_column_for_annotation, object is
+                    # not a SQL type or a python type, it's just some object
+                    else re.escape(
+                        "The object provided inside the 'data' attribute "
+                        "Mapped annotation is not a Python type, it's the "
+                        "object Column('q', BigInteger(), table=None). "
+                        "Expected a Python type."
+                    )
+                ),
+            ):
+
+                class User(decl_base):
+                    __tablename__ = "users"
+
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[Column("q", BigInteger)] = (  # noqa: F821
+                        mapped_column()
+                    )
 
-        class Address(decl_base):
-            __tablename__ = "addresses"
+        elif argtype.mapped_column:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # properties.py -> _init_column_for_annotation, object is
+                # not a SQL type or a python type, it's just some object
+                # interestingly, this raises at the same point for both
+                # future annotations mode and legacy annotations mode
+                r"The object provided inside the 'data' attribute "
+                "Mapped annotation is not a Python type, it's the object "
+                r"\<sqlalchemy.orm.properties.MappedColumn.*\>. "
+                "Expected a Python type.",
+            ):
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
+                class User(decl_base):
+                    __tablename__ = "users"
 
-            user = relationship(User, primaryjoin=user_id == User.id)
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    big_integer: Mapped[int] = mapped_column()
+                    data: Mapped[big_integer] = mapped_column()
 
-        self.assert_compile(
-            select(Address.user_id, User.id).join(Address.user),
-            "SELECT addresses.user_id, users.id FROM addresses "
-            "JOIN users ON addresses.user_id = users.id",
-        )
+        elif argtype.column_class:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # properties.py -> _init_column_for_annotation, type is not
+                # a SQL type
+                "Could not locate SQLAlchemy Core type when resolving for "
+                "Python type indicated by "
+                r"'.*class .*.Column.*' inside the "
+                r"Mapped\[\] annotation for the 'data' attribute; the "
+                "type object is not resolvable by the registry",
+            ):
 
-    def test_construct_works_as_polymorphic_on(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+                class User(decl_base):
+                    __tablename__ = "users"
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            type: Mapped[str] = mapped_column()
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[Column] = mapped_column()
 
-            __mapper_args__ = {"polymorphic_on": type}
+        elif argtype.ref_to_type:
+            mytype = BigInteger
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                (
+                    # decl_base.py -> _exract_mappable_attributes
+                    re.escape(
+                        "Could not resolve all types within mapped "
+                        'annotation: "Mapped[mytype]"'
+                    )
+                    if expect_future_annotations
+                    # properties.py -> _init_column_for_annotation, type is
+                    # a SQL type
+                    else re.escape(
+                        "The type provided inside the 'data' attribute Mapped "
+                        "annotation is the SQLAlchemy type "
+                        "<class 'sqlalchemy.sql.sqltypes.BigInteger'>. "
+                        "Expected a Python type instead"
+                    )
+                ),
+            ):
 
-        decl_base.registry.configure()
-        is_(User.__table__.c.type, User.__mapper__.polymorphic_on)
+                class User(decl_base):
+                    __tablename__ = "users"
 
-    def test_construct_works_as_version_id_col(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[mytype] = mapped_column()
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            version_id: Mapped[int] = mapped_column()
+        elif argtype.ref_to_column:
+            mycol = Column("q", BigInteger)
 
-            __mapper_args__ = {"version_id_col": version_id}
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # decl_base.py -> _exract_mappable_attributes
+                (
+                    re.escape(
+                        "Could not resolve all types within mapped "
+                        'annotation: "Mapped[mycol]"'
+                    )
+                    if expect_future_annotations
+                    else
+                    # properties.py -> _init_column_for_annotation, object is
+                    # not a SQL type or a python type, it's just some object
+                    re.escape(
+                        "The object provided inside the 'data' attribute "
+                        "Mapped "
+                        "annotation is not a Python type, it's the object "
+                        "Column('q', BigInteger(), table=None). "
+                        "Expected a Python type."
+                    )
+                ),
+            ):
 
-        decl_base.registry.configure()
-        is_(User.__table__.c.version_id, User.__mapper__.version_id_col)
+                class User(decl_base):
+                    __tablename__ = "users"
 
-    def test_construct_works_in_deferred(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[mycol] = mapped_column()
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str] = deferred(mapped_column())
+        else:
+            argtype.fail()
 
-        self.assert_compile(select(User), "SELECT users.id FROM users")
-        self.assert_compile(
-            select(User).options(undefer(User.data)),
-            "SELECT users.id, users.data FROM users",
+    def test_plain_typealias_as_typemap_keys(
+        self, decl_base: Type[DeclarativeBase]
+    ):
+        decl_base.registry.update_type_annotation_map(
+            {_UnionTypeAlias: JSON, _StrTypeAlias: String(30)}
         )
 
-    def test_deferred_kw(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
-
+        class Test(decl_base):
+            __tablename__ = "test"
             id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str] = mapped_column(deferred=True)
+            data: Mapped[_StrTypeAlias]
+            structure: Mapped[_UnionTypeAlias]
 
-        self.assert_compile(select(User), "SELECT users.id FROM users")
-        self.assert_compile(
-            select(User).options(undefer(User.data)),
-            "SELECT users.id, users.data FROM users",
-        )
+        eq_(Test.__table__.c.data.type.length, 30)
+        is_(Test.__table__.c.structure.type._type_affinity, JSON)
 
-    @testing.combinations(
-        (str, types.String),
-        (Decimal, types.Numeric),
-        (float, types.Float),
-        (datetime.datetime, types.DateTime),
-        (uuid.UUID, types.Uuid),
-        argnames="pytype_arg,sqltype",
+    @testing.variation(
+        "option",
+        [
+            "plain",
+            "union",
+            "union_604",
+            "union_null",
+            "union_null_604",
+            "optional",
+            "optional_union",
+            "optional_union_604",
+            "union_newtype",
+            "union_null_newtype",
+            "union_695",
+            "union_null_695",
+        ],
     )
-    def test_datatype_lookups(self, decl_base, pytype_arg, sqltype):
-        global pytype
-        pytype = pytype_arg
+    @testing.variation("in_map", ["yes", "no", "value"])
+    @testing.requires.python312
+    def test_pep695_behavior(self, decl_base, in_map, option):
+        """Issue #11955; later issue #12829"""
 
-        class MyClass(decl_base):
-            __tablename__ = "mytable"
-            id: Mapped[int] = mapped_column(primary_key=True)
+        global tat
 
-            data: Mapped[pytype]
+        if option.plain:
+            tat = TypeAliasType("tat", str)
+        elif option.union:
+            tat = TypeAliasType("tat", Union[str, int])
+        elif option.union_604:
+            tat = TypeAliasType("tat", str | int)
+        elif option.union_null:
+            tat = TypeAliasType("tat", Union[str, int, None])
+        elif option.union_null_604:
+            tat = TypeAliasType("tat", str | int | None)
+        elif option.optional:
+            tat = TypeAliasType("tat", Optional[str])
+        elif option.optional_union:
+            tat = TypeAliasType("tat", Optional[Union[str, int]])
+        elif option.optional_union_604:
+            tat = TypeAliasType("tat", Optional[str | int])
+        elif option.union_newtype:
+            # this seems to be illegal for typing but "works"
+            tat = NewType("tat", Union[str, int])
+        elif option.union_null_newtype:
+            # this seems to be illegal for typing but "works"
+            tat = NewType("tat", Union[str, int, None])
+        elif option.union_695:
+            tat = TypeAliasType("tat", str | int)
+        elif option.union_null_695:
+            tat = TypeAliasType("tat", str | int | None)
+        else:
+            option.fail()
 
-        assert isinstance(MyClass.__table__.c.data.type, sqltype)
+        is_newtype = "newtype" in option.name
+        if in_map.yes:
+            decl_base.registry.update_type_annotation_map({tat: String(99)})
+        elif in_map.value and not is_newtype:
+            decl_base.registry.update_type_annotation_map(
+                {tat.__value__: String(99)}
+            )
 
-    def test_dont_ignore_unresolvable(self, decl_base):
-        """test #8888"""
+        def declare():
+            class Test(decl_base):
+                __tablename__ = "test"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[tat]
 
-        with expect_raises_message(
-            sa_exc.ArgumentError,
-            r"Could not resolve all types within mapped annotation: "
-            r"\".*Mapped\[.*fake.*\]\".  Ensure all types are written "
-            r"correctly and are imported within the module in use.",
-        ):
+            return Test.__table__.c.data
+
+        if in_map.yes or (in_map.value and not is_newtype):
+            col = declare()
+            # String(99) inside the type_map
+            is_true(isinstance(col.type, String))
+            eq_(col.type.length, 99)
+            nullable = "null" in option.name or "optional" in option.name
+            eq_(col.nullable, nullable)
+        elif option.plain or option.optional:
+            col = declare()
+            # plain string from default lookup
+            is_true(isinstance(col.type, String))
+            eq_(col.type.length, None)
+            nullable = "null" in option.name or "optional" in option.name
+            eq_(col.nullable, nullable)
+        else:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                r"Could not locate SQLAlchemy Core type when resolving "
+                r"for Python type "
+                r"indicated by '.*tat' inside the Mapped\[\] "
+                r"annotation for the 'data' attribute;",
+            ):
+                declare()
+            return
+
+    @testing.variation(
+        "type_",
+        [
+            "str_extension",
+            "str_typing",
+            "generic_extension",
+            "generic_typing",
+            "generic_typed_extension",
+            "generic_typed_typing",
+        ],
+    )
+    @testing.requires.python312
+    def test_pep695_typealias_as_typemap_keys(
+        self, decl_base: Type[DeclarativeBase], type_
+    ):
+        """test #10807, #12829"""
+
+        decl_base.registry.update_type_annotation_map(
+            {
+                _UnionPep695: JSON,
+                _StrPep695: String(30),
+                _TypingStrPep695: String(30),
+                _GenericPep695: String(30),
+                _TypingGenericPep695: String(30),
+                _GenericPep695Typed: String(30),
+                _TypingGenericPep695Typed: String(30),
+            }
+        )
 
-            class A(decl_base):
-                __tablename__ = "a"
+        class Test(decl_base):
+            __tablename__ = "test"
+            id: Mapped[int] = mapped_column(primary_key=True)
+            if type_.str_extension:
+                data: Mapped[_StrPep695]
+            elif type_.str_typing:
+                data: Mapped[_TypingStrPep695]
+            elif type_.generic_extension:
+                data: Mapped[_GenericPep695]
+            elif type_.generic_typing:
+                data: Mapped[_TypingGenericPep695]
+            elif type_.generic_typed_extension:
+                data: Mapped[_GenericPep695Typed]
+            elif type_.generic_typed_typing:
+                data: Mapped[_TypingGenericPep695Typed]
+            else:
+                type_.fail()
+            structure: Mapped[_UnionPep695]
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped["fake"]  # noqa
+        eq_(Test.__table__.c.data.type._type_affinity, String)
+        eq_(Test.__table__.c.data.type.length, 30)
+        is_(Test.__table__.c.structure.type._type_affinity, JSON)
 
-    def test_type_dont_mis_resolve_on_superclass(self):
-        """test for #8859.
+    def test_pep484_newtypes_as_typemap_keys(
+        self, decl_base: Type[DeclarativeBase]
+    ):
+        global str50, str30, str3050
 
-        For subclasses of a type that's in the map, don't resolve this
-        by default, even though we do a search through __mro__.
+        str50 = NewType("str50", str)
+        str30 = NewType("str30", str)
+        str3050 = NewType("str30", str50)
 
-        """
-        global int_sub
+        decl_base.registry.update_type_annotation_map(
+            {str50: String(50), str30: String(30), str3050: String(150)}
+        )
 
-        class int_sub(int):
-            pass
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
 
-        Base = declarative_base(
-            type_annotation_map={
-                int: Integer,
-            }
-        )
+            id: Mapped[str50] = mapped_column(primary_key=True)
+            data_one: Mapped[str30]
+            data_two: Mapped[str50]
+            data_three: Mapped[Optional[str30]]
+            data_four: Mapped[str3050]
 
-        with expect_raises_message(
-            orm_exc.MappedAnnotationError,
-            "Could not locate SQLAlchemy Core type",
-        ):
+        eq_(MyClass.__table__.c.data_one.type.length, 30)
+        is_false(MyClass.__table__.c.data_one.nullable)
 
-            class MyClass(Base):
-                __tablename__ = "mytable"
+        eq_(MyClass.__table__.c.data_two.type.length, 50)
+        is_false(MyClass.__table__.c.data_two.nullable)
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped[int_sub]
+        eq_(MyClass.__table__.c.data_three.type.length, 30)
+        is_true(MyClass.__table__.c.data_three.nullable)
 
-    @testing.variation(
-        "dict_key", ["typing", ("plain", testing.requires.python310)]
-    )
-    def test_type_dont_mis_resolve_on_non_generic(self, dict_key):
-        """test for #8859.
+        eq_(MyClass.__table__.c.data_four.type.length, 150)
+        is_false(MyClass.__table__.c.data_four.nullable)
 
-        For a specific generic type with arguments, don't do any MRO
-        lookup.
+    def test_newtype_missing_from_map(self, decl_base):
+        global str50
 
-        """
+        str50 = NewType("str50", str)
 
-        Base = declarative_base(
-            type_annotation_map={
-                dict: String,
-            }
-        )
+        if compat.py310:
+            text = ".*str50"
+        else:
+            # NewTypes before 3.10 had a very bad repr
+            # <function NewType.<locals>.new_type at 0x...>
+            text = ".*NewType.*"
 
-        with expect_raises_message(
-            sa_exc.ArgumentError, "Could not locate SQLAlchemy Core type"
+        with expect_deprecated(
+            f"Matching the provided NewType '{text}' on its "
+            "resolved value without matching it in the "
+            "type_annotation_map is deprecated; add this type to the "
+            "type_annotation_map to allow it to match explicitly.",
         ):
 
-            class MyClass(Base):
-                __tablename__ = "mytable"
+            class MyClass(decl_base):
+                __tablename__ = "my_table"
 
                 id: Mapped[int] = mapped_column(primary_key=True)
+                data_one: Mapped[str50]
 
-                if dict_key.plain:
-                    data: Mapped[dict[str, str]]
-                elif dict_key.typing:
-                    data: Mapped[Dict[str, str]]
-
-    def test_type_secondary_resolution(self):
-        class MyString(String):
-            def _resolve_for_python_type(
-                self, python_type, matched_type, matched_on_flattened
-            ):
-                return String(length=42)
-
-        Base = declarative_base(type_annotation_map={str: MyString})
-
-        class MyClass(Base):
-            __tablename__ = "mytable"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str]
-
-        is_true(isinstance(MyClass.__table__.c.data.type, String))
-        eq_(MyClass.__table__.c.data.type.length, 42)
+        is_true(isinstance(MyClass.data_one.type, String))
 
 
-class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL):
+class ResolveToEnumTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = "default"
 
     @testing.variation("use_explicit_name", [True, False])
@@ -3048,6 +3091,117 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL):
 
             is_true(isinstance(Foo.__table__.c.status.type, JSON))
 
+    @testing.variation(
+        "type_",
+        [
+            "literal",
+            "literal_typing",
+            "recursive",
+            "not_literal",
+            "not_literal_typing",
+            "generic",
+            "generic_typing",
+            "generic_typed",
+            "generic_typed_typing",
+        ],
+    )
+    @testing.combinations(True, False, argnames="in_map")
+    @testing.requires.python312
+    def test_pep695_literal_defaults_to_enum(self, decl_base, type_, in_map):
+        """test #11305."""
+
+        def declare():
+            class Foo(decl_base):
+                __tablename__ = "footable"
+
+                id: Mapped[int] = mapped_column(primary_key=True)
+                if type_.recursive:
+                    status: Mapped[_RecursiveLiteral695]  # noqa: F821
+                elif type_.literal:
+                    status: Mapped[_Literal695]  # noqa: F821
+                elif type_.literal_typing:
+                    status: Mapped[_TypingLiteral695]  # noqa: F821
+                elif type_.not_literal:
+                    status: Mapped[_StrPep695]  # noqa: F821
+                elif type_.not_literal_typing:
+                    status: Mapped[_TypingStrPep695]  # noqa: F821
+                elif type_.generic:
+                    status: Mapped[_GenericPep695]  # noqa: F821
+                elif type_.generic_typing:
+                    status: Mapped[_TypingGenericPep695]  # noqa: F821
+                elif type_.generic_typed:
+                    status: Mapped[_GenericPep695Typed]  # noqa: F821
+                elif type_.generic_typed_typing:
+                    status: Mapped[_TypingGenericPep695Typed]  # noqa: F821
+                else:
+                    type_.fail()
+
+            return Foo
+
+        if in_map:
+            decl_base.registry.update_type_annotation_map(
+                {
+                    _Literal695: Enum(enum.Enum),  # noqa: F821
+                    _TypingLiteral695: Enum(enum.Enum),  # noqa: F821
+                    _RecursiveLiteral695: Enum(enum.Enum),  # noqa: F821
+                    _StrPep695: Enum(enum.Enum),  # noqa: F821
+                    _TypingStrPep695: Enum(enum.Enum),  # noqa: F821
+                    _GenericPep695: Enum(enum.Enum),  # noqa: F821
+                    _TypingGenericPep695: Enum(enum.Enum),  # noqa: F821
+                    _GenericPep695Typed: Enum(enum.Enum),  # noqa: F821
+                    _TypingGenericPep695Typed: Enum(enum.Enum),  # noqa: F821
+                }
+            )
+            if type_.recursive:
+                with expect_deprecated(
+                    "Mapping recursive TypeAliasType '.+' that resolve to "
+                    "literal to generate an Enum is deprecated. SQLAlchemy "
+                    "2.1 will not support this use case. Please avoid using "
+                    "recursing TypeAliasType",
+                ):
+                    Foo = declare()
+            elif type_.literal or type_.literal_typing:
+                Foo = declare()
+            else:
+                with expect_raises_message(
+                    exc.ArgumentError,
+                    "Can't associate TypeAliasType '.+' to an Enum "
+                    "since it's not a direct alias of a Literal. Only "
+                    "aliases in this form `type my_alias = Literal.'a', "
+                    "'b'.` are supported when generating Enums.",
+                ):
+                    declare()
+        elif type_.literal or type_.literal_typing:
+            Foo = declare()
+            col = Foo.__table__.c.status
+            is_true(isinstance(col.type, Enum))
+            eq_(col.type.enums, ["to-do", "in-progress", "done"])
+            is_(col.type.native_enum, False)
+        elif type_.not_literal or type_.not_literal_typing:
+            Foo = declare()
+            col = Foo.__table__.c.status
+            is_true(isinstance(col.type, String))
+        elif type_.recursive:
+            with expect_deprecated(
+                "Matching to pep-695 type '_Literal695' in a "
+                "recursive fashion "
+                "without the recursed type being present in the "
+                "type_annotation_map is deprecated; add this type or its "
+                "recursed value to the type_annotation_map to allow it to "
+                "match explicitly."
+            ):
+                Foo = declare()
+        else:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                r"Could not locate SQLAlchemy Core type when resolving "
+                r"for Python type "
+                r"indicated by '.+' inside the Mapped\[\] "
+                r"annotation for the 'status' attribute",
+            ):
+                declare()
+            return
+
 
 class MixinTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = "default"
@@ -3956,32 +4110,6 @@ class CompositeTest(fixtures.TestBase, testing.AssertsCompiledSQL):
                     mapped_column(), mapped_column(), mapped_column("zip")
                 )
 
-    def test_extract_from_pep593(self, decl_base):
-        global Address
-
-        @dataclasses.dataclass
-        class Address:
-            street: str
-            state: str
-            zip_: str
-
-        class User(decl_base):
-            __tablename__ = "user"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
-            name: Mapped[str] = mapped_column()
-
-            address: Mapped[Annotated[Address, "foo"]] = composite(
-                mapped_column(), mapped_column(), mapped_column("zip")
-            )
-
-        self.assert_compile(
-            select(User),
-            'SELECT "user".id, "user".name, "user".street, '
-            '"user".state, "user".zip FROM "user"',
-            dialect="default",
-        )
-
     def test_cls_not_composite_compliant(self, decl_base):
         global Address
 
index 78863bca81c1eb1e6eef603f1ad4bdd0823f5ab0..003872a809004139430d46a0e818b2392e7d8a63 100644 (file)
@@ -159,6 +159,19 @@ _TypingLiteral695 = TypingTypeAliasType(
 )
 _RecursiveLiteral695 = TypeAliasType("_RecursiveLiteral695", _Literal695)
 
+_GenericPep593TypeAlias = Annotated[TV, mapped_column(info={"hi": "there"})]
+
+_GenericPep593Pep695 = TypingTypeAliasType(
+    "_GenericPep593Pep695",
+    Annotated[TV, mapped_column(info={"hi": "there"})],
+    type_params=(TV,),
+)
+
+_RecursivePep695Pep593 = TypingTypeAliasType(
+    "_RecursivePep695Pep593",
+    Annotated[_TypingStrPep695, mapped_column(info={"hi": "there"})],
+)
+
 
 def expect_annotation_syntax_error(name):
     return expect_raises_message(
@@ -323,31 +336,6 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
 
         assert Child.__mapper__.attrs.parent.strategy.use_get
 
-    @testing.combinations(
-        (BIGINT(),),
-        (BIGINT,),
-        (Integer().with_variant(BIGINT, "default")),
-        (Integer().with_variant(BIGINT(), "default")),
-        (BIGINT().with_variant(String(), "some_other_dialect")),
-    )
-    def test_type_map_varieties(self, typ):
-        Base = declarative_base(type_annotation_map={int: typ})
-
-        class MyClass(Base):
-            __tablename__ = "mytable"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
-            x: Mapped[int]
-            y: Mapped[int] = mapped_column()
-            z: Mapped[int] = mapped_column(typ)
-
-        self.assert_compile(
-            CreateTable(MyClass.__table__),
-            "CREATE TABLE mytable (id BIGINT NOT NULL, "
-            "x BIGINT NOT NULL, y BIGINT NOT NULL, z BIGINT NOT NULL, "
-            "PRIMARY KEY (id))",
-        )
-
     def test_required_no_arg(self, decl_base):
         with expect_raises_message(
             sa_exc.ArgumentError,
@@ -603,198 +591,6 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         is_true(User.__table__.c.data.nullable)
         assert isinstance(User.__table__.c.created_at.type, DateTime)
 
-    def test_construct_lhs_type_missing(self, decl_base):
-        # anno only: global MyClass
-
-        class MyClass:
-            pass
-
-        with expect_raises_message(
-            sa_exc.ArgumentError,
-            "Could not locate SQLAlchemy Core type for Python type "
-            ".*MyClass.* inside the 'data' attribute Mapped annotation",
-        ):
-
-            class User(decl_base):
-                __tablename__ = "users"
-
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped[MyClass] = mapped_column()
-
-    @testing.variation(
-        "argtype",
-        [
-            "type",
-            ("column", testing.requires.python310),
-            ("mapped_column", testing.requires.python310),
-            "column_class",
-            "ref_to_type",
-            ("ref_to_column", testing.requires.python310),
-        ],
-    )
-    def test_construct_lhs_sqlalchemy_type(self, decl_base, argtype):
-        """test for #12329.
-
-        of note here are all the different messages we have for when the
-        wrong thing is put into Mapped[], and in fact in #12329 we added
-        another one.
-
-        This is a lot of different messages, but at the same time they
-        occur at different places in the interpretation of types.   If
-        we were to centralize all these messages, we'd still likely end up
-        doing distinct messages for each scenario, so instead we added
-        a new ArgumentError subclass MappedAnnotationError that provides
-        some commonality to all of these cases.
-
-
-        """
-        expect_future_annotations = "annotations" in globals()
-
-        if argtype.type:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # properties.py -> _init_column_for_annotation, type is
-                # a SQL type
-                "The type provided inside the 'data' attribute Mapped "
-                "annotation is the SQLAlchemy type .*BigInteger.*. Expected "
-                "a Python type instead",
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[BigInteger] = mapped_column()
-
-        elif argtype.column:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # util.py -> _extract_mapped_subtype
-                (
-                    re.escape(
-                        "Could not interpret annotation "
-                        "Mapped[Column('q', BigInteger)]."
-                    )
-                    if expect_future_annotations
-                    # properties.py -> _init_column_for_annotation, object is
-                    # not a SQL type or a python type, it's just some object
-                    else re.escape(
-                        "The object provided inside the 'data' attribute "
-                        "Mapped annotation is not a Python type, it's the "
-                        "object Column('q', BigInteger(), table=None). "
-                        "Expected a Python type."
-                    )
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[Column("q", BigInteger)] = (  # noqa: F821
-                        mapped_column()
-                    )
-
-        elif argtype.mapped_column:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # properties.py -> _init_column_for_annotation, object is
-                # not a SQL type or a python type, it's just some object
-                # interestingly, this raises at the same point for both
-                # future annotations mode and legacy annotations mode
-                r"The object provided inside the 'data' attribute "
-                "Mapped annotation is not a Python type, it's the object "
-                r"\<sqlalchemy.orm.properties.MappedColumn.*\>. "
-                "Expected a Python type.",
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    big_integer: Mapped[int] = mapped_column()
-                    data: Mapped[big_integer] = mapped_column()
-
-        elif argtype.column_class:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # properties.py -> _init_column_for_annotation, type is not
-                # a SQL type
-                re.escape(
-                    "Could not locate SQLAlchemy Core type for Python type "
-                    "<class 'sqlalchemy.sql.schema.Column'> inside the "
-                    "'data' attribute Mapped annotation"
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[Column] = mapped_column()
-
-        elif argtype.ref_to_type:
-            mytype = BigInteger
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                (
-                    # decl_base.py -> _exract_mappable_attributes
-                    re.escape(
-                        "Could not resolve all types within mapped "
-                        'annotation: "Mapped[mytype]"'
-                    )
-                    if expect_future_annotations
-                    # properties.py -> _init_column_for_annotation, type is
-                    # a SQL type
-                    else re.escape(
-                        "The type provided inside the 'data' attribute Mapped "
-                        "annotation is the SQLAlchemy type "
-                        "<class 'sqlalchemy.sql.sqltypes.BigInteger'>. "
-                        "Expected a Python type instead"
-                    )
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[mytype] = mapped_column()
-
-        elif argtype.ref_to_column:
-            mycol = Column("q", BigInteger)
-
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                # decl_base.py -> _exract_mappable_attributes
-                (
-                    re.escape(
-                        "Could not resolve all types within mapped "
-                        'annotation: "Mapped[mycol]"'
-                    )
-                    if expect_future_annotations
-                    else
-                    # properties.py -> _init_column_for_annotation, object is
-                    # not a SQL type or a python type, it's just some object
-                    re.escape(
-                        "The object provided inside the 'data' attribute "
-                        "Mapped "
-                        "annotation is not a Python type, it's the object "
-                        "Column('q', BigInteger(), table=None). "
-                        "Expected a Python type."
-                    )
-                ),
-            ):
-
-                class User(decl_base):
-                    __tablename__ = "users"
-
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    data: Mapped[mycol] = mapped_column()
-
-        else:
-            argtype.fail()
-
     def test_construct_rhs_type_override_lhs(self, decl_base):
         class Element(decl_base):
             __tablename__ = "element"
@@ -966,668 +762,752 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
             id: Mapped["int"] = mapped_column(primary_key=True)
             data_one: Mapped["str"]
 
-    def test_pep593_types_as_typemap_keys(
-        self, decl_base: Type[DeclarativeBase]
-    ):
-        """neat!!!"""
-        # anno only: global str50, str30, opt_str50, opt_str30
+    @testing.requires.python38
+    def test_typing_literal_identity(self, decl_base):
+        """See issue #11820"""
 
-        str50 = Annotated[str, 50]
-        str30 = Annotated[str, 30]
-        opt_str50 = Optional[str50]
-        opt_str30 = Optional[str30]
+        class Foo(decl_base):
+            __tablename__ = "footable"
 
-        decl_base.registry.update_type_annotation_map(
-            {str50: String(50), str30: String(30)}
-        )
+            id: Mapped[int] = mapped_column(primary_key=True)
+            t: Mapped[_TypingLiteral]
+            te: Mapped[_TypingExtensionsLiteral]
 
-        class MyClass(decl_base):
-            __tablename__ = "my_table"
+        for col in (Foo.__table__.c.t, Foo.__table__.c.te):
+            is_true(isinstance(col.type, Enum))
+            eq_(col.type.enums, ["a", "b"])
+            is_(col.type.native_enum, False)
 
-            id: Mapped[str50] = mapped_column(primary_key=True)
-            data_one: Mapped[str30]
-            data_two: Mapped[opt_str30]
-            data_three: Mapped[str50]
-            data_four: Mapped[opt_str50]
-            data_five: Mapped[str]
-            data_six: Mapped[Optional[str]]
+    @testing.requires.python310
+    def test_we_got_all_attrs_test_annotated(self):
+        argnames = _py_inspect.getfullargspec(mapped_column)
+        assert _annotated_names_tested.issuperset(argnames.kwonlyargs), (
+            f"annotated attributes were not tested: "
+            f"{set(argnames.kwonlyargs).difference(_annotated_names_tested)}"
+        )
 
-        eq_(MyClass.__table__.c.data_one.type.length, 30)
-        is_false(MyClass.__table__.c.data_one.nullable)
-        eq_(MyClass.__table__.c.data_two.type.length, 30)
-        is_true(MyClass.__table__.c.data_two.nullable)
-        eq_(MyClass.__table__.c.data_three.type.length, 50)
-
-    def test_plain_typealias_as_typemap_keys(
-        self, decl_base: Type[DeclarativeBase]
+    @annotated_name_test_cases(
+        ("sort_order", 100, lambda sort_order: sort_order == 100),
+        ("nullable", False, lambda column: column.nullable is False),
+        (
+            "active_history",
+            True,
+            lambda column_property: column_property.active_history is True,
+        ),
+        (
+            "deferred",
+            True,
+            lambda column_property: column_property.deferred is True,
+        ),
+        (
+            "deferred",
+            _NoArg.NO_ARG,
+            lambda column_property: column_property is None,
+        ),
+        (
+            "deferred_group",
+            "mygroup",
+            lambda column_property: column_property.deferred is True
+            and column_property.group == "mygroup",
+        ),
+        (
+            "deferred_raiseload",
+            True,
+            lambda column_property: column_property.deferred is True
+            and column_property.raiseload is True,
+        ),
+        (
+            "server_default",
+            "25",
+            lambda column: column.server_default.arg == "25",
+        ),
+        (
+            "server_onupdate",
+            "25",
+            lambda column: column.server_onupdate.arg == "25",
+        ),
+        (
+            "default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "insert_default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "onupdate",
+            25,
+            lambda column: column.onupdate.arg == 25,
+        ),
+        ("doc", "some doc", lambda column: column.doc == "some doc"),
+        (
+            "comment",
+            "some comment",
+            lambda column: column.comment == "some comment",
+        ),
+        ("index", True, lambda column: column.index is True),
+        ("index", _NoArg.NO_ARG, lambda column: column.index is None),
+        ("index", False, lambda column: column.index is False),
+        ("unique", True, lambda column: column.unique is True),
+        ("unique", False, lambda column: column.unique is False),
+        ("autoincrement", True, lambda column: column.autoincrement is True),
+        ("system", True, lambda column: column.system is True),
+        ("primary_key", True, lambda column: column.primary_key is True),
+        ("type_", BIGINT, lambda column: isinstance(column.type, BIGINT)),
+        ("info", {"foo": "bar"}, lambda column: column.info == {"foo": "bar"}),
+        (
+            "use_existing_column",
+            True,
+            lambda mc: mc._use_existing_column is True,
+        ),
+        (
+            "quote",
+            True,
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "key",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "name",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "kw_only",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'kw_only' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "compare",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'compare' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "default_factory",
+            lambda: 25,
+            exc.SADeprecationWarning(
+                "Argument 'default_factory' is a dataclass argument "
+            ),
+        ),
+        (
+            "repr",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'repr' is a dataclass argument "
+            ),
+        ),
+        (
+            "init",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'init' is a dataclass argument"
+            ),
+        ),
+        (
+            "hash",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'hash' is a dataclass argument"
+            ),
+        ),
+        (
+            "dataclass_metadata",
+            {},
+            exc.SADeprecationWarning(
+                "Argument 'dataclass_metadata' is a dataclass argument"
+            ),
+        ),
+        argnames="argname, argument, assertion",
+    )
+    @testing.variation("use_annotated", [True, False, "control"])
+    def test_names_encountered_for_annotated(
+        self, argname, argument, assertion, use_annotated, decl_base
     ):
-        decl_base.registry.update_type_annotation_map(
-            {_UnionTypeAlias: JSON, _StrTypeAlias: String(30)}
+        # anno only: global myint
+
+        if argument is not _NoArg.NO_ARG:
+            kw = {argname: argument}
+
+            if argname == "quote":
+                kw["name"] = "somename"
+        else:
+            kw = {}
+
+        is_warning = isinstance(assertion, exc.SADeprecationWarning)
+        is_dataclass = argname in (
+            "kw_only",
+            "init",
+            "repr",
+            "compare",
+            "default_factory",
+            "hash",
+            "dataclass_metadata",
         )
 
-        class Test(decl_base):
-            __tablename__ = "test"
+        if is_dataclass:
+
+            class Base(MappedAsDataclass, decl_base):
+                __abstract__ = True
+
+        else:
+            Base = decl_base
+
+        if use_annotated.control:
+            # test in reverse; that kw set on the main mapped_column() takes
+            # effect when the Annotated is there also and does not have the
+            # kw
+            amc = mapped_column()
+            myint = Annotated[int, amc]
+
+            mc = mapped_column(**kw)
+
+            class User(Base):
+                __tablename__ = "user"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                myname: Mapped[myint] = mc
+
+        elif use_annotated:
+            amc = mapped_column(**kw)
+            myint = Annotated[int, amc]
+
+            mc = mapped_column()
+
+            if is_warning:
+                with expect_deprecated(assertion.args[0]):
+
+                    class User(Base):
+                        __tablename__ = "user"
+                        id: Mapped[int] = mapped_column(primary_key=True)
+                        myname: Mapped[myint] = mc
+
+            else:
+
+                class User(Base):
+                    __tablename__ = "user"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    myname: Mapped[myint] = mc
+
+        else:
+            mc = cast(MappedColumn, mapped_column(**kw))
+
+        mapper_prop = mc.mapper_property_to_assign
+        column_to_assign, sort_order = mc.columns_to_assign[0]
+
+        if not is_warning:
+            assert_result = testing.resolve_lambda(
+                assertion,
+                sort_order=sort_order,
+                column_property=mapper_prop,
+                column=column_to_assign,
+                mc=mc,
+            )
+            assert assert_result
+        elif is_dataclass and (not use_annotated or use_annotated.control):
+            eq_(
+                getattr(mc._attribute_options, f"dataclasses_{argname}"),
+                argument,
+            )
+
+    @testing.combinations(("index",), ("unique",), argnames="paramname")
+    @testing.combinations((True,), (False,), (None,), argnames="orig")
+    @testing.combinations((True,), (False,), (None,), argnames="merging")
+    def test_index_unique_combinations(
+        self, paramname, orig, merging, decl_base
+    ):
+        """test #11091"""
+
+        # anno only: global myint
+
+        amc = mapped_column(**{paramname: merging})
+        myint = Annotated[int, amc]
+
+        mc = mapped_column(**{paramname: orig})
+
+        class User(decl_base):
+            __tablename__ = "user"
             id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[_StrTypeAlias]
-            structure: Mapped[_UnionTypeAlias]
+            myname: Mapped[myint] = mc
 
-        eq_(Test.__table__.c.data.type.length, 30)
-        is_(Test.__table__.c.structure.type._type_affinity, JSON)
+        result = getattr(User.__table__.c.myname, paramname)
+        if orig is None:
+            is_(result, merging)
+        else:
+            is_(result, orig)
 
     @testing.variation(
-        "option",
+        "union",
         [
-            "plain",
             "union",
-            "union_604",
+            ("pep604", requires.python310),
             "union_null",
-            "union_null_604",
-            "optional",
-            "optional_union",
-            "optional_union_604",
-            "union_newtype",
-            "union_null_newtype",
-            "union_695",
-            "union_null_695",
+            ("pep604_null", requires.python310),
         ],
     )
-    @testing.variation("in_map", ["yes", "no", "value"])
-    @testing.requires.python312
-    def test_pep695_behavior(self, decl_base, in_map, option):
-        """Issue #11955"""
-        # anno only: global tat
+    def test_unions(self, union):
+        # anno only: global UnionType
+        our_type = Numeric(10, 2)
 
-        if option.plain:
-            tat = TypeAliasType("tat", str)
-        elif option.union:
-            tat = TypeAliasType("tat", Union[str, int])
-        elif option.union_604:
-            tat = TypeAliasType("tat", str | int)
-        elif option.union_null:
-            tat = TypeAliasType("tat", Union[str, int, None])
-        elif option.union_null_604:
-            tat = TypeAliasType("tat", str | int | None)
-        elif option.optional:
-            tat = TypeAliasType("tat", Optional[str])
-        elif option.optional_union:
-            tat = TypeAliasType("tat", Optional[Union[str, int]])
-        elif option.optional_union_604:
-            tat = TypeAliasType("tat", Optional[str | int])
-        elif option.union_newtype:
-            # this seems to be illegal for typing but "works"
-            tat = NewType("tat", Union[str, int])
-        elif option.union_null_newtype:
-            # this seems to be illegal for typing but "works"
-            tat = NewType("tat", Union[str, int, None])
-        elif option.union_695:
-            tat = TypeAliasType("tat", str | int)
-        elif option.union_null_695:
-            tat = TypeAliasType("tat", str | int | None)
+        if union.union:
+            UnionType = Union[float, Decimal]
+        elif union.union_null:
+            UnionType = Union[float, Decimal, None]
+        elif union.pep604:
+            UnionType = float | Decimal
+        elif union.pep604_null:
+            UnionType = float | Decimal | None
         else:
-            option.fail()
+            union.fail()
 
-        if in_map.yes:
-            decl_base.registry.update_type_annotation_map({tat: String(99)})
-        elif in_map.value and "newtype" not in option.name:
-            decl_base.registry.update_type_annotation_map(
-                {tat.__value__: String(99)}
+        class Base(DeclarativeBase):
+            type_annotation_map = {UnionType: our_type}
+
+        class User(Base):
+            __tablename__ = "users"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+
+            data: Mapped[Union[float, Decimal]]
+            reverse_data: Mapped[Union[Decimal, float]]
+
+            optional_data: Mapped[Optional[Union[float, Decimal]]] = (
+                mapped_column()
             )
 
-        def declare():
-            class Test(decl_base):
-                __tablename__ = "test"
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped[tat]
+            # use Optional directly
+            reverse_optional_data: Mapped[Optional[Union[Decimal, float]]] = (
+                mapped_column()
+            )
 
-            return Test.__table__.c.data
+            # use Union with None, same as Optional but presents differently
+            # (Optional object with __origin__ Union vs. Union)
+            reverse_u_optional_data: Mapped[Union[Decimal, float, None]] = (
+                mapped_column()
+            )
 
-        if in_map.yes:
-            col = declare()
-            length = 99
-        elif (
-            in_map.value
-            and "newtype" not in option.name
-            or option.optional
-            or option.plain
-        ):
-            with expect_deprecated(
-                "Matching the provided TypeAliasType 'tat' on its "
-                "resolved value without matching it in the "
-                "type_annotation_map is deprecated; add this type to the "
-                "type_annotation_map to allow it to match explicitly.",
-            ):
-                col = declare()
-            length = 99 if in_map.value else None
-        else:
-            with expect_raises_message(
-                orm_exc.MappedAnnotationError,
-                r"Could not locate SQLAlchemy Core type for Python type .*tat "
-                "inside the 'data' attribute Mapped annotation",
-            ):
-                declare()
-            return
+            refer_union: Mapped[UnionType]
+            refer_union_optional: Mapped[Optional[UnionType]]
 
-        is_true(isinstance(col.type, String))
-        eq_(col.type.length, length)
-        nullable = "null" in option.name or "optional" in option.name
-        eq_(col.nullable, nullable)
+            # py38, 37 does not automatically flatten unions, add extra tests
+            # for this.  maintain these in order to catch future regressions
+            # in the behavior of ``Union``
+            unflat_union_optional_data: Mapped[
+                Union[Union[Decimal, float, None], None]
+            ] = mapped_column()
 
-    @testing.variation(
-        "type_",
-        [
-            "str_extension",
-            "str_typing",
-            "generic_extension",
-            "generic_typing",
-            "generic_typed_extension",
-            "generic_typed_typing",
-        ],
-    )
-    @testing.requires.python312
-    def test_pep695_typealias_as_typemap_keys(
-        self, decl_base: Type[DeclarativeBase], type_
-    ):
-        """test #10807"""
+            float_data: Mapped[float] = mapped_column()
+            decimal_data: Mapped[Decimal] = mapped_column()
 
-        decl_base.registry.update_type_annotation_map(
-            {
-                _UnionPep695: JSON,
-                _StrPep695: String(30),
-                _TypingStrPep695: String(30),
-                _GenericPep695: String(30),
-                _TypingGenericPep695: String(30),
-                _GenericPep695Typed: String(30),
-                _TypingGenericPep695Typed: String(30),
-            }
-        )
+            if compat.py310:
+                pep604_data: Mapped[float | Decimal] = mapped_column()
+                pep604_reverse: Mapped[Decimal | float] = mapped_column()
+                pep604_optional: Mapped[Decimal | float | None] = (
+                    mapped_column()
+                )
+                pep604_data_fwd: Mapped["float | Decimal"] = mapped_column()
+                pep604_reverse_fwd: Mapped["Decimal | float"] = mapped_column()
+                pep604_optional_fwd: Mapped["Decimal | float | None"] = (
+                    mapped_column()
+                )
 
-        class Test(decl_base):
-            __tablename__ = "test"
-            id: Mapped[int] = mapped_column(primary_key=True)
-            if type_.str_extension:
-                data: Mapped[_StrPep695]
-            elif type_.str_typing:
-                data: Mapped[_TypingStrPep695]
-            elif type_.generic_extension:
-                data: Mapped[_GenericPep695]
-            elif type_.generic_typing:
-                data: Mapped[_TypingGenericPep695]
-            elif type_.generic_typed_extension:
-                data: Mapped[_GenericPep695Typed]
-            elif type_.generic_typed_typing:
-                data: Mapped[_TypingGenericPep695Typed]
-            else:
-                type_.fail()
-            structure: Mapped[_UnionPep695]
+        info = [
+            ("data", False),
+            ("reverse_data", False),
+            ("optional_data", True),
+            ("reverse_optional_data", True),
+            ("reverse_u_optional_data", True),
+            ("refer_union", "null" in union.name),
+            ("refer_union_optional", True),
+            ("unflat_union_optional_data", True),
+        ]
+        if compat.py310:
+            info += [
+                ("pep604_data", False),
+                ("pep604_reverse", False),
+                ("pep604_optional", True),
+                ("pep604_data_fwd", False),
+                ("pep604_reverse_fwd", False),
+                ("pep604_optional_fwd", True),
+            ]
+
+        for name, nullable in info:
+            col = User.__table__.c[name]
+            is_(col.type, our_type, name)
+            is_(col.nullable, nullable, name)
 
-        eq_(Test.__table__.c.data.type._type_affinity, String)
-        eq_(Test.__table__.c.data.type.length, 30)
-        is_(Test.__table__.c.structure.type._type_affinity, JSON)
+        is_true(isinstance(User.__table__.c.float_data.type, Float))
+        ne_(User.__table__.c.float_data.type, our_type)
+
+        is_true(isinstance(User.__table__.c.decimal_data.type, Numeric))
+        ne_(User.__table__.c.decimal_data.type, our_type)
 
     @testing.variation(
-        "alias_type",
-        ["none", "typekeyword", "typealias", "typekeyword_nested"],
+        "union",
+        [
+            "union",
+            ("pep604", requires.python310),
+            ("pep695", requires.python312),
+        ],
     )
-    @testing.requires.python312
-    def test_extract_pep593_from_pep695(
-        self, decl_base: Type[DeclarativeBase], alias_type
-    ):
-        """test #11130"""
-        if alias_type.typekeyword:
-            decl_base.registry.update_type_annotation_map(
-                {strtypalias_keyword: VARCHAR(33)}  # noqa: F821
-            )
-        if alias_type.typekeyword_nested:
-            decl_base.registry.update_type_annotation_map(
-                {strtypalias_keyword_nested: VARCHAR(42)}  # noqa: F821
-            )
-
-        class MyClass(decl_base):
-            __tablename__ = "my_table"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
+    def test_optional_in_annotation_map(self, union):
+        """See issue #11370"""
 
-            if alias_type.typekeyword:
-                data_one: Mapped[strtypalias_keyword]  # noqa: F821
-            elif alias_type.typealias:
-                data_one: Mapped[strtypalias_ta]  # noqa: F821
-            elif alias_type.none:
-                data_one: Mapped[strtypalias_plain]  # noqa: F821
-            elif alias_type.typekeyword_nested:
-                data_one: Mapped[strtypalias_keyword_nested]  # noqa: F821
+        class Base(DeclarativeBase):
+            if union.union:
+                type_annotation_map = {_Json: JSON}
+            elif union.pep604:
+                type_annotation_map = {_JsonPep604: JSON}
+            elif union.pep695:
+                type_annotation_map = {_JsonPep695: JSON}  # noqa: F821
             else:
-                alias_type.fail()
+                union.fail()
 
-        table = MyClass.__table__
-        assert table is not None
+        class A(Base):
+            __tablename__ = "a"
 
-        if alias_type.typekeyword_nested:
-            # a nested annotation is not supported
-            eq_(MyClass.data_one.expression.info, {})
-        else:
-            eq_(MyClass.data_one.expression.info, {"hi": "there"})
+            id: Mapped[int] = mapped_column(primary_key=True)
+            if union.union:
+                json1: Mapped[_Json]
+                json2: Mapped[_Json] = mapped_column(nullable=False)
+            elif union.pep604:
+                json1: Mapped[_JsonPep604]
+                json2: Mapped[_JsonPep604] = mapped_column(nullable=False)
+            elif union.pep695:
+                json1: Mapped[_JsonPep695]  # noqa: F821
+                json2: Mapped[_JsonPep695] = mapped_column(  # noqa: F821
+                    nullable=False
+                )
+            else:
+                union.fail()
 
-        if alias_type.typekeyword:
-            eq_(MyClass.data_one.type.length, 33)
-        elif alias_type.typekeyword_nested:
-            eq_(MyClass.data_one.type.length, 42)
-        else:
-            eq_(MyClass.data_one.type.length, None)
+        is_(A.__table__.c.json1.type._type_affinity, JSON)
+        is_(A.__table__.c.json2.type._type_affinity, JSON)
+        is_true(A.__table__.c.json1.nullable)
+        is_false(A.__table__.c.json2.nullable)
 
     @testing.variation(
-        "type_",
+        "option",
         [
-            "literal",
-            "literal_typing",
-            "recursive",
-            "not_literal",
-            "not_literal_typing",
-            "generic",
-            "generic_typing",
-            "generic_typed",
-            "generic_typed_typing",
+            "not_optional",
+            "optional",
+            "optional_fwd_ref",
+            "union_none",
+            ("pep604", testing.requires.python310),
+            ("pep604_fwd_ref", testing.requires.python310),
         ],
     )
-    @testing.combinations(True, False, argnames="in_map")
-    @testing.requires.python312
-    def test_pep695_literal_defaults_to_enum(self, decl_base, type_, in_map):
-        """test #11305."""
+    @testing.variation("brackets", ["oneset", "twosets"])
+    @testing.combinations(
+        "include_mc_type", "derive_from_anno", argnames="include_mc_type"
+    )
+    def test_optional_styles_nested_brackets(
+        self, option, brackets, include_mc_type
+    ):
+        """composed types test, includes tests that were added later for
+        #12207"""
 
-        def declare():
-            class Foo(decl_base):
-                __tablename__ = "footable"
+        class Base(DeclarativeBase):
+            if testing.requires.python310.enabled:
+                type_annotation_map = {
+                    Dict[str, Decimal]: JSON,
+                    dict[str, Decimal]: JSON,
+                    Union[List[int], List[str]]: JSON,
+                    list[int] | list[str]: JSON,
+                }
+            else:
+                type_annotation_map = {
+                    Dict[str, Decimal]: JSON,
+                    Union[List[int], List[str]]: JSON,
+                }
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                if type_.recursive:
-                    status: Mapped[_RecursiveLiteral695]  # noqa: F821
-                elif type_.literal:
-                    status: Mapped[_Literal695]  # noqa: F821
-                elif type_.literal_typing:
-                    status: Mapped[_TypingLiteral695]  # noqa: F821
-                elif type_.not_literal:
-                    status: Mapped[_StrPep695]  # noqa: F821
-                elif type_.not_literal_typing:
-                    status: Mapped[_TypingStrPep695]  # noqa: F821
-                elif type_.generic:
-                    status: Mapped[_GenericPep695]  # noqa: F821
-                elif type_.generic_typing:
-                    status: Mapped[_TypingGenericPep695]  # noqa: F821
-                elif type_.generic_typed:
-                    status: Mapped[_GenericPep695Typed]  # noqa: F821
-                elif type_.generic_typed_typing:
-                    status: Mapped[_TypingGenericPep695Typed]  # noqa: F821
-                else:
-                    type_.fail()
+        if include_mc_type == "include_mc_type":
+            mc = mapped_column(JSON)
+            mc2 = mapped_column(JSON)
+        else:
+            mc = mapped_column()
+            mc2 = mapped_column()
 
-            return Foo
+        class A(Base):
+            __tablename__ = "a"
 
-        if in_map:
-            decl_base.registry.update_type_annotation_map(
-                {
-                    _Literal695: Enum(enum.Enum),  # noqa: F821
-                    _TypingLiteral695: Enum(enum.Enum),  # noqa: F821
-                    _RecursiveLiteral695: Enum(enum.Enum),  # noqa: F821
-                    _StrPep695: Enum(enum.Enum),  # noqa: F821
-                    _TypingStrPep695: Enum(enum.Enum),  # noqa: F821
-                    _GenericPep695: Enum(enum.Enum),  # noqa: F821
-                    _TypingGenericPep695: Enum(enum.Enum),  # noqa: F821
-                    _GenericPep695Typed: Enum(enum.Enum),  # noqa: F821
-                    _TypingGenericPep695Typed: Enum(enum.Enum),  # noqa: F821
-                }
-            )
-            if type_.recursive:
-                with expect_deprecated(
-                    "Mapping recursive TypeAliasType '.+' that resolve to "
-                    "literal to generate an Enum is deprecated. SQLAlchemy "
-                    "2.1 will not support this use case. Please avoid using "
-                    "recursing TypeAliasType",
-                ):
-                    Foo = declare()
-            elif type_.literal or type_.literal_typing:
-                Foo = declare()
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str] = mapped_column()
+
+            if brackets.oneset:
+                if option.not_optional:
+                    json: Mapped[Dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
+                elif option.optional:
+                    json: Mapped[Optional[Dict[str, Decimal]]] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[Optional[dict[str, Decimal]]] = mc2
+                elif option.optional_fwd_ref:
+                    json: Mapped["Optional[Dict[str, Decimal]]"] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped["Optional[dict[str, Decimal]]"] = mc2
+                elif option.union_none:
+                    json: Mapped[Union[Dict[str, Decimal], None]] = mc
+                    json2: Mapped[Union[None, Dict[str, Decimal]]] = mc2
+                elif option.pep604:
+                    json: Mapped[dict[str, Decimal] | None] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[None | dict[str, Decimal]] = mc2
+                elif option.pep604_fwd_ref:
+                    json: Mapped["dict[str, Decimal] | None"] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped["None | dict[str, Decimal]"] = mc2
+            elif brackets.twosets:
+                if option.not_optional:
+                    json: Mapped[Union[List[int], List[str]]] = mapped_column()  # type: ignore  # noqa: E501
+                elif option.optional:
+                    json: Mapped[Optional[Union[List[int], List[str]]]] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[
+                            Optional[Union[list[int], list[str]]]
+                        ] = mc2
+                elif option.optional_fwd_ref:
+                    json: Mapped["Optional[Union[List[int], List[str]]]"] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[
+                            "Optional[Union[list[int], list[str]]]"
+                        ] = mc2
+                elif option.union_none:
+                    json: Mapped[Union[List[int], List[str], None]] = mc
+                    if testing.requires.python310.enabled:
+                        json2: Mapped[Union[None, list[int], list[str]]] = mc2
+                elif option.pep604:
+                    json: Mapped[list[int] | list[str] | None] = mc
+                    json2: Mapped[None | list[int] | list[str]] = mc2
+                elif option.pep604_fwd_ref:
+                    json: Mapped["list[int] | list[str] | None"] = mc
+                    json2: Mapped["None | list[int] | list[str]"] = mc2
             else:
-                with expect_raises_message(
-                    exc.ArgumentError,
-                    "Can't associate TypeAliasType '.+' to an Enum "
-                    "since it's not a direct alias of a Literal. Only "
-                    "aliases in this form `type my_alias = Literal.'a', "
-                    "'b'.` are supported when generating Enums.",
-                ):
-                    declare()
-                return
-        elif (
-            type_.generic
-            or type_.generic_typing
-            or type_.generic_typed
-            or type_.generic_typed_typing
-        ):
-            # This behaves like 2.1 -> rationale is that no-one asked to
-            # support such types and in 2.1 will already be like this
-            # so it makes little sense to add support this late in the 2.0
-            # series
+                brackets.fail()
+
+        is_(A.__table__.c.json.type._type_affinity, JSON)
+        if hasattr(A, "json2"):
+            is_(A.__table__.c.json2.type._type_affinity, JSON)
+            if option.not_optional:
+                is_false(A.__table__.c.json2.nullable)
+            else:
+                is_true(A.__table__.c.json2.nullable)
+
+        if option.not_optional:
+            is_false(A.__table__.c.json.nullable)
+        else:
+            is_true(A.__table__.c.json.nullable)
+
+    @testing.variation("optional", [True, False])
+    @testing.variation("provide_type", [True, False])
+    @testing.variation("add_to_type_map", [True, False])
+    def test_recursive_type(
+        self, decl_base, optional, provide_type, add_to_type_map
+    ):
+        """test #9553"""
+
+        global T
+
+        T = Dict[str, Optional["T"]]
+
+        if not provide_type and not add_to_type_map:
             with expect_raises_message(
-                exc.ArgumentError,
-                "Could not locate SQLAlchemy Core type for Python type "
-                ".+ inside the 'status' attribute Mapped annotation",
+                sa_exc.ArgumentError,
+                r"Could not locate SQLAlchemy.*" r".*ForwardRef\('T'\).*",
             ):
-                declare()
+
+                class TypeTest(decl_base):
+                    __tablename__ = "my_table"
+
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    if optional:
+                        type_test: Mapped[Optional[T]] = mapped_column()
+                    else:
+                        type_test: Mapped[T] = mapped_column()
+
             return
+
         else:
-            with expect_deprecated(
-                "Matching the provided TypeAliasType '.*' on its "
-                "resolved value without matching it in the "
-                "type_annotation_map is deprecated; add this type to the "
-                "type_annotation_map to allow it to match explicitly.",
-            ):
-                Foo = declare()
-        col = Foo.__table__.c.status
-        if in_map and not type_.not_literal:
-            is_true(isinstance(col.type, Enum))
-            eq_(col.type.enums, ["to-do", "in-progress", "done"])
-            is_(col.type.native_enum, False)
+            if add_to_type_map:
+                decl_base.registry.update_type_annotation_map({T: JSON()})
+
+            class TypeTest(decl_base):
+                __tablename__ = "my_table"
+
+                id: Mapped[int] = mapped_column(primary_key=True)
+
+                if add_to_type_map:
+                    if optional:
+                        type_test: Mapped[Optional[T]] = mapped_column()
+                    else:
+                        type_test: Mapped[T] = mapped_column()
+                else:
+                    if optional:
+                        type_test: Mapped[Optional[T]] = mapped_column(JSON())
+                    else:
+                        type_test: Mapped[T] = mapped_column(JSON())
+
+        if optional:
+            is_(TypeTest.__table__.c.type_test.nullable, True)
         else:
-            is_true(isinstance(col.type, String))
+            is_(TypeTest.__table__.c.type_test.nullable, False)
 
-    @testing.requires.python38
-    def test_typing_literal_identity(self, decl_base):
-        """See issue #11820"""
+        self.assert_compile(
+            select(TypeTest),
+            "SELECT my_table.id, my_table.type_test FROM my_table",
+        )
 
-        class Foo(decl_base):
-            __tablename__ = "footable"
+    def test_missing_mapped_lhs(self, decl_base):
+        with expect_annotation_syntax_error("User.name"):
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            t: Mapped[_TypingLiteral]
-            te: Mapped[_TypingExtensionsLiteral]
+            class User(decl_base):
+                __tablename__ = "users"
 
-        for col in (Foo.__table__.c.t, Foo.__table__.c.te):
-            is_true(isinstance(col.type, Enum))
-            eq_(col.type.enums, ["a", "b"])
-            is_(col.type.native_enum, False)
+                id: Mapped[int] = mapped_column(primary_key=True)
+                name: str = mapped_column()  # type: ignore
 
-    @testing.requires.python310
-    def test_we_got_all_attrs_test_annotated(self):
-        argnames = _py_inspect.getfullargspec(mapped_column)
-        assert _annotated_names_tested.issuperset(argnames.kwonlyargs), (
-            f"annotated attributes were not tested: "
-            f"{set(argnames.kwonlyargs).difference(_annotated_names_tested)}"
+    def test_construct_lhs_separate_name(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+            name: Mapped[str] = mapped_column()
+            data: Mapped[Optional[str]] = mapped_column("the_data")
+
+        self.assert_compile(
+            select(User.data), "SELECT users.the_data FROM users"
         )
+        is_true(User.__table__.c.the_data.nullable)
 
-    @annotated_name_test_cases(
-        ("sort_order", 100, lambda sort_order: sort_order == 100),
-        ("nullable", False, lambda column: column.nullable is False),
-        (
-            "active_history",
-            True,
-            lambda column_property: column_property.active_history is True,
-        ),
-        (
-            "deferred",
-            True,
-            lambda column_property: column_property.deferred is True,
-        ),
-        (
-            "deferred",
-            _NoArg.NO_ARG,
-            lambda column_property: column_property is None,
-        ),
-        (
-            "deferred_group",
-            "mygroup",
-            lambda column_property: column_property.deferred is True
-            and column_property.group == "mygroup",
-        ),
-        (
-            "deferred_raiseload",
-            True,
-            lambda column_property: column_property.deferred is True
-            and column_property.raiseload is True,
-        ),
-        (
-            "server_default",
-            "25",
-            lambda column: column.server_default.arg == "25",
-        ),
-        (
-            "server_onupdate",
-            "25",
-            lambda column: column.server_onupdate.arg == "25",
-        ),
-        (
-            "default",
-            25,
-            lambda column: column.default.arg == 25,
-        ),
-        (
-            "insert_default",
-            25,
-            lambda column: column.default.arg == 25,
-        ),
-        (
-            "onupdate",
-            25,
-            lambda column: column.onupdate.arg == 25,
-        ),
-        ("doc", "some doc", lambda column: column.doc == "some doc"),
-        (
-            "comment",
-            "some comment",
-            lambda column: column.comment == "some comment",
-        ),
-        ("index", True, lambda column: column.index is True),
-        ("index", _NoArg.NO_ARG, lambda column: column.index is None),
-        ("index", False, lambda column: column.index is False),
-        ("unique", True, lambda column: column.unique is True),
-        ("unique", False, lambda column: column.unique is False),
-        ("autoincrement", True, lambda column: column.autoincrement is True),
-        ("system", True, lambda column: column.system is True),
-        ("primary_key", True, lambda column: column.primary_key is True),
-        ("type_", BIGINT, lambda column: isinstance(column.type, BIGINT)),
-        ("info", {"foo": "bar"}, lambda column: column.info == {"foo": "bar"}),
-        (
-            "use_existing_column",
-            True,
-            lambda mc: mc._use_existing_column is True,
-        ),
-        (
-            "quote",
-            True,
-            exc.SADeprecationWarning(
-                "Can't use the 'key' or 'name' arguments in Annotated "
-            ),
-        ),
-        (
-            "key",
-            "mykey",
-            exc.SADeprecationWarning(
-                "Can't use the 'key' or 'name' arguments in Annotated "
-            ),
-        ),
-        (
-            "name",
-            "mykey",
-            exc.SADeprecationWarning(
-                "Can't use the 'key' or 'name' arguments in Annotated "
-            ),
-        ),
-        (
-            "kw_only",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'kw_only' is a dataclass argument "
-            ),
-            testing.requires.python310,
-        ),
-        (
-            "compare",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'compare' is a dataclass argument "
-            ),
-            testing.requires.python310,
-        ),
-        (
-            "default_factory",
-            lambda: 25,
-            exc.SADeprecationWarning(
-                "Argument 'default_factory' is a dataclass argument "
-            ),
-        ),
-        (
-            "repr",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'repr' is a dataclass argument "
-            ),
-        ),
-        (
-            "init",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'init' is a dataclass argument"
-            ),
-        ),
-        (
-            "hash",
-            True,
-            exc.SADeprecationWarning(
-                "Argument 'hash' is a dataclass argument"
-            ),
-        ),
-        (
-            "dataclass_metadata",
-            {},
-            exc.SADeprecationWarning(
-                "Argument 'dataclass_metadata' is a dataclass argument"
-            ),
-        ),
-        argnames="argname, argument, assertion",
-    )
-    @testing.variation("use_annotated", [True, False, "control"])
-    def test_names_encountered_for_annotated(
-        self, argname, argument, assertion, use_annotated, decl_base
-    ):
-        # anno only: global myint
+    def test_construct_works_in_expr(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-        if argument is not _NoArg.NO_ARG:
-            kw = {argname: argument}
+            id: Mapped[int] = mapped_column(primary_key=True)
 
-            if argname == "quote":
-                kw["name"] = "somename"
-        else:
-            kw = {}
+        class Address(decl_base):
+            __tablename__ = "addresses"
 
-        is_warning = isinstance(assertion, exc.SADeprecationWarning)
-        is_dataclass = argname in (
-            "kw_only",
-            "init",
-            "repr",
-            "compare",
-            "default_factory",
-            "hash",
-            "dataclass_metadata",
-        )
+            id: Mapped[int] = mapped_column(primary_key=True)
+            user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
 
-        if is_dataclass:
+            user = relationship(User, primaryjoin=user_id == User.id)
 
-            class Base(MappedAsDataclass, decl_base):
-                __abstract__ = True
+        self.assert_compile(
+            select(Address.user_id, User.id).join(Address.user),
+            "SELECT addresses.user_id, users.id FROM addresses "
+            "JOIN users ON addresses.user_id = users.id",
+        )
 
-        else:
-            Base = decl_base
+    def test_construct_works_as_polymorphic_on(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-        if use_annotated.control:
-            # test in reverse; that kw set on the main mapped_column() takes
-            # effect when the Annotated is there also and does not have the
-            # kw
-            amc = mapped_column()
-            myint = Annotated[int, amc]
+            id: Mapped[int] = mapped_column(primary_key=True)
+            type: Mapped[str] = mapped_column()
 
-            mc = mapped_column(**kw)
+            __mapper_args__ = {"polymorphic_on": type}
 
-            class User(Base):
-                __tablename__ = "user"
-                id: Mapped[int] = mapped_column(primary_key=True)
-                myname: Mapped[myint] = mc
+        decl_base.registry.configure()
+        is_(User.__table__.c.type, User.__mapper__.polymorphic_on)
 
-        elif use_annotated:
-            amc = mapped_column(**kw)
-            myint = Annotated[int, amc]
+    def test_construct_works_as_version_id_col(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-            mc = mapped_column()
+            id: Mapped[int] = mapped_column(primary_key=True)
+            version_id: Mapped[int] = mapped_column()
 
-            if is_warning:
-                with expect_deprecated(assertion.args[0]):
+            __mapper_args__ = {"version_id_col": version_id}
 
-                    class User(Base):
-                        __tablename__ = "user"
-                        id: Mapped[int] = mapped_column(primary_key=True)
-                        myname: Mapped[myint] = mc
+        decl_base.registry.configure()
+        is_(User.__table__.c.version_id, User.__mapper__.version_id_col)
 
-            else:
+    def test_construct_works_in_deferred(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-                class User(Base):
-                    __tablename__ = "user"
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    myname: Mapped[myint] = mc
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str] = deferred(mapped_column())
 
-        else:
-            mc = cast(MappedColumn, mapped_column(**kw))
+        self.assert_compile(select(User), "SELECT users.id FROM users")
+        self.assert_compile(
+            select(User).options(undefer(User.data)),
+            "SELECT users.id, users.data FROM users",
+        )
 
-        mapper_prop = mc.mapper_property_to_assign
-        column_to_assign, sort_order = mc.columns_to_assign[0]
+    def test_deferred_kw(self, decl_base):
+        class User(decl_base):
+            __tablename__ = "users"
 
-        if not is_warning:
-            assert_result = testing.resolve_lambda(
-                assertion,
-                sort_order=sort_order,
-                column_property=mapper_prop,
-                column=column_to_assign,
-                mc=mc,
-            )
-            assert assert_result
-        elif is_dataclass and (not use_annotated or use_annotated.control):
-            eq_(
-                getattr(mc._attribute_options, f"dataclasses_{argname}"),
-                argument,
-            )
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str] = mapped_column(deferred=True)
 
-    @testing.combinations(("index",), ("unique",), argnames="paramname")
-    @testing.combinations((True,), (False,), (None,), argnames="orig")
-    @testing.combinations((True,), (False,), (None,), argnames="merging")
-    def test_index_unique_combinations(
-        self, paramname, orig, merging, decl_base
-    ):
-        """test #11091"""
+        self.assert_compile(select(User), "SELECT users.id FROM users")
+        self.assert_compile(
+            select(User).options(undefer(User.data)),
+            "SELECT users.id, users.data FROM users",
+        )
 
-        # anno only: global myint
 
-        amc = mapped_column(**{paramname: merging})
-        myint = Annotated[int, amc]
+class Pep593InterpretationTests(fixtures.TestBase, testing.AssertsCompiledSQL):
+    __dialect__ = "default"
 
-        mc = mapped_column(**{paramname: orig})
+    def test_extract_from_pep593(self, decl_base):
+        # anno only: global Address
+
+        @dataclasses.dataclass
+        class Address:
+            street: str
+            state: str
+            zip_: str
 
         class User(decl_base):
             __tablename__ = "user"
+
             id: Mapped[int] = mapped_column(primary_key=True)
-            myname: Mapped[myint] = mc
+            name: Mapped[str] = mapped_column()
 
-        result = getattr(User.__table__.c.myname, paramname)
-        if orig is None:
-            is_(result, merging)
-        else:
-            is_(result, orig)
+            address: Mapped[Annotated[Address, "foo"]] = composite(
+                mapped_column(), mapped_column(), mapped_column("zip")
+            )
 
-    def test_pep484_newtypes_as_typemap_keys(
+        self.assert_compile(
+            select(User),
+            'SELECT "user".id, "user".name, "user".street, '
+            '"user".state, "user".zip FROM "user"',
+            dialect="default",
+        )
+
+    def test_pep593_types_as_typemap_keys(
         self, decl_base: Type[DeclarativeBase]
     ):
-        # anno only: global str50, str30, str3050
+        """neat!!!"""
+        # anno only: global str50, str30, opt_str50, opt_str30
 
-        str50 = NewType("str50", str)
-        str30 = NewType("str30", str)
-        str3050 = NewType("str30", str50)
+        str50 = Annotated[str, 50]
+        str30 = Annotated[str, 30]
+        opt_str50 = Optional[str50]
+        opt_str30 = Optional[str30]
 
         decl_base.registry.update_type_annotation_map(
-            {str50: String(50), str30: String(30), str3050: String(150)}
+            {str50: String(50), str30: String(30)}
         )
 
         class MyClass(decl_base):
@@ -1635,48 +1515,130 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
 
             id: Mapped[str50] = mapped_column(primary_key=True)
             data_one: Mapped[str30]
-            data_two: Mapped[str50]
-            data_three: Mapped[Optional[str30]]
-            data_four: Mapped[str3050]
+            data_two: Mapped[opt_str30]
+            data_three: Mapped[str50]
+            data_four: Mapped[opt_str50]
+            data_five: Mapped[str]
+            data_six: Mapped[Optional[str]]
 
         eq_(MyClass.__table__.c.data_one.type.length, 30)
         is_false(MyClass.__table__.c.data_one.nullable)
+        eq_(MyClass.__table__.c.data_two.type.length, 30)
+        is_true(MyClass.__table__.c.data_two.nullable)
+        eq_(MyClass.__table__.c.data_three.type.length, 50)
 
-        eq_(MyClass.__table__.c.data_two.type.length, 50)
-        is_false(MyClass.__table__.c.data_two.nullable)
+    @testing.variation(
+        "alias_type",
+        [
+            "none",
+            "typekeyword",
+            "typekeyword_unpopulated",
+            "typealias",
+            "typekeyword_nested",
+        ],
+    )
+    @testing.requires.python312
+    def test_extract_pep593_from_pep695(
+        self, decl_base: Type[DeclarativeBase], alias_type
+    ):
+        """test #11130"""
+        if alias_type.typekeyword:
+            decl_base.registry.update_type_annotation_map(
+                {strtypalias_keyword: VARCHAR(33)}  # noqa: F821
+            )
+        if alias_type.typekeyword_nested:
+            decl_base.registry.update_type_annotation_map(
+                {strtypalias_keyword_nested: VARCHAR(42)}  # noqa: F821
+            )
 
-        eq_(MyClass.__table__.c.data_three.type.length, 30)
-        is_true(MyClass.__table__.c.data_three.nullable)
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
 
-        eq_(MyClass.__table__.c.data_four.type.length, 150)
-        is_false(MyClass.__table__.c.data_four.nullable)
+            id: Mapped[int] = mapped_column(primary_key=True)
 
-    def test_newtype_missing_from_map(self, decl_base):
-        # anno only: global str50
+            if alias_type.typekeyword or alias_type.typekeyword_unpopulated:
+                data_one: Mapped[strtypalias_keyword]  # noqa: F821
+            elif alias_type.typealias:
+                data_one: Mapped[strtypalias_ta]  # noqa: F821
+            elif alias_type.none:
+                data_one: Mapped[strtypalias_plain]  # noqa: F821
+            elif alias_type.typekeyword_nested:
+                data_one: Mapped[strtypalias_keyword_nested]  # noqa: F821
+            else:
+                alias_type.fail()
 
-        str50 = NewType("str50", str)
+        table = MyClass.__table__
+        assert table is not None
 
-        if compat.py310:
-            text = ".*str50"
+        if alias_type.typekeyword_nested:
+            # a nested annotation is not supported
+            eq_(MyClass.data_one.expression.info, {})
         else:
-            # NewTypes before 3.10 had a very bad repr
-            # <function NewType.<locals>.new_type at 0x...>
-            text = ".*NewType.*"
+            eq_(MyClass.data_one.expression.info, {"hi": "there"})
 
-        with expect_deprecated(
-            f"Matching the provided NewType '{text}' on its "
-            "resolved value without matching it in the "
-            "type_annotation_map is deprecated; add this type to the "
-            "type_annotation_map to allow it to match explicitly.",
+        if alias_type.typekeyword:
+            eq_(MyClass.data_one.type.length, 33)
+        elif alias_type.typekeyword_nested:
+            eq_(MyClass.data_one.type.length, 42)
+        else:
+            eq_(MyClass.data_one.type.length, None)
+
+    @testing.requires.python312
+    def test_no_recursive_pep593_from_pep695(
+        self, decl_base: Type[DeclarativeBase]
+    ):
+        def declare():
+            class MyClass(decl_base):
+                __tablename__ = "my_table"
+
+                id: Mapped[int] = mapped_column(primary_key=True)
+
+                data_one: Mapped[_RecursivePep695Pep593]  # noqa: F821
+
+        with expect_raises_message(
+            orm_exc.MappedAnnotationError,
+            r"Could not locate SQLAlchemy Core type when resolving for Python "
+            r"type "
+            r"indicated by '_RecursivePep695Pep593' inside the Mapped\[\] "
+            r"annotation for the 'data_one' attribute; none of "
+            r"'_RecursivePep695Pep593', "
+            r"'typing.Annotated\[_TypingStrPep695, .*\]', '_TypingStrPep695' "
+            r"are resolvable by the registry",
         ):
+            declare()
+
+    @testing.variation("in_map", [True, False])
+    @testing.variation("alias_type", ["plain", "pep695"])
+    @testing.requires.python312
+    def test_generic_typealias_pep593(
+        self, decl_base: Type[DeclarativeBase], alias_type: Variation, in_map
+    ):
+
+        if in_map:
+            decl_base.registry.update_type_annotation_map(
+                {
+                    _GenericPep593TypeAlias[str]: VARCHAR(33),
+                    _GenericPep593Pep695[str]: VARCHAR(33),
+                }
+            )
 
-            class MyClass(decl_base):
-                __tablename__ = "my_table"
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data_one: Mapped[str50]
+            id: Mapped[int] = mapped_column(primary_key=True)
 
-        is_true(isinstance(MyClass.data_one.type, String))
+            if alias_type.plain:
+                data_one: Mapped[_GenericPep593TypeAlias[str]]  # noqa: F821
+            elif alias_type.pep695:
+                data_one: Mapped[_GenericPep593Pep695[str]]  # noqa: F821
+            else:
+                alias_type.fail()
+
+        eq_(MyClass.data_one.expression.info, {"hi": "there"})
+        if in_map:
+            eq_(MyClass.data_one.expression.type.length, 33)
+        else:
+            eq_(MyClass.data_one.expression.type.length, None)
 
     def test_extract_base_type_from_pep593(
         self, decl_base: Type[DeclarativeBase]
@@ -2203,536 +2165,617 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         eq_(A_1.label.property.columns[0].table, A.__table__)
         eq_(A_2.label.property.columns[0].table, A.__table__)
 
-    @testing.variation(
-        "union",
-        [
-            "union",
-            ("pep604", requires.python310),
-            "union_null",
-            ("pep604_null", requires.python310),
-        ],
-    )
-    def test_unions(self, union):
-        # anno only: global UnionType
-        our_type = Numeric(10, 2)
-
-        if union.union:
-            UnionType = Union[float, Decimal]
-        elif union.union_null:
-            UnionType = Union[float, Decimal, None]
-        elif union.pep604:
-            UnionType = float | Decimal
-        elif union.pep604_null:
-            UnionType = float | Decimal | None
-        else:
-            union.fail()
 
-        class Base(DeclarativeBase):
-            type_annotation_map = {UnionType: our_type}
+class TypeResolutionTests(fixtures.TestBase, testing.AssertsCompiledSQL):
+    __dialect__ = "default"
 
-        class User(Base):
-            __tablename__ = "users"
+    @testing.combinations(
+        (str, types.String),
+        (Decimal, types.Numeric),
+        (float, types.Float),
+        (datetime.datetime, types.DateTime),
+        (uuid.UUID, types.Uuid),
+        argnames="pytype_arg,sqltype",
+    )
+    def test_datatype_lookups(self, decl_base, pytype_arg, sqltype):
+        # anno only: global pytype
+        pytype = pytype_arg
 
+        class MyClass(decl_base):
+            __tablename__ = "mytable"
             id: Mapped[int] = mapped_column(primary_key=True)
 
-            data: Mapped[Union[float, Decimal]]
-            reverse_data: Mapped[Union[Decimal, float]]
-
-            optional_data: Mapped[Optional[Union[float, Decimal]]] = (
-                mapped_column()
-            )
-
-            # use Optional directly
-            reverse_optional_data: Mapped[Optional[Union[Decimal, float]]] = (
-                mapped_column()
-            )
-
-            # use Union with None, same as Optional but presents differently
-            # (Optional object with __origin__ Union vs. Union)
-            reverse_u_optional_data: Mapped[Union[Decimal, float, None]] = (
-                mapped_column()
-            )
-
-            refer_union: Mapped[UnionType]
-            refer_union_optional: Mapped[Optional[UnionType]]
-
-            # py38, 37 does not automatically flatten unions, add extra tests
-            # for this.  maintain these in order to catch future regressions
-            # in the behavior of ``Union``
-            unflat_union_optional_data: Mapped[
-                Union[Union[Decimal, float, None], None]
-            ] = mapped_column()
+            data: Mapped[pytype]
 
-            float_data: Mapped[float] = mapped_column()
-            decimal_data: Mapped[Decimal] = mapped_column()
+        assert isinstance(MyClass.__table__.c.data.type, sqltype)
 
-            if compat.py310:
-                pep604_data: Mapped[float | Decimal] = mapped_column()
-                pep604_reverse: Mapped[Decimal | float] = mapped_column()
-                pep604_optional: Mapped[Decimal | float | None] = (
-                    mapped_column()
-                )
-                pep604_data_fwd: Mapped["float | Decimal"] = mapped_column()
-                pep604_reverse_fwd: Mapped["Decimal | float"] = mapped_column()
-                pep604_optional_fwd: Mapped["Decimal | float | None"] = (
-                    mapped_column()
-                )
+    @testing.combinations(
+        (BIGINT(),),
+        (BIGINT,),
+        (Integer().with_variant(BIGINT, "default")),
+        (Integer().with_variant(BIGINT(), "default")),
+        (BIGINT().with_variant(String(), "some_other_dialect")),
+    )
+    def test_type_map_varieties(self, typ):
+        Base = declarative_base(type_annotation_map={int: typ})
 
-        info = [
-            ("data", False),
-            ("reverse_data", False),
-            ("optional_data", True),
-            ("reverse_optional_data", True),
-            ("reverse_u_optional_data", True),
-            ("refer_union", "null" in union.name),
-            ("refer_union_optional", True),
-            ("unflat_union_optional_data", True),
-        ]
-        if compat.py310:
-            info += [
-                ("pep604_data", False),
-                ("pep604_reverse", False),
-                ("pep604_optional", True),
-                ("pep604_data_fwd", False),
-                ("pep604_reverse_fwd", False),
-                ("pep604_optional_fwd", True),
-            ]
+        class MyClass(Base):
+            __tablename__ = "mytable"
 
-        for name, nullable in info:
-            col = User.__table__.c[name]
-            is_(col.type, our_type, name)
-            is_(col.nullable, nullable, name)
+            id: Mapped[int] = mapped_column(primary_key=True)
+            x: Mapped[int]
+            y: Mapped[int] = mapped_column()
+            z: Mapped[int] = mapped_column(typ)
 
-        is_true(isinstance(User.__table__.c.float_data.type, Float))
-        ne_(User.__table__.c.float_data.type, our_type)
+        self.assert_compile(
+            CreateTable(MyClass.__table__),
+            "CREATE TABLE mytable (id BIGINT NOT NULL, "
+            "x BIGINT NOT NULL, y BIGINT NOT NULL, z BIGINT NOT NULL, "
+            "PRIMARY KEY (id))",
+        )
 
-        is_true(isinstance(User.__table__.c.decimal_data.type, Numeric))
-        ne_(User.__table__.c.decimal_data.type, our_type)
+    def test_dont_ignore_unresolvable(self, decl_base):
+        """test #8888"""
 
-    @testing.variation(
-        "union",
-        [
-            "union",
-            ("pep604", requires.python310),
-            ("pep695", requires.python312),
-        ],
-    )
-    def test_optional_in_annotation_map(self, union):
-        """See issue #11370"""
+        with expect_raises_message(
+            sa_exc.ArgumentError,
+            r"Could not resolve all types within mapped annotation: "
+            r"\".*Mapped\[.*fake.*\]\".  Ensure all types are written "
+            r"correctly and are imported within the module in use.",
+        ):
 
-        class Base(DeclarativeBase):
-            if union.union:
-                type_annotation_map = {_Json: JSON}
-            elif union.pep604:
-                type_annotation_map = {_JsonPep604: JSON}
-            elif union.pep695:
-                type_annotation_map = {_JsonPep695: JSON}  # noqa: F821
-            else:
-                union.fail()
+            class A(decl_base):
+                __tablename__ = "a"
 
-        class A(Base):
-            __tablename__ = "a"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped["fake"]  # noqa
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            if union.union:
-                json1: Mapped[_Json]
-                json2: Mapped[_Json] = mapped_column(nullable=False)
-            elif union.pep604:
-                json1: Mapped[_JsonPep604]
-                json2: Mapped[_JsonPep604] = mapped_column(nullable=False)
-            elif union.pep695:
-                json1: Mapped[_JsonPep695]  # noqa: F821
-                json2: Mapped[_JsonPep695] = mapped_column(  # noqa: F821
-                    nullable=False
-                )
-            else:
-                union.fail()
+    def test_type_dont_mis_resolve_on_superclass(self):
+        """test for #8859.
 
-        is_(A.__table__.c.json1.type._type_affinity, JSON)
-        is_(A.__table__.c.json2.type._type_affinity, JSON)
-        is_true(A.__table__.c.json1.nullable)
-        is_false(A.__table__.c.json2.nullable)
+        For subclasses of a type that's in the map, don't resolve this
+        by default, even though we do a search through __mro__.
 
-    @testing.variation(
-        "option",
-        [
-            "not_optional",
-            "optional",
-            "optional_fwd_ref",
-            "union_none",
-            ("pep604", testing.requires.python310),
-            ("pep604_fwd_ref", testing.requires.python310),
-        ],
-    )
-    @testing.variation("brackets", ["oneset", "twosets"])
-    @testing.combinations(
-        "include_mc_type", "derive_from_anno", argnames="include_mc_type"
-    )
-    def test_optional_styles_nested_brackets(
-        self, option, brackets, include_mc_type
-    ):
-        """composed types test, includes tests that were added later for
-        #12207"""
+        """
+        # anno only: global int_sub
 
-        class Base(DeclarativeBase):
-            if testing.requires.python310.enabled:
-                type_annotation_map = {
-                    Dict[str, Decimal]: JSON,
-                    dict[str, Decimal]: JSON,
-                    Union[List[int], List[str]]: JSON,
-                    list[int] | list[str]: JSON,
-                }
-            else:
-                type_annotation_map = {
-                    Dict[str, Decimal]: JSON,
-                    Union[List[int], List[str]]: JSON,
-                }
+        class int_sub(int):
+            pass
 
-        if include_mc_type == "include_mc_type":
-            mc = mapped_column(JSON)
-            mc2 = mapped_column(JSON)
-        else:
-            mc = mapped_column()
-            mc2 = mapped_column()
+        Base = declarative_base(
+            type_annotation_map={
+                int: Integer,
+            }
+        )
 
-        class A(Base):
-            __tablename__ = "a"
+        with expect_raises_message(
+            orm_exc.MappedAnnotationError,
+            "Could not locate SQLAlchemy Core type",
+        ):
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str] = mapped_column()
+            class MyClass(Base):
+                __tablename__ = "mytable"
 
-            if brackets.oneset:
-                if option.not_optional:
-                    json: Mapped[Dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[dict[str, Decimal]] = mapped_column()  # type: ignore  # noqa: E501
-                elif option.optional:
-                    json: Mapped[Optional[Dict[str, Decimal]]] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[Optional[dict[str, Decimal]]] = mc2
-                elif option.optional_fwd_ref:
-                    json: Mapped["Optional[Dict[str, Decimal]]"] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped["Optional[dict[str, Decimal]]"] = mc2
-                elif option.union_none:
-                    json: Mapped[Union[Dict[str, Decimal], None]] = mc
-                    json2: Mapped[Union[None, Dict[str, Decimal]]] = mc2
-                elif option.pep604:
-                    json: Mapped[dict[str, Decimal] | None] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[None | dict[str, Decimal]] = mc2
-                elif option.pep604_fwd_ref:
-                    json: Mapped["dict[str, Decimal] | None"] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped["None | dict[str, Decimal]"] = mc2
-            elif brackets.twosets:
-                if option.not_optional:
-                    json: Mapped[Union[List[int], List[str]]] = mapped_column()  # type: ignore  # noqa: E501
-                elif option.optional:
-                    json: Mapped[Optional[Union[List[int], List[str]]]] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[
-                            Optional[Union[list[int], list[str]]]
-                        ] = mc2
-                elif option.optional_fwd_ref:
-                    json: Mapped["Optional[Union[List[int], List[str]]]"] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[
-                            "Optional[Union[list[int], list[str]]]"
-                        ] = mc2
-                elif option.union_none:
-                    json: Mapped[Union[List[int], List[str], None]] = mc
-                    if testing.requires.python310.enabled:
-                        json2: Mapped[Union[None, list[int], list[str]]] = mc2
-                elif option.pep604:
-                    json: Mapped[list[int] | list[str] | None] = mc
-                    json2: Mapped[None | list[int] | list[str]] = mc2
-                elif option.pep604_fwd_ref:
-                    json: Mapped["list[int] | list[str] | None"] = mc
-                    json2: Mapped["None | list[int] | list[str]"] = mc2
-            else:
-                brackets.fail()
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[int_sub]
 
-        is_(A.__table__.c.json.type._type_affinity, JSON)
-        if hasattr(A, "json2"):
-            is_(A.__table__.c.json2.type._type_affinity, JSON)
-            if option.not_optional:
-                is_false(A.__table__.c.json2.nullable)
-            else:
-                is_true(A.__table__.c.json2.nullable)
+    @testing.variation("in_map", ["yes", "no", "value"])
+    @testing.variation("lookup", ["A", "B", "value"])
+    def test_recursive_pep695_cases(
+        self, decl_base, in_map: Variation, lookup: Variation
+    ):
+        # anno only: global A, B
+        A = TypingTypeAliasType("A", Union[int, float])
+        B = TypingTypeAliasType("B", A)
 
-        if option.not_optional:
-            is_false(A.__table__.c.json.nullable)
-        else:
-            is_true(A.__table__.c.json.nullable)
+        if in_map.yes:
+            decl_base.registry.update_type_annotation_map({A: Numeric(10, 5)})
+        elif in_map.value:
+            decl_base.registry.update_type_annotation_map(
+                {A.__value__: Numeric(10, 5)}
+            )
 
-    @testing.variation("optional", [True, False])
-    @testing.variation("provide_type", [True, False])
-    @testing.variation("add_to_type_map", [True, False])
-    def test_recursive_type(
-        self, decl_base, optional, provide_type, add_to_type_map
-    ):
-        """test #9553"""
+        def declare():
+            class MyClass(decl_base):
+                __tablename__ = "my_table"
+                id: Mapped[int] = mapped_column(primary_key=True)
 
-        global T
+                if lookup.A:
+                    data: Mapped[A]
+                elif lookup.B:
+                    data: Mapped[B]
+                elif lookup.value:
+                    data: Mapped[Union[int, float]]
+                else:
+                    lookup.fail()
 
-        T = Dict[str, Optional["T"]]
+            return MyClass
 
-        if not provide_type and not add_to_type_map:
+        if in_map.value and lookup.B:
+            with expect_deprecated(
+                "Matching to pep-695 type 'A' in a recursive fashion"
+            ):
+                MyClass = declare()
+                eq_(MyClass.data.expression.type.precision, 10)
+        elif in_map.no or (in_map.yes and lookup.value):
             with expect_raises_message(
-                sa_exc.ArgumentError,
-                r"Could not locate SQLAlchemy.*" r".*ForwardRef\('T'\).*",
+                orm_exc.MappedAnnotationError,
+                "Could not locate SQLAlchemy Core type when resolving "
+                "for Python type indicated by",
             ):
+                declare()
+        else:
+            MyClass = declare()
+            eq_(MyClass.data.expression.type.precision, 10)
 
-                class TypeTest(decl_base):
-                    __tablename__ = "my_table"
+    @testing.variation(
+        "dict_key", ["typing", ("plain", testing.requires.python310)]
+    )
+    def test_type_dont_mis_resolve_on_non_generic(self, dict_key):
+        """test for #8859.
 
-                    id: Mapped[int] = mapped_column(primary_key=True)
-                    if optional:
-                        type_test: Mapped[Optional[T]] = mapped_column()
-                    else:
-                        type_test: Mapped[T] = mapped_column()
+        For a specific generic type with arguments, don't do any MRO
+        lookup.
 
-            return
+        """
 
-        else:
-            if add_to_type_map:
-                decl_base.registry.update_type_annotation_map({T: JSON()})
+        Base = declarative_base(
+            type_annotation_map={
+                dict: String,
+            }
+        )
 
-            class TypeTest(decl_base):
-                __tablename__ = "my_table"
+        with expect_raises_message(
+            sa_exc.ArgumentError, "Could not locate SQLAlchemy Core type"
+        ):
+
+            class MyClass(Base):
+                __tablename__ = "mytable"
 
                 id: Mapped[int] = mapped_column(primary_key=True)
 
-                if add_to_type_map:
-                    if optional:
-                        type_test: Mapped[Optional[T]] = mapped_column()
-                    else:
-                        type_test: Mapped[T] = mapped_column()
-                else:
-                    if optional:
-                        type_test: Mapped[Optional[T]] = mapped_column(JSON())
-                    else:
-                        type_test: Mapped[T] = mapped_column(JSON())
+                if dict_key.plain:
+                    data: Mapped[dict[str, str]]
+                elif dict_key.typing:
+                    data: Mapped[Dict[str, str]]
 
-        if optional:
-            is_(TypeTest.__table__.c.type_test.nullable, True)
-        else:
-            is_(TypeTest.__table__.c.type_test.nullable, False)
+    def test_type_secondary_resolution(self):
+        class MyString(String):
+            def _resolve_for_python_type(
+                self, python_type, matched_type, matched_on_flattened
+            ):
+                return String(length=42)
 
-        self.assert_compile(
-            select(TypeTest),
-            "SELECT my_table.id, my_table.type_test FROM my_table",
-        )
+        Base = declarative_base(type_annotation_map={str: MyString})
 
-    def test_missing_mapped_lhs(self, decl_base):
-        with expect_annotation_syntax_error("User.name"):
+        class MyClass(Base):
+            __tablename__ = "mytable"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+            data: Mapped[str]
+
+        is_true(isinstance(MyClass.__table__.c.data.type, String))
+        eq_(MyClass.__table__.c.data.type.length, 42)
+
+    def test_construct_lhs_type_missing(self, decl_base):
+        # anno only: global MyClass
+
+        class MyClass:
+            pass
+
+        with expect_raises_message(
+            orm_exc.MappedAnnotationError,
+            "Could not locate SQLAlchemy Core type when resolving for Python "
+            r"type indicated by '.*class .*MyClass.*' inside the "
+            r"Mapped\[\] annotation for the 'data' attribute; the type "
+            "object is not resolvable by the registry",
+        ):
 
             class User(decl_base):
                 __tablename__ = "users"
 
                 id: Mapped[int] = mapped_column(primary_key=True)
-                name: str = mapped_column()  # type: ignore
+                data: Mapped[MyClass] = mapped_column()
 
-    def test_construct_lhs_separate_name(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+    @testing.variation(
+        "argtype",
+        [
+            "type",
+            ("column", testing.requires.python310),
+            ("mapped_column", testing.requires.python310),
+            "column_class",
+            "ref_to_type",
+            ("ref_to_column", testing.requires.python310),
+        ],
+    )
+    def test_construct_lhs_sqlalchemy_type(self, decl_base, argtype):
+        """test for #12329.
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            name: Mapped[str] = mapped_column()
-            data: Mapped[Optional[str]] = mapped_column("the_data")
+        of note here are all the different messages we have for when the
+        wrong thing is put into Mapped[], and in fact in #12329 we added
+        another one.
 
-        self.assert_compile(
-            select(User.data), "SELECT users.the_data FROM users"
-        )
-        is_true(User.__table__.c.the_data.nullable)
+        This is a lot of different messages, but at the same time they
+        occur at different places in the interpretation of types.   If
+        we were to centralize all these messages, we'd still likely end up
+        doing distinct messages for each scenario, so instead we added
+        a new ArgumentError subclass MappedAnnotationError that provides
+        some commonality to all of these cases.
 
-    def test_construct_works_in_expr(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
 
-            id: Mapped[int] = mapped_column(primary_key=True)
+        """
+        expect_future_annotations = "annotations" in globals()
+
+        if argtype.type:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # properties.py -> _init_column_for_annotation, type is
+                # a SQL type
+                "The type provided inside the 'data' attribute Mapped "
+                "annotation is the SQLAlchemy type .*BigInteger.*. Expected "
+                "a Python type instead",
+            ):
+
+                class User(decl_base):
+                    __tablename__ = "users"
+
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[BigInteger] = mapped_column()
+
+        elif argtype.column:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # util.py -> _extract_mapped_subtype
+                (
+                    re.escape(
+                        "Could not interpret annotation "
+                        "Mapped[Column('q', BigInteger)]."
+                    )
+                    if expect_future_annotations
+                    # properties.py -> _init_column_for_annotation, object is
+                    # not a SQL type or a python type, it's just some object
+                    else re.escape(
+                        "The object provided inside the 'data' attribute "
+                        "Mapped annotation is not a Python type, it's the "
+                        "object Column('q', BigInteger(), table=None). "
+                        "Expected a Python type."
+                    )
+                ),
+            ):
+
+                class User(decl_base):
+                    __tablename__ = "users"
+
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[Column("q", BigInteger)] = (  # noqa: F821
+                        mapped_column()
+                    )
 
-        class Address(decl_base):
-            __tablename__ = "addresses"
+        elif argtype.mapped_column:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # properties.py -> _init_column_for_annotation, object is
+                # not a SQL type or a python type, it's just some object
+                # interestingly, this raises at the same point for both
+                # future annotations mode and legacy annotations mode
+                r"The object provided inside the 'data' attribute "
+                "Mapped annotation is not a Python type, it's the object "
+                r"\<sqlalchemy.orm.properties.MappedColumn.*\>. "
+                "Expected a Python type.",
+            ):
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
+                class User(decl_base):
+                    __tablename__ = "users"
 
-            user = relationship(User, primaryjoin=user_id == User.id)
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    big_integer: Mapped[int] = mapped_column()
+                    data: Mapped[big_integer] = mapped_column()
 
-        self.assert_compile(
-            select(Address.user_id, User.id).join(Address.user),
-            "SELECT addresses.user_id, users.id FROM addresses "
-            "JOIN users ON addresses.user_id = users.id",
-        )
+        elif argtype.column_class:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # properties.py -> _init_column_for_annotation, type is not
+                # a SQL type
+                "Could not locate SQLAlchemy Core type when resolving for "
+                "Python type indicated by "
+                r"'.*class .*.Column.*' inside the "
+                r"Mapped\[\] annotation for the 'data' attribute; the "
+                "type object is not resolvable by the registry",
+            ):
 
-    def test_construct_works_as_polymorphic_on(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+                class User(decl_base):
+                    __tablename__ = "users"
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            type: Mapped[str] = mapped_column()
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[Column] = mapped_column()
 
-            __mapper_args__ = {"polymorphic_on": type}
+        elif argtype.ref_to_type:
+            mytype = BigInteger
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                (
+                    # decl_base.py -> _exract_mappable_attributes
+                    re.escape(
+                        "Could not resolve all types within mapped "
+                        'annotation: "Mapped[mytype]"'
+                    )
+                    if expect_future_annotations
+                    # properties.py -> _init_column_for_annotation, type is
+                    # a SQL type
+                    else re.escape(
+                        "The type provided inside the 'data' attribute Mapped "
+                        "annotation is the SQLAlchemy type "
+                        "<class 'sqlalchemy.sql.sqltypes.BigInteger'>. "
+                        "Expected a Python type instead"
+                    )
+                ),
+            ):
 
-        decl_base.registry.configure()
-        is_(User.__table__.c.type, User.__mapper__.polymorphic_on)
+                class User(decl_base):
+                    __tablename__ = "users"
 
-    def test_construct_works_as_version_id_col(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[mytype] = mapped_column()
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            version_id: Mapped[int] = mapped_column()
+        elif argtype.ref_to_column:
+            mycol = Column("q", BigInteger)
 
-            __mapper_args__ = {"version_id_col": version_id}
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                # decl_base.py -> _exract_mappable_attributes
+                (
+                    re.escape(
+                        "Could not resolve all types within mapped "
+                        'annotation: "Mapped[mycol]"'
+                    )
+                    if expect_future_annotations
+                    else
+                    # properties.py -> _init_column_for_annotation, object is
+                    # not a SQL type or a python type, it's just some object
+                    re.escape(
+                        "The object provided inside the 'data' attribute "
+                        "Mapped "
+                        "annotation is not a Python type, it's the object "
+                        "Column('q', BigInteger(), table=None). "
+                        "Expected a Python type."
+                    )
+                ),
+            ):
 
-        decl_base.registry.configure()
-        is_(User.__table__.c.version_id, User.__mapper__.version_id_col)
+                class User(decl_base):
+                    __tablename__ = "users"
 
-    def test_construct_works_in_deferred(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    data: Mapped[mycol] = mapped_column()
 
-            id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str] = deferred(mapped_column())
+        else:
+            argtype.fail()
 
-        self.assert_compile(select(User), "SELECT users.id FROM users")
-        self.assert_compile(
-            select(User).options(undefer(User.data)),
-            "SELECT users.id, users.data FROM users",
+    def test_plain_typealias_as_typemap_keys(
+        self, decl_base: Type[DeclarativeBase]
+    ):
+        decl_base.registry.update_type_annotation_map(
+            {_UnionTypeAlias: JSON, _StrTypeAlias: String(30)}
         )
 
-    def test_deferred_kw(self, decl_base):
-        class User(decl_base):
-            __tablename__ = "users"
-
+        class Test(decl_base):
+            __tablename__ = "test"
             id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str] = mapped_column(deferred=True)
+            data: Mapped[_StrTypeAlias]
+            structure: Mapped[_UnionTypeAlias]
 
-        self.assert_compile(select(User), "SELECT users.id FROM users")
-        self.assert_compile(
-            select(User).options(undefer(User.data)),
-            "SELECT users.id, users.data FROM users",
-        )
+        eq_(Test.__table__.c.data.type.length, 30)
+        is_(Test.__table__.c.structure.type._type_affinity, JSON)
 
-    @testing.combinations(
-        (str, types.String),
-        (Decimal, types.Numeric),
-        (float, types.Float),
-        (datetime.datetime, types.DateTime),
-        (uuid.UUID, types.Uuid),
-        argnames="pytype_arg,sqltype",
+    @testing.variation(
+        "option",
+        [
+            "plain",
+            "union",
+            "union_604",
+            "union_null",
+            "union_null_604",
+            "optional",
+            "optional_union",
+            "optional_union_604",
+            "union_newtype",
+            "union_null_newtype",
+            "union_695",
+            "union_null_695",
+        ],
     )
-    def test_datatype_lookups(self, decl_base, pytype_arg, sqltype):
-        # anno only: global pytype
-        pytype = pytype_arg
+    @testing.variation("in_map", ["yes", "no", "value"])
+    @testing.requires.python312
+    def test_pep695_behavior(self, decl_base, in_map, option):
+        """Issue #11955; later issue #12829"""
 
-        class MyClass(decl_base):
-            __tablename__ = "mytable"
-            id: Mapped[int] = mapped_column(primary_key=True)
+        # anno only: global tat
 
-            data: Mapped[pytype]
+        if option.plain:
+            tat = TypeAliasType("tat", str)
+        elif option.union:
+            tat = TypeAliasType("tat", Union[str, int])
+        elif option.union_604:
+            tat = TypeAliasType("tat", str | int)
+        elif option.union_null:
+            tat = TypeAliasType("tat", Union[str, int, None])
+        elif option.union_null_604:
+            tat = TypeAliasType("tat", str | int | None)
+        elif option.optional:
+            tat = TypeAliasType("tat", Optional[str])
+        elif option.optional_union:
+            tat = TypeAliasType("tat", Optional[Union[str, int]])
+        elif option.optional_union_604:
+            tat = TypeAliasType("tat", Optional[str | int])
+        elif option.union_newtype:
+            # this seems to be illegal for typing but "works"
+            tat = NewType("tat", Union[str, int])
+        elif option.union_null_newtype:
+            # this seems to be illegal for typing but "works"
+            tat = NewType("tat", Union[str, int, None])
+        elif option.union_695:
+            tat = TypeAliasType("tat", str | int)
+        elif option.union_null_695:
+            tat = TypeAliasType("tat", str | int | None)
+        else:
+            option.fail()
 
-        assert isinstance(MyClass.__table__.c.data.type, sqltype)
+        is_newtype = "newtype" in option.name
+        if in_map.yes:
+            decl_base.registry.update_type_annotation_map({tat: String(99)})
+        elif in_map.value and not is_newtype:
+            decl_base.registry.update_type_annotation_map(
+                {tat.__value__: String(99)}
+            )
 
-    def test_dont_ignore_unresolvable(self, decl_base):
-        """test #8888"""
+        def declare():
+            class Test(decl_base):
+                __tablename__ = "test"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[tat]
 
-        with expect_raises_message(
-            sa_exc.ArgumentError,
-            r"Could not resolve all types within mapped annotation: "
-            r"\".*Mapped\[.*fake.*\]\".  Ensure all types are written "
-            r"correctly and are imported within the module in use.",
-        ):
+            return Test.__table__.c.data
+
+        if in_map.yes or (in_map.value and not is_newtype):
+            col = declare()
+            # String(99) inside the type_map
+            is_true(isinstance(col.type, String))
+            eq_(col.type.length, 99)
+            nullable = "null" in option.name or "optional" in option.name
+            eq_(col.nullable, nullable)
+        elif option.plain or option.optional:
+            col = declare()
+            # plain string from default lookup
+            is_true(isinstance(col.type, String))
+            eq_(col.type.length, None)
+            nullable = "null" in option.name or "optional" in option.name
+            eq_(col.nullable, nullable)
+        else:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                r"Could not locate SQLAlchemy Core type when resolving "
+                r"for Python type "
+                r"indicated by '.*tat' inside the Mapped\[\] "
+                r"annotation for the 'data' attribute;",
+            ):
+                declare()
+            return
+
+    @testing.variation(
+        "type_",
+        [
+            "str_extension",
+            "str_typing",
+            "generic_extension",
+            "generic_typing",
+            "generic_typed_extension",
+            "generic_typed_typing",
+        ],
+    )
+    @testing.requires.python312
+    def test_pep695_typealias_as_typemap_keys(
+        self, decl_base: Type[DeclarativeBase], type_
+    ):
+        """test #10807, #12829"""
+
+        decl_base.registry.update_type_annotation_map(
+            {
+                _UnionPep695: JSON,
+                _StrPep695: String(30),
+                _TypingStrPep695: String(30),
+                _GenericPep695: String(30),
+                _TypingGenericPep695: String(30),
+                _GenericPep695Typed: String(30),
+                _TypingGenericPep695Typed: String(30),
+            }
+        )
 
-            class A(decl_base):
-                __tablename__ = "a"
+        class Test(decl_base):
+            __tablename__ = "test"
+            id: Mapped[int] = mapped_column(primary_key=True)
+            if type_.str_extension:
+                data: Mapped[_StrPep695]
+            elif type_.str_typing:
+                data: Mapped[_TypingStrPep695]
+            elif type_.generic_extension:
+                data: Mapped[_GenericPep695]
+            elif type_.generic_typing:
+                data: Mapped[_TypingGenericPep695]
+            elif type_.generic_typed_extension:
+                data: Mapped[_GenericPep695Typed]
+            elif type_.generic_typed_typing:
+                data: Mapped[_TypingGenericPep695Typed]
+            else:
+                type_.fail()
+            structure: Mapped[_UnionPep695]
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped["fake"]  # noqa
+        eq_(Test.__table__.c.data.type._type_affinity, String)
+        eq_(Test.__table__.c.data.type.length, 30)
+        is_(Test.__table__.c.structure.type._type_affinity, JSON)
 
-    def test_type_dont_mis_resolve_on_superclass(self):
-        """test for #8859.
+    def test_pep484_newtypes_as_typemap_keys(
+        self, decl_base: Type[DeclarativeBase]
+    ):
+        # anno only: global str50, str30, str3050
 
-        For subclasses of a type that's in the map, don't resolve this
-        by default, even though we do a search through __mro__.
+        str50 = NewType("str50", str)
+        str30 = NewType("str30", str)
+        str3050 = NewType("str30", str50)
 
-        """
-        # anno only: global int_sub
+        decl_base.registry.update_type_annotation_map(
+            {str50: String(50), str30: String(30), str3050: String(150)}
+        )
 
-        class int_sub(int):
-            pass
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
 
-        Base = declarative_base(
-            type_annotation_map={
-                int: Integer,
-            }
-        )
+            id: Mapped[str50] = mapped_column(primary_key=True)
+            data_one: Mapped[str30]
+            data_two: Mapped[str50]
+            data_three: Mapped[Optional[str30]]
+            data_four: Mapped[str3050]
 
-        with expect_raises_message(
-            orm_exc.MappedAnnotationError,
-            "Could not locate SQLAlchemy Core type",
-        ):
+        eq_(MyClass.__table__.c.data_one.type.length, 30)
+        is_false(MyClass.__table__.c.data_one.nullable)
 
-            class MyClass(Base):
-                __tablename__ = "mytable"
+        eq_(MyClass.__table__.c.data_two.type.length, 50)
+        is_false(MyClass.__table__.c.data_two.nullable)
 
-                id: Mapped[int] = mapped_column(primary_key=True)
-                data: Mapped[int_sub]
+        eq_(MyClass.__table__.c.data_three.type.length, 30)
+        is_true(MyClass.__table__.c.data_three.nullable)
 
-    @testing.variation(
-        "dict_key", ["typing", ("plain", testing.requires.python310)]
-    )
-    def test_type_dont_mis_resolve_on_non_generic(self, dict_key):
-        """test for #8859.
+        eq_(MyClass.__table__.c.data_four.type.length, 150)
+        is_false(MyClass.__table__.c.data_four.nullable)
 
-        For a specific generic type with arguments, don't do any MRO
-        lookup.
+    def test_newtype_missing_from_map(self, decl_base):
+        # anno only: global str50
 
-        """
+        str50 = NewType("str50", str)
 
-        Base = declarative_base(
-            type_annotation_map={
-                dict: String,
-            }
-        )
+        if compat.py310:
+            text = ".*str50"
+        else:
+            # NewTypes before 3.10 had a very bad repr
+            # <function NewType.<locals>.new_type at 0x...>
+            text = ".*NewType.*"
 
-        with expect_raises_message(
-            sa_exc.ArgumentError, "Could not locate SQLAlchemy Core type"
+        with expect_deprecated(
+            f"Matching the provided NewType '{text}' on its "
+            "resolved value without matching it in the "
+            "type_annotation_map is deprecated; add this type to the "
+            "type_annotation_map to allow it to match explicitly.",
         ):
 
-            class MyClass(Base):
-                __tablename__ = "mytable"
+            class MyClass(decl_base):
+                __tablename__ = "my_table"
 
                 id: Mapped[int] = mapped_column(primary_key=True)
+                data_one: Mapped[str50]
 
-                if dict_key.plain:
-                    data: Mapped[dict[str, str]]
-                elif dict_key.typing:
-                    data: Mapped[Dict[str, str]]
-
-    def test_type_secondary_resolution(self):
-        class MyString(String):
-            def _resolve_for_python_type(
-                self, python_type, matched_type, matched_on_flattened
-            ):
-                return String(length=42)
-
-        Base = declarative_base(type_annotation_map={str: MyString})
-
-        class MyClass(Base):
-            __tablename__ = "mytable"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
-            data: Mapped[str]
-
-        is_true(isinstance(MyClass.__table__.c.data.type, String))
-        eq_(MyClass.__table__.c.data.type.length, 42)
+        is_true(isinstance(MyClass.data_one.type, String))
 
 
-class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL):
+class ResolveToEnumTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = "default"
 
     @testing.variation("use_explicit_name", [True, False])
@@ -3039,6 +3082,117 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL):
 
             is_true(isinstance(Foo.__table__.c.status.type, JSON))
 
+    @testing.variation(
+        "type_",
+        [
+            "literal",
+            "literal_typing",
+            "recursive",
+            "not_literal",
+            "not_literal_typing",
+            "generic",
+            "generic_typing",
+            "generic_typed",
+            "generic_typed_typing",
+        ],
+    )
+    @testing.combinations(True, False, argnames="in_map")
+    @testing.requires.python312
+    def test_pep695_literal_defaults_to_enum(self, decl_base, type_, in_map):
+        """test #11305."""
+
+        def declare():
+            class Foo(decl_base):
+                __tablename__ = "footable"
+
+                id: Mapped[int] = mapped_column(primary_key=True)
+                if type_.recursive:
+                    status: Mapped[_RecursiveLiteral695]  # noqa: F821
+                elif type_.literal:
+                    status: Mapped[_Literal695]  # noqa: F821
+                elif type_.literal_typing:
+                    status: Mapped[_TypingLiteral695]  # noqa: F821
+                elif type_.not_literal:
+                    status: Mapped[_StrPep695]  # noqa: F821
+                elif type_.not_literal_typing:
+                    status: Mapped[_TypingStrPep695]  # noqa: F821
+                elif type_.generic:
+                    status: Mapped[_GenericPep695]  # noqa: F821
+                elif type_.generic_typing:
+                    status: Mapped[_TypingGenericPep695]  # noqa: F821
+                elif type_.generic_typed:
+                    status: Mapped[_GenericPep695Typed]  # noqa: F821
+                elif type_.generic_typed_typing:
+                    status: Mapped[_TypingGenericPep695Typed]  # noqa: F821
+                else:
+                    type_.fail()
+
+            return Foo
+
+        if in_map:
+            decl_base.registry.update_type_annotation_map(
+                {
+                    _Literal695: Enum(enum.Enum),  # noqa: F821
+                    _TypingLiteral695: Enum(enum.Enum),  # noqa: F821
+                    _RecursiveLiteral695: Enum(enum.Enum),  # noqa: F821
+                    _StrPep695: Enum(enum.Enum),  # noqa: F821
+                    _TypingStrPep695: Enum(enum.Enum),  # noqa: F821
+                    _GenericPep695: Enum(enum.Enum),  # noqa: F821
+                    _TypingGenericPep695: Enum(enum.Enum),  # noqa: F821
+                    _GenericPep695Typed: Enum(enum.Enum),  # noqa: F821
+                    _TypingGenericPep695Typed: Enum(enum.Enum),  # noqa: F821
+                }
+            )
+            if type_.recursive:
+                with expect_deprecated(
+                    "Mapping recursive TypeAliasType '.+' that resolve to "
+                    "literal to generate an Enum is deprecated. SQLAlchemy "
+                    "2.1 will not support this use case. Please avoid using "
+                    "recursing TypeAliasType",
+                ):
+                    Foo = declare()
+            elif type_.literal or type_.literal_typing:
+                Foo = declare()
+            else:
+                with expect_raises_message(
+                    exc.ArgumentError,
+                    "Can't associate TypeAliasType '.+' to an Enum "
+                    "since it's not a direct alias of a Literal. Only "
+                    "aliases in this form `type my_alias = Literal.'a', "
+                    "'b'.` are supported when generating Enums.",
+                ):
+                    declare()
+        elif type_.literal or type_.literal_typing:
+            Foo = declare()
+            col = Foo.__table__.c.status
+            is_true(isinstance(col.type, Enum))
+            eq_(col.type.enums, ["to-do", "in-progress", "done"])
+            is_(col.type.native_enum, False)
+        elif type_.not_literal or type_.not_literal_typing:
+            Foo = declare()
+            col = Foo.__table__.c.status
+            is_true(isinstance(col.type, String))
+        elif type_.recursive:
+            with expect_deprecated(
+                "Matching to pep-695 type '_Literal695' in a "
+                "recursive fashion "
+                "without the recursed type being present in the "
+                "type_annotation_map is deprecated; add this type or its "
+                "recursed value to the type_annotation_map to allow it to "
+                "match explicitly."
+            ):
+                Foo = declare()
+        else:
+            with expect_raises_message(
+                orm_exc.MappedAnnotationError,
+                r"Could not locate SQLAlchemy Core type when resolving "
+                r"for Python type "
+                r"indicated by '.+' inside the Mapped\[\] "
+                r"annotation for the 'status' attribute",
+            ):
+                declare()
+            return
+
 
 class MixinTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = "default"
@@ -3947,32 +4101,6 @@ class CompositeTest(fixtures.TestBase, testing.AssertsCompiledSQL):
                     mapped_column(), mapped_column(), mapped_column("zip")
                 )
 
-    def test_extract_from_pep593(self, decl_base):
-        # anno only: global Address
-
-        @dataclasses.dataclass
-        class Address:
-            street: str
-            state: str
-            zip_: str
-
-        class User(decl_base):
-            __tablename__ = "user"
-
-            id: Mapped[int] = mapped_column(primary_key=True)
-            name: Mapped[str] = mapped_column()
-
-            address: Mapped[Annotated[Address, "foo"]] = composite(
-                mapped_column(), mapped_column(), mapped_column("zip")
-            )
-
-        self.assert_compile(
-            select(User),
-            'SELECT "user".id, "user".name, "user".street, '
-            '"user".state, "user".zip FROM "user"',
-            dialect="default",
-        )
-
     def test_cls_not_composite_compliant(self, decl_base):
         # anno only: global Address