mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-04 15:00:02 -04:00
docs
This commit is contained in:
Vendored
+85
@@ -423,6 +423,91 @@ that will disable the feature based on database version detection.
|
||||
|
||||
:ticket:`1068`
|
||||
|
||||
Columns can reliably get their type from a column referred to via ForeignKey
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
There's a long standing behavior which says that a :class:`.Column` can be
|
||||
declared without a type, as long as that :class:`.Column` is referred to
|
||||
by a :class:`.ForeignKeyConstraint`, and the type from the referenced column
|
||||
will be copied into this one. The problem has been that this feature never
|
||||
worked very well and wasn't maintained. The core issue was that the
|
||||
:class:`.ForeignKey` object doesn't know what target :class:`.Column` it
|
||||
refers to until it is asked, typically the first time the foreign key is used
|
||||
to construct a :class:`.Join`. So until that time, the parent :class:`.Column`
|
||||
would not have a type, or more specifically, it would have a default type
|
||||
of :class:`.NullType`.
|
||||
|
||||
While it's taken a long time, the work to reorganize the initialization of
|
||||
:class:`.ForeignKey` objects has been completed such that this feature can
|
||||
finally work acceptably. At the core of the change is that the :attr:`.ForeignKey.column`
|
||||
attribute no longer lazily initializes the location of the target :class:`.Column`;
|
||||
the issue with this system was that the owning :class:`.Column` would be stuck
|
||||
with :class:`.NullType` as its type until the :class:`.ForeignKey` happened to
|
||||
be used.
|
||||
|
||||
In the new version, the :class:`.ForeignKey` coordinates with the eventual
|
||||
:class:`.Column` it will refer to using internal attachment events, so that the
|
||||
moment the referencing :class:`.Column` is associated with the
|
||||
:class:`.MetaData`, all :class:`.ForeignKey` objects that
|
||||
refer to it will be sent a message that they need to initialize their parent
|
||||
column. This system is more complicated but works more solidly; as a bonus,
|
||||
there are now tests in place for a wide variety of :class:`.Column` /
|
||||
:class:`.ForeignKey` configuration scenarios and error messages have been
|
||||
improved to be very specific to no less than seven different error conditions.
|
||||
|
||||
Scenarios which now work correctly include:
|
||||
|
||||
1. The type on a :class:`.Column` is immediately present as soon as the
|
||||
target :class:`.Column` becomes associated with the same :class:`.MetaData`;
|
||||
this works no matter which side is configured first::
|
||||
|
||||
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
|
||||
>>> metadata = MetaData()
|
||||
>>> t2 = Table('t2', metadata, Column('t1id', ForeignKey('t1.id')))
|
||||
>>> t2.c.t1id.type
|
||||
NullType()
|
||||
>>> t1 = Table('t1', metadata, Column('id', Integer, primary_key=True))
|
||||
>>> t2.c.t1id.type
|
||||
Integer()
|
||||
|
||||
2. The system now works with :class:`.ForeignKeyConstraint` as well::
|
||||
|
||||
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKeyConstraint
|
||||
>>> metadata = MetaData()
|
||||
>>> t2 = Table('t2', metadata,
|
||||
... Column('t1a'), Column('t1b'),
|
||||
... ForeignKeyConstraint(['t1a', 't1b'], ['t1.a', 't1.b']))
|
||||
>>> t2.c.t1a.type
|
||||
NullType()
|
||||
>>> t2.c.t1b.type
|
||||
NullType()
|
||||
>>> t1 = Table('t1', metadata,
|
||||
... Column('a', Integer, primary_key=True),
|
||||
... Column('b', Integer, primary_key=True))
|
||||
>>> t2.c.t1a.type
|
||||
Integer()
|
||||
>>> t2.c.t1b.type
|
||||
Integer()
|
||||
|
||||
3. It even works for "multiple hops" - that is, a :class:`.ForeignKey` that refers to a
|
||||
:class:`.Column` that refers to another :class:`.Column`::
|
||||
|
||||
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
|
||||
>>> metadata = MetaData()
|
||||
>>> t2 = Table('t2', metadata, Column('t1id', ForeignKey('t1.id')))
|
||||
>>> t3 = Table('t3', metadata, Column('t2t1id', ForeignKey('t2.t1id')))
|
||||
>>> t2.c.t1id.type
|
||||
NullType()
|
||||
>>> t3.c.t2t1id.type
|
||||
NullType()
|
||||
>>> t1 = Table('t1', metadata, Column('id', Integer, primary_key=True))
|
||||
>>> t2.c.t1id.type
|
||||
Integer()
|
||||
>>> t3.c.t2t1id.type
|
||||
Integer()
|
||||
|
||||
:ticket:`1765`
|
||||
|
||||
Dialect Changes
|
||||
===============
|
||||
|
||||
|
||||
@@ -730,7 +730,7 @@ class Column(SchemaItem, expression.ColumnClause):
|
||||
The ``type`` argument may be the second positional argument
|
||||
or specified by keyword.
|
||||
|
||||
If the ``type`` is ``None``, it will first default to the special
|
||||
If the ``type`` is ``None`` or is omitted, it will first default to the special
|
||||
type :class:`.NullType`. If and when this :class:`.Column` is
|
||||
made to refer to another column using :class:`.ForeignKey`
|
||||
and/or :class:`.ForeignKeyConstraint`, the type of the remote-referenced
|
||||
|
||||
+18
-7
@@ -909,6 +909,8 @@ class Variant(TypeDecorator):
|
||||
|
||||
.. versionadded:: 0.7.2
|
||||
|
||||
.. seealso:: :meth:`.TypeEngine.with_variant` for an example of use.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, base, mapping):
|
||||
@@ -985,14 +987,23 @@ def adapt_type(typeobj, colspecs):
|
||||
class NullType(TypeEngine):
|
||||
"""An unknown type.
|
||||
|
||||
NullTypes will stand in if :class:`~sqlalchemy.Table` reflection
|
||||
encounters a column data type unknown to SQLAlchemy. The
|
||||
resulting columns are nearly fully usable: the DB-API adapter will
|
||||
handle all translation to and from the database data type.
|
||||
:class:`.NullType` is used as a default type for those cases where
|
||||
a type cannot be determined, including:
|
||||
|
||||
NullType does not have sufficient information to particpate in a
|
||||
``CREATE TABLE`` statement and will raise an exception if
|
||||
encountered during a :meth:`~sqlalchemy.Table.create` operation.
|
||||
* During table reflection, when the type of a column is not recognized
|
||||
by the :class:`.Dialect`
|
||||
* When constructing SQL expressions using plain Python objects of
|
||||
unknown types (e.g. ``somecolumn == my_special_object``)
|
||||
* When a new :class:`.Column` is created, and the given type is passed
|
||||
as ``None`` or is not passed at all.
|
||||
|
||||
The :class:`.NullType` can be used within SQL expression invocation
|
||||
without issue, it just has no behavior either at the expression construction
|
||||
level or at the bind-parameter/result processing level. :class:`.NullType`
|
||||
will result in a :class:`.CompileException` if the compiler is asked to render
|
||||
the type itself, such as if it is used in a :func:`.cast` operation
|
||||
or within a schema creation operation such as that invoked by
|
||||
:meth:`.MetaData.create_all` or the :class:`.CreateTable` construct.
|
||||
|
||||
"""
|
||||
__visit_name__ = 'null'
|
||||
|
||||
Reference in New Issue
Block a user