summaryrefslogtreecommitdiff
path: root/doc/build/tutorial/metadata.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/build/tutorial/metadata.rst')
-rw-r--r--doc/build/tutorial/metadata.rst526
1 files changed, 526 insertions, 0 deletions
diff --git a/doc/build/tutorial/metadata.rst b/doc/build/tutorial/metadata.rst
new file mode 100644
index 000000000..969536c01
--- /dev/null
+++ b/doc/build/tutorial/metadata.rst
@@ -0,0 +1,526 @@
+.. |prev| replace:: :doc:`dbapi_transactions`
+.. |next| replace:: :doc:`data`
+
+.. include:: tutorial_nav_include.rst
+
+.. _tutorial_working_with_metadata:
+
+Working with Database Metadata
+==============================
+
+With engines and SQL execution down, we are ready to begin some Alchemy.
+The central element of both SQLAlchemy Core and ORM is the SQL Expression
+Language which allows for fluent, composable construction of SQL queries.
+The foundation for these queries are Python objects that represent database
+concepts like tables and columns. These objects are known collectively
+as :term:`database metadata`.
+
+The most common foundational objects for database metadata in SQLAlchemy are
+known as :class:`_schema.MetaData`, :class:`_schema.Table`, and :class:`_schema.Column`.
+The sections below will illustrate how these objects are used in both a
+Core-oriented style as well as an ORM-oriented style.
+
+.. container:: orm-header
+
+ **ORM readers, stay with us!**
+
+ As with other sections, Core users can skip the ORM sections, but ORM users
+ would best be familiar with these objects from both perspectives.
+
+
+.. rst-class:: core-header
+
+.. _tutorial_core_metadata:
+
+Setting up MetaData with Table objects
+---------------------------------------
+
+When we work with a relational database, the basic structure that we create and
+query from is known as a **table**. In SQLAlchemy, the "table" is represented
+by a Python object similarly named :class:`_schema.Table`.
+
+To start using the SQLAlchemy Expression Language,
+we will want to have :class:`_schema.Table` objects constructed that represent
+all of the database tables we are interested in working with. Each
+:class:`_schema.Table` may be **declared**, meaning we explicitly spell out
+in source code what the table looks like, or may be **reflected**, which means
+we generate the object based on what's already present in a particular database.
+The two approaches can also be blended in many ways.
+
+Whether we will declare or reflect our tables, we start out with a collection
+that will be where we place our tables known as the :class:`_schema.MetaData`
+object. This object is essentially a :term:`facade` around a Python dictionary
+that stores a series of :class:`_schema.Table` objects keyed to their string
+name. Constructing this object looks like::
+
+ >>> from sqlalchemy import MetaData
+ >>> metadata = MetaData()
+
+Having a single :class:`_schema.MetaData` object for an entire application is
+the most common case, represented as a module-level variable in a single place
+in an application, often in a "models" or "dbschema" type of package. There
+can be multiple :class:`_schema.MetaData` collections as well, however
+it's typically most helpful if a series :class:`_schema.Table` objects that are
+related to each other belong to a single :class:`_schema.MetaData` collection.
+
+
+Once we have a :class:`_schema.MetaData` object, we can declare some
+:class:`_schema.Table` objects. This tutorial will start with the classic
+SQLAlchemy tutorial model, that of the table ``user``, which would for
+example represent the users of a website, and the table ``address``,
+representing a list of email addresses associated with rows in the ``user``
+table. We normally assign each :class:`_schema.Table` object to a variable
+that will be how we will refer to the table in application code::
+
+ >>> from sqlalchemy import Table, Column, Integer, String
+ >>> user_table = Table(
+ ... "user_account",
+ ... metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('name', String(30)),
+ ... Column('fullname', String)
+ ... )
+
+We can observe that the above :class:`_schema.Table` construct looks a lot like
+a SQL CREATE TABLE statement; starting with the table name, then listing out
+each column, where each column has a name and a datatype. The objects we
+use above are:
+
+* :class:`_schema.Table` - represents a database table and assigns itself
+ to a :class:`_schema.MetaData` collection.
+
+* :class:`_schema.Column` - represents a column in a database table, and
+ assigns itself to a :class:`_schema.Table` object. The :class:`_schema.Column`
+ usually includes a string name and a type object. The collection of
+ :class:`_schema.Column` objects in terms of the parent :class:`_schema.Table`
+ are typically accessed via an associative array located at :attr:`_schema.Table.c`::
+
+ >>> user_table.c.name
+ Column('name', String(length=30), table=<user_account>)
+
+ >>> user_table.c.keys()
+ ['id', 'name', 'fullname']
+
+* :class:`_types.Integer`, :class:`_types.String` - these classes represent
+ SQL datatypes and can be passed to a :class:`_schema.Column` with or without
+ necessarily being instantiated. Above, we want to give a length of "30" to
+ the "name" column, so we instantiated ``String(30)``. But for "id" and
+ "fullname" we did not specify these, so we can send the class itself.
+
+.. seealso::
+
+ The reference and API documentation for :class:`_schema.MetaData`,
+ :class:`_schema.Table` and :class:`_schema.Column` is at :ref:`metadata_toplevel`.
+ The reference documentation for datatypes is at :ref:`types_toplevel`.
+
+In an upcoming section, we will illustrate one of the fundamental
+functions of :class:`_schema.Table` which
+is to generate :term:`DDL` on a particular database connection. But first
+we will declare a second :class:`_schema.Table`.
+
+.. rst-class:: core-header
+
+Declaring Simple Constraints
+-----------------------------
+
+The first :class:`_schema.Column` in the above ``user_table`` includes the
+:paramref:`_schema.Column.primary_key` parameter which is a shorthand technique
+of indicating that this :class:`_schema.Column` should be part of the primary
+key for this table. The primary key itself is normally declared implicitly
+and is represented by the :class:`_schema.PrimaryKeyConstraint` construct,
+which we can see on the :attr:`_schema.Table.primary_key`
+attribute on the :class:`_schema.Table` object::
+
+ >>> user_table.primary_key
+ PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
+
+The constraint that is most typically declared explicitly is the
+:class:`_schema.ForeignKeyConstraint` object that corresponds to a database
+:term:`foreign key constraint`. When we declare tables that are related to
+each other, SQLAlchemy uses the presence of these foreign key constraint
+declarations not only so that they are emitted within CREATE statements to
+the database, but also to assist in constructing SQL expressions.
+
+A :class:`_schema.ForeignKeyConstraint` that involves only a single column
+on the target table is typically declared using a column-level shorthand notation
+via the :class:`_schema.ForeignKey` object. Below we declare a second table
+``address`` that will have a foreign key constraint referring to the ``user``
+table::
+
+ >>> from sqlalchemy import ForeignKey
+ >>> address_table = Table(
+ ... "address",
+ ... metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('user_id', ForeignKey('user_account.id'), nullable=False),
+ ... Column('email_address', String, nullable=False)
+ ... )
+
+The table above also features a third kind of constraint, which in SQL is the
+"NOT NULL" constraint, indicated above using the :paramref:`_schema.Column.nullable`
+parameter.
+
+.. tip:: When using the :class:`_schema.ForeignKey` object within a
+ :class:`_schema.Column` definition, we can omit the datatype for that
+ :class:`_schema.Column`; it is automatically inferred from that of the
+ related column, in the above example the :class:`_types.Integer` datatype
+ of the ``user_account.id`` column.
+
+In the next section we will emit the completed DDL for the ``user`` and
+``address`` table to see the completed result.
+
+.. rst-class:: core-header, orm-dependency
+
+
+.. _tutorial_emitting_ddl:
+
+Emitting DDL to the Database
+----------------------------
+
+We've constructed a fairly elaborate object hierarchy to represent
+two database tables, starting at the root :class:`_schema.MetaData`
+object, then into two :class:`_schema.Table` objects, each of which hold
+onto a collection of :class:`_schema.Column` and :class:`_schema.Constraint`
+objects. This object structure will be at the center of most operations
+we perform with both Core and ORM going forward.
+
+The first useful thing we can do with this structure will be to emit CREATE
+TABLE statements, or :term:`DDL`, to our SQLite database so that we can insert
+and query data from them. We have already all the tools needed to do so, by
+invoking the
+:meth:`_schema.MetaData.create_all` method on our :class:`_schema.MetaData`,
+sending it the :class:`_future.Engine` that refers to the target database:
+
+.. sourcecode:: pycon+sql
+
+ >>> metadata.create_all(engine)
+ {opensql}BEGIN (implicit)
+ PRAGMA main.table_...info("user_account")
+ ...
+ PRAGMA main.table_...info("address")
+ ...
+ CREATE TABLE user_account (
+ id INTEGER NOT NULL,
+ name VARCHAR(30),
+ fullname VARCHAR,
+ PRIMARY KEY (id)
+ )
+ ...
+ CREATE TABLE address (
+ id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ email_address VARCHAR NOT NULL,
+ PRIMARY KEY (id),
+ FOREIGN KEY(user_id) REFERENCES user_account (id)
+ )
+ ...
+ COMMIT
+
+The DDL create process by default includes some SQLite-specific PRAGMA statements
+that test for the existence of each table before emitting a CREATE. The full
+series of steps are also included within a BEGIN/COMMIT pair to accommodate
+for transactional DDL (SQLite does actually support transactional DDL, however
+the ``sqlite3`` database driver historically runs DDL in "autocommit" mode).
+
+The create process also takes care of emitting CREATE statements in the correct
+order; above, the FOREIGN KEY constraint is dependent on the ``user`` table
+existing, so the ``address`` table is created second. In more complicated
+dependency scenarios the FOREIGN KEY constraints may also be applied to tables
+after the fact using ALTER.
+
+The :class:`_schema.MetaData` object also features a
+:meth:`_schema.MetaData.drop_all` method that will emit DROP statements in the
+reverse order as it would emit CREATE in order to drop schema elements.
+
+.. topic:: Migration tools are usually appropriate
+
+ Overall, the CREATE / DROP feature of :class:`_schema.MetaData` is useful
+ for test suites, small and/or new applications, and applications that use
+ short-lived databases. For management of an application database schema
+ over the long term however, a schema management tool such as `Alembic
+ <https://alembic.sqlalchemy.org>`_, which builds upon SQLAlchemy, is likely
+ a better choice, as it can manage and orchestrate the process of
+ incrementally altering a fixed database schema over time as the design of
+ the application changes.
+
+
+.. rst-class:: orm-header
+
+.. _tutorial_orm_table_metadata:
+
+Defining Table Metadata with the ORM
+------------------------------------
+
+This ORM-only section will provide an example of the declaring the
+same database structure illustrated in the previous section, using a more
+ORM-centric configuration paradigm. When using
+the ORM, the process by which we declare :class:`_schema.Table` metadata
+is usually combined with the process of declaring :term:`mapped` classes.
+The mapped class is any Python class we'd like to create, which will then
+have attributes on it that will be linked to the columns in a database table.
+While there are a few varieties of how this is achieved, the most common
+style is known as
+:ref:`declarative <orm_declarative_mapper_config_toplevel>`, and allows us
+to declare our user-defined classes and :class:`_schema.Table` metadata
+at once.
+
+Setting up the Registry
+^^^^^^^^^^^^^^^^^^^^^^^
+
+When using the ORM, the :class:`_schema.MetaData` collection remains present,
+however it itself is contained within an ORM-only object known as the
+:class:`_orm.registry`. We create a :class:`_orm.registry` by constructing
+it::
+
+ >>> from sqlalchemy.orm import registry
+ >>> mapper_registry = registry()
+
+The above :class:`_orm.registry`, when constructed, automatically includes
+a :class:`_schema.MetaData` object that will store a collection of
+:class:`_schema.Table` objects::
+
+ >>> mapper_registry.metadata
+ MetaData()
+
+Instead of declaring :class:`_schema.Table` objects directly, we will now
+declare them indirectly through directives applied to our mapped classes. In
+the most common approach, each mapped class descends from a common base class
+known as the **declarative base**. We get a new declarative base from the
+:class:`_orm.registry` using the :meth:`_orm.registry.generate_base` method::
+
+ >>> Base = mapper_registry.generate_base()
+
+.. tip::
+
+ The steps of creating the :class:`_orm.registry` and "declarative base"
+ classes can be combined into one step using the historically familiar
+ :func:`_orm.declarative_base` function::
+
+ from sqlalchemy.orm import declarative_base
+ Base = declarative_base()
+
+ ..
+
+.. _tutorial_declaring_mapped_classes:
+
+Declaring Mapped Classes
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``Base`` object above is a Python class which will serve as the base class
+for the ORM mapped classes we declare. We can now define ORM mapped classes
+for the ``user`` and ``address`` table in terms of new classes ``User`` and
+``Address``::
+
+ >>> from sqlalchemy.orm import relationship
+ >>> class User(Base):
+ ... __tablename__ = 'user_account'
+ ...
+ ... id = Column(Integer, primary_key=True)
+ ... name = Column(String(30))
+ ... fullname = Column(String)
+ ...
+ ... addresses = relationship("Address", back_populates="user")
+ ...
+ ... def __repr__(self):
+ ... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
+
+ >>> class Address(Base):
+ ... __tablename__ = 'address'
+ ...
+ ... id = Column(Integer, primary_key=True)
+ ... email_address = Column(String, nullable=False)
+ ... user_id = Column(Integer, ForeignKey('user_account.id'))
+ ...
+ ... user = relationship("User", back_populates="addresses")
+ ...
+ ... def __repr__(self):
+ ... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
+
+The above two classes are now our mapped classes, and are available for use in
+ORM persistence and query operations, which will be described later. But they
+also include :class:`_schema.Table` objects that were generated as part of the
+declarative mapping process, and are equivalent to the ones that we declared
+directly in the previous Core section. We can see these
+:class:`_schema.Table` objects from a declarative mapped class using the
+``.__table__`` attribute::
+
+ >>> User.__table__
+ Table('user_account', MetaData(),
+ Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
+ Column('name', String(length=30), table=<user_account>),
+ Column('fullname', String(), table=<user_account>), schema=None)
+
+This :class:`_schema.Table` object was generated from the declarative process
+based on the ``.__tablename__`` attribute defined on each of our classes,
+as well as through the use of :class:`_schema.Column` objects assigned
+to class-level attributes within the classes. These :class:`_schema.Column`
+objects can usually be declared without an explicit "name" field inside
+the constructor, as the Declarative process will name them automatically
+based on the attribute name that was used.
+
+Other Mapped Class Details
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For a few quick explanations for the classes above, note the following
+attributes:
+
+* **the classes have an automatically generated __init__() method** - both classes by default
+ receive an ``__init__()`` method that allows for parameterized construction
+ of the objects. We are free to provide our own ``__init__()`` method as well.
+ The ``__init__()`` allows us to create instances of ``User`` and ``Address``
+ passing attribute names, most of which above are linked directly to
+ :class:`_schema.Column` objects, as parameter names::
+
+ >>> sandy = User(name="sandy", fullname="Sandy Cheeks")
+
+ More detail on this method is at :ref:`mapped_class_default_constructor`.
+
+ ..
+
+* **we provided a __repr__() method** - this is **fully optional**, and is
+ strictly so that our custom classes have a descriptive string representation
+ and is not otherwise required::
+
+ >>> sandy
+ User(id=None, name='sandy', fullname='Sandy Cheeks')
+
+ ..
+
+ An interesting thing to note above is that the ``id`` attribute automatically
+ returns ``None`` when accessed, rather than raising ``AttributeError`` as
+ would be the usual Python behavior for missing attributes.
+
+* **we also included a bidirectional relationship** - this is another **fully optional**
+ construct, where we made use of an ORM construct called
+ :func:`_orm.relationship` on both classes, which indicates to the ORM that
+ these ``User`` and ``Address`` classes refer to each other in a :term:`one to
+ many` / :term:`many to one` relationship. The use of
+ :func:`_orm.relationship` above is so that we may demonstrate its behavior
+ later in this tutorial; it is **not required** in order to define the
+ :class:`_schema.Table` structure.
+
+
+Emitting DDL to the database
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This section is named the same as the section :ref:`tutorial_emitting_ddl`
+discussed in terms of Core. This is because emitting DDL with our
+ORM mapped classes is not any different. If we wanted to emit DDL
+for the :class:`_schema.Table` objects we've created as part of
+our declaratively mapped classes, we still can use
+:meth:`_schema.MetaData.create_all` as before.
+
+In our case, we have already generated the ``user`` and ``address`` tables
+in our SQLite database. If we had not done so already, we would be free to
+make use of the :class:`_schema.MetaData` associated with our
+:class:`_orm.registry` and ORM declarative base class in order to do so,
+using :meth:`_schema.MetaData.create_all`::
+
+ # emit CREATE statements given ORM registry
+ mapper_registry.metadata.create_all(engine)
+
+ # the identical MetaData object is also present on the
+ # declarative base
+ Base.metadata.create_all(engine)
+
+
+Combining Core Table Declarations with ORM Declarative
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As an alternative approach to the mapping process shown previously
+at :ref:`tutorial_declaring_mapped_classes`, we may also make
+use of the :class:`_schema.Table` objects we created directly in the section
+:ref:`tutorial_core_metadata` in conjunction with
+declarative mapped classes from a :func:`_orm.declarative_base` generated base
+class.
+
+This form is called :ref:`hybrid table <orm_imperative_table_configuration>`,
+and it consists of assigning to the ``.__table__`` attribute directly, rather
+than having the declarative process generate it::
+
+ class User(Base):
+ __table__ = user_table
+
+ addresses = relationship("Address", back_populates="user")
+
+ def __repr__(self):
+ return f"User({self.name!r}, {self.fullname!r})"
+
+ class Address(Base):
+ __table__ = address_table
+
+ user = relationship("User", back_populates="addresses")
+
+ def __repr__(self):
+ return f"Address({self.email_address!r})"
+
+The above two classes are equivalent to those which we declared in the
+previous mapping example.
+
+The traditional "declarative base" approach using ``__tablename__`` to
+automatically generate :class:`_schema.Table` objects remains the most popular
+method to declare table metadata. However, disregarding the ORM mapping
+functionality it achieves, as far as table declaration it's merely a syntactical
+convenience on top of the :class:`_schema.Table` constructor.
+
+We will next refer to our ORM mapped classes above when we talk about data
+manipulation in terms of the ORM, in the section :ref:`tutorial_inserting_orm`.
+
+
+.. rst-class:: core-header
+
+.. _tutorial_table_reflection:
+
+Table Reflection
+-------------------------------
+
+To round out the section on working with table metadata, we will illustrate
+another operation that was mentioned at the beginning of the section,
+that of **table reflection**. Table reflection refers to the process of
+generating :class:`_schema.Table` and related objects by reading the current
+state of a database. Whereas in the previous sections we've been declaring
+:class:`_schema.Table` objects in Python and then emitting DDL to the database,
+the reflection process does it in reverse.
+
+As an example of reflection, we will create a new :class:`_schema.Table`
+object which represents the ``some_table`` object we created manually in
+the earler sections of this document. There are again some varieties of
+how this is performed, however the most basic is to construct a
+:class:`_schema.Table` object, given the name of the table and a
+:class:`_schema.MetaData` collection to which it will belong, then
+instead of indicating individual :class:`_schema.Column` and
+:class:`_schema.Constraint` objects, pass it the target :class:`_future.Engine`
+using the :paramref:`_schema.Table.autoload_with` parameter:
+
+.. sourcecode:: pycon+sql
+
+ >>> some_table = Table("some_table", metadata, autoload_with=engine)
+ {opensql}BEGIN (implicit)
+ PRAGMA main.table_...info("some_table")
+ [raw sql] ()
+ SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type = 'table'
+ [raw sql] ('some_table',)
+ PRAGMA main.foreign_key_list("some_table")
+ ...
+ PRAGMA main.index_list("some_table")
+ ...
+ ROLLBACK{stop}
+
+At the end of the process, the ``some_table`` object now contains the
+information about the :class:`_schema.Column` objects present in the table, and
+the object is usable in exactly the same way as a :class:`_schema.Table` that
+we declared explicitly.::
+
+ >>> some_table
+ Table('some_table', MetaData(),
+ Column('x', INTEGER(), table=<some_table>),
+ Column('y', INTEGER(), table=<some_table>),
+ schema=None)
+
+.. seealso::
+
+ Read more about table and schema reflection at :ref:`metadata_reflection_toplevel`.
+
+ For ORM-related variants of table reflection, the section
+ :ref:`orm_declarative_reflected` includes an overview of the available
+ options.