]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
improve typing intro
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 8 Aug 2022 18:50:29 +0000 (14:50 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 8 Aug 2022 18:51:01 +0000 (14:51 -0400)
Fixes: #8362
Change-Id: I38aa1727e94c50a9f06bd75d57ea1ca1cfffd2f3
(cherry picked from commit ac1d775d862e62561effcce54462f687dd79b8bb)

doc/build/core/metadata.rst
doc/build/core/type_basics.rst

index 701146195a76e7e3bfb3cbe3801a215128549e76..03721c2b6c3b208ee1676cc2fb38dfbb789d628a 100644 (file)
@@ -34,7 +34,11 @@ primary arguments are the table name, then the
 The remaining positional arguments are mostly
 :class:`~sqlalchemy.schema.Column` objects describing each column::
 
-    user = Table('user', metadata_obj,
+    from sqlalchemy import Table, Column, Integer, String
+
+    user = Table(
+        'user',
+        metadata_obj,
         Column('user_id', Integer, primary_key=True),
         Column('user_name', String(16), nullable=False),
         Column('email_address', String(60)),
index 069214f99b99cef25f18d15ce9276bfdfa574c00..52debc32f9a91f019a8b021534dc2ad1df41c394 100644 (file)
@@ -4,31 +4,172 @@ Column and Data Types
 .. module:: sqlalchemy.types
 
 SQLAlchemy provides abstractions for most common database data types,
-and a mechanism for specifying your own custom data types.
+as well as several techniques for customization of datatypes.
+
+Database types are represented using Python classes, all of which ultimately
+extend from the base type class known as :class:`_types.TypeEngine`. There are
+two general categories of datatypes, each of which express themselves within
+the typing hierarchy in different ways. The category used by an individual
+datatype class can be identified based on the use of two different naming
+conventions, which are "CamelCase" and "UPPERCASE".
+
+.. seealso::
+
+    :ref:`tutorial_core_metadata` - in the :ref:`unified_tutorial`.  Illustrates
+    the most rudimental use of :class:`_types.TypeEngine` type objects to
+    define :class:`_schema.Table` metadata and introduces the concept
+    of type objects in tutorial form.
+
+The "CamelCase" datatypes
+-------------------------
+
+The rudimental types have "CamelCase" names such as :class:`_types.String`,
+:class:`_types.Numeric`, :class:`_types.Integer`, and :class:`_types.DateTime`.
+All of the immediate subclasses of :class:`_types.TypeEngine` are
+"CamelCase" types. The "CamelCase" types are to the greatest degree possible
+**database agnostic**, meaning they can all be used on any database backend
+where they will behave in such a way as appropriate to that backend in order to
+produce the desired behavior.
+
+An example of a straightforward "CamelCase" datatype is :class:`_types.String`.
+On most backends, using this datatype in a
+:ref:`table specification <metadata_describing>` will correspond to the
+``VARCHAR`` database type being used on the target backend, delivering string
+values to and from the database, as in the example below::
+
+    from sqlalchemy import MetaData
+    from sqlalchemy import Table, Column, Integer, String
+
+    metadata_obj = MetaData()
+
+    user = Table(
+        'user',
+        metadata_obj,
+        Column('user_name', String, primary_key=True),
+        Column('email_address', String(60)),
+    )
+
+When using a particular :class:`_types.TypeEngine` class in a
+:class:`_schema.Table` definition or in any SQL expression overall, if no
+arguments are required it may be passed as the class itself, that is, without
+instantiating it with ``()``. If arguments are needed, such as the length
+argument of 60 in the ``"email_address"`` column above, the type may be
+instantiated.
+
+Another "CamelCase" datatype that expresses more backend-specific behavior
+is the :class:`_types.Boolean` datatype. Unlike :class:`_types.String`,
+which represents a string datatype that all databases have,
+not every backend has a real "boolean" datatype; some make use of integers
+or BIT values 0 and 1, some have boolean literal constants ``true`` and
+``false`` while others dont.   For this datatype, :class:`_types.Boolean`
+may render ``BOOLEAN`` on a backend such as PostgreSQL, ``BIT`` on the
+MySQL backend and ``SMALLINT`` on Oracle.  As data is sent and received
+from the database using this type, based on the dialect in use it may be
+interpreting Python numeric or boolean values.
+
+The typical SQLAlchemy application will likely wish to use primarily
+"CamelCase" types in the general case, as they will generally provide the best
+basic behavior and be automatically portable to all backends.
+
+Reference for the general set of "CamelCase" datatypes is below at
+:ref:`types_generic`.
+
+The "UPPERCASE" datatypes
+-------------------------
+
+In contrast to the "CamelCase" types are the "UPPERCASE" datatypes. These
+datatypes are always inherited from a particular "CamelCase" datatype, and
+always represent an **exact** datatype.   When using an "UPPERCASE" datatype,
+the name of the type is always rendered exactly as given, without regard for
+whether or not the current backend supports it.   Therefore the use
+of "UPPERCASE" types in a SQLAlchemy application indicates that specific
+datatypes are required, which then implies that the application would normally,
+without additional steps taken,
+be limited to those backends which use the type exactly as given.   Examples
+of UPPERCASE types include :class:`_types.VARCHAR`, :class:`_types.NUMERIC`,
+:class:`_types.INTEGER`, and :class:`_types.TIMESTAMP`, which inherit directly
+from the previously mentioned "CamelCase" types
+:class:`_types.String`,
+:class:`_types.Numeric`, :class:`_types.Integer`, and :class:`_types.DateTime`,
+respectively.
+
+The "UPPERCASE" datatypes that are part of ``sqlalchemy.types`` are common
+SQL types that typically expect to be available on at least two backends
+if not more.
+
+Reference for the general set of "UPPERCASE" datatypes is below at
+:ref:`types_sqlstandard`.
+
+
+
+.. _types_vendor:
+
+Backend-specific "UPPERCASE" datatypes
+--------------------------------------
 
-The methods and attributes of type objects are rarely used directly.
-Type objects are supplied to :class:`~sqlalchemy.schema.Table` definitions
-and can be supplied as type hints to `functions` for occasions where
-the database driver returns an incorrect type.
+Most databases also have their own datatypes that
+are either fully specific to those databases, or add additional arguments
+that are specific to those databases.   For these datatypes, specific
+SQLAlchemy dialects provide **backend-specific** "UPPERCASE" datatypes, for a
+SQL type that has no analogue on other backends.  Examples of backend-specific
+uppercase datatypes include PostgreSQL's :class:`_postgresql.JSONB`, SQL Server's
+:class:`_mssql.IMAGE` and MySQL's :class:`_mysql.TINYTEXT`.
+
+Specific backends may also include "UPPERCASE" datatypes that extend the
+arguments available from that same "UPPERCASE" datatype as found in the
+``sqlalchemy.types`` module. An example is when creating a MySQL string
+datatype, one might want to specify MySQL-specific arguments such as ``charset``
+or ``national``, which are available from the MySQL version
+of :class:`_mysql.VARCHAR` as the MySQL-only parameters
+:paramref:`_mysql.VARCHAR.charset` and :paramref:`_mysql.VARCHAR.national`.
+
+API documentation for backend-specific types are in the dialect-specific
+documentation, listed at :ref:`dialect_toplevel`.
+
+
+Using "UPPERCASE" and Backend-specific types for multiple backends
+------------------------------------------------------------------
+
+Reviewing the presence of "UPPERCASE" and "CamelCase" types leads to the natural
+use case of how to make use of "UPPERCASE" datatypes for backend-specific
+options, but only when that backend is in use.   To tie together the
+database-agnostic "CamelCase" and backend-specific "UPPERCASE" systems, one
+makes use of the :meth:`_types.TypeEngine.with_variant` method in order to
+**compose** types together to work with specific behaviors on specific backends.
+
+Such as, to use the :class:`_types.String` datatype, but when running on MySQL
+to make use of the :paramref:`_mysql.VARCHAR.charset` parameter of
+:class:`_mysql.VARCHAR` when the table is created on MySQL or MariaDB,
+:meth:`_types.TypeEngine.with_variant` may be used as below::
+
+    from sqlalchemy import MetaData
+    from sqlalchemy import Table, Column, Integer, String
+    from sqlalchemy.dialects.mysql import VARCHAR
+
+    metadata_obj = MetaData()
+
+    user = Table(
+        "user",
+        metadata_obj,
+        Column("user_name", String, primary_key=True),
+        Column(
+            "bio",
+            String(255).with_variant(VARCHAR(255, charset="utf-8"), "mysql", "mariadb"),
+        ),
+    )
 
-.. code-block:: pycon
+In the above table definition, the ``"bio"`` column will have string-behaviors
+on all backends.  On most backends it will render in DDL as ``VARCHAR``.
+However on MySQL and MariaDB, it will render as ``VARCHAR(255) CHARACTER SET utf-8``.
 
-  >>> users = Table('users', metadata,
-  ...               Column('id', Integer, primary_key=True),
-  ...               Column('login', String(32))
-  ...              )
+.. seealso::
 
-SQLAlchemy will use the ``Integer`` and ``String(32)`` type
-information when issuing a ``CREATE TABLE`` statement and will use it
-again when reading back rows ``SELECTed`` from the database.
-Functions that accept a type (such as :func:`~sqlalchemy.schema.Column`) will
-typically accept a type class or instance; ``Integer`` is equivalent
-to ``Integer()`` with no construction arguments in this case.
+    :meth:`_types.TypeEngine.with_variant` - additional usage examples and notes
 
 .. _types_generic:
 
-Generic Types
--------------
+Generic "CamelCase" Types
+-------------------------
 
 Generic types specify a column that can read, write and store a
 particular type of Python data.  SQLAlchemy will choose the best
@@ -97,8 +238,8 @@ type is emitted in ``CREATE TABLE``, such as ``VARCHAR`` see
 
 .. _types_sqlstandard:
 
-SQL Standard and Multiple Vendor Types
---------------------------------------
+SQL Standard and Multiple Vendor "UPPERCASE" Types
+--------------------------------------------------
 
 This category of types refers to types that are either part of the
 SQL standard, or are potentially found within a subset of database backends.
@@ -181,59 +322,3 @@ its exact name in DDL with ``CREATE TABLE`` is issued.
 .. autoclass:: VARCHAR
 
 
-.. _types_vendor:
-
-Vendor-Specific Types
----------------------
-
-Database-specific types are also available for import from each
-database's dialect module. See the :ref:`dialect_toplevel`
-reference for the database you're interested in.
-
-For example, MySQL has a ``BIGINT`` type and PostgreSQL has an
-``INET`` type.  To use these, import them from the module explicitly::
-
-    from sqlalchemy.dialects import mysql
-
-    table = Table('foo', metadata,
-        Column('id', mysql.BIGINT),
-        Column('enumerates', mysql.ENUM('a', 'b', 'c'))
-    )
-
-Or some PostgreSQL types::
-
-    from sqlalchemy.dialects import postgresql
-
-    table = Table('foo', metadata,
-        Column('ipaddress', postgresql.INET),
-        Column('elements', postgresql.ARRAY(String))
-    )
-
-Each dialect provides the full set of database types supported by
-that backend within its own module, so they may all be used
-against the module directly without the need to differentiate between
-which types are specific to that backend or not::
-
-    from sqlalchemy.dialects import postgresql
-
-    t = Table('mytable', metadata,
-               Column('id', postgresql.INTEGER, primary_key=True),
-               Column('name', postgresql.VARCHAR(300)),
-               Column('inetaddr', postgresql.INET)
-    )
-
-Where above, the INTEGER and VARCHAR types are ultimately from
-sqlalchemy.types, and INET is specific to the PostgreSQL dialect.
-
-Some dialect level types have the same name as the SQL standard type,
-but also provide additional arguments.  For example, MySQL implements
-the full range of character and string types including additional arguments
-such as `collation` and `charset`::
-
-    from sqlalchemy.dialects.mysql import VARCHAR, TEXT
-
-    table = Table('foo', metadata_obj,
-        Column('col1', VARCHAR(200, collation='binary')),
-        Column('col2', TEXT(charset='latin1'))
-    )
-