diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-09-19 13:12:08 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-09-19 16:27:51 -0400 |
| commit | 80aeba3d5e0269eb689d991ca0b8e281715113ed (patch) | |
| tree | 0c6096a8b9b1612a3b05ecf06b831d92938b071f /doc | |
| parent | 371f1a82c5981156a359f690923840d2627c9a6f (diff) | |
| download | sqlalchemy-80aeba3d5e0269eb689d991ca0b8e281715113ed.tar.gz | |
- Added a new type-level modifier :meth:`.TypeEngine.evaluates_none`
which indicates to the ORM that a positive set of None should be
persisted as the value NULL, instead of omitting the column from
the INSERT statement. This feature is used both as part of the
implementation for :ticket:`3514` as well as a standalone feature
available on any type. fixes #3250
- add new documentation section illustrating the "how to force null"
use case of #3250
- alter our change from #3514 so that the class-level flag is now
called "should_evaluate_none"; so that "evaluates_none" is now
a generative method.
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/build/changelog/changelog_11.rst | 17 | ||||
| -rw-r--r-- | doc/build/changelog/migration_11.rst | 29 | ||||
| -rw-r--r-- | doc/build/orm/persistence_techniques.rst | 106 |
3 files changed, 149 insertions, 3 deletions
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 2f49a6bdb..e20e0b4ca 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -22,6 +22,21 @@ :version: 1.1.0b1 .. change:: + :tags: bug, orm + :tickets: 3250 + + Added a new type-level modifier :meth:`.TypeEngine.evaluates_none` + which indicates to the ORM that a positive set of None should be + persisted as the value NULL, instead of omitting the column from + the INSERT statement. This feature is used both as part of the + implementation for :ticket:`3514` as well as a standalone feature + available on any type. + + .. seealso:: + + :ref:`change_3250` + + .. change:: :tags: bug, postgresql :tickets: 2729 @@ -278,6 +293,8 @@ :ref:`change_3514` + :ref:`change_3250` + .. change:: :tags: feature, postgresql :tickets: 3514 diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst index 21c976589..81c438e06 100644 --- a/doc/build/changelog/migration_11.rst +++ b/doc/build/changelog/migration_11.rst @@ -16,7 +16,7 @@ What's New in SQLAlchemy 1.1? some issues may be moved to later milestones in order to allow for a timely release. - Document last updated: September 2, 2015 + Document last updated: September 19, 2015 Introduction ============ @@ -233,6 +233,26 @@ relationship attribute to an object, which is handled distinctly:: :ticket:`3321` +.. _change_3250: + +New options allowing explicit persistence of NULL over a default +---------------------------------------------------------------- + +Related to the new JSON-NULL support added to Postgresql as part of +:ref:`change_3514`, the base :class:`.TypeEngine` class now supports +a method :meth:`.TypeEngine.evaluates_none` which allows a positive set +of the ``None`` value on an attribute to be persisted as NULL, rather than +omitting the column from the INSERT statement, which has the effect of using +the column-level default. This allows a mapper-level +configuration of the existing object-level technique of assigning +:func:`.sql.null` to the attribute. + +.. seealso:: + + :ref:`session_forcing_null` + +:ticket:`3250` + New Features and Improvements - Core ==================================== @@ -671,7 +691,8 @@ method were used, ``None`` would be ignored in all cases:: MyObject, [{"json_value": None}]) # would insert SQL NULL and/or trigger defaults -The :class:`.JSON` type now adds a new flag :attr:`.TypeEngine.evaluates_none` +The :class:`.JSON` type now implements the +:attr:`.TypeEngine.should_evaluate_none` flag, indicating that ``None`` should not be ignored here; it is configured automatically based on the value of :paramref:`.JSON.none_as_null`. Thanks to :ticket:`3061`, we can differentiate when the value ``None`` is actively @@ -693,7 +714,9 @@ previously. Below, the two variants are illustrated:: .. seealso:: - :ref:`change_3514_jsonnull` + :ref:`change_3250` + + :ref:`change_3514_jsonnull` .. _change_3514_jsonnull: diff --git a/doc/build/orm/persistence_techniques.rst b/doc/build/orm/persistence_techniques.rst index aee48121d..a30d486b5 100644 --- a/doc/build/orm/persistence_techniques.rst +++ b/doc/build/orm/persistence_techniques.rst @@ -78,6 +78,112 @@ proper context for the desired engine:: connection = session.connection(MyMappedClass) +.. _session_forcing_null: + +Forcing NULL on a column with a default +======================================= + +The ORM considers any attribute that was never set on an object as a +"default" case; the attribute will be omitted from the INSERT statement:: + + class MyObject(Base): + __tablename__ = 'my_table' + id = Column(Integer, primary_key=True) + data = Column(String(50), nullable=True) + + obj = MyObject(id=1) + session.add(obj) + session.commit() # INSERT with the 'data' column omitted; the database + # itself will persist this as the NULL value + +Omitting a column from the INSERT means that the column will +have the NULL value set, *unless* the column has a default set up, +in which case the default value will be persisted. This holds true +both from a pure SQL perspective with server-side defaults, as well as the +behavior of SQLAlchemy's insert behavior with both client-side and server-side +defaults:: + + class MyObject(Base): + __tablename__ = 'my_table' + id = Column(Integer, primary_key=True) + data = Column(String(50), nullable=True, server_default="default") + + obj = MyObject(id=1) + session.add(obj) + session.commit() # INSERT with the 'data' column omitted; the database + # itself will persist this as the value 'default' + +However, in the ORM, even if one assigns the Python value ``None`` explicitly +to the object, this is treated the **same** as though the value were never +assigned:: + + class MyObject(Base): + __tablename__ = 'my_table' + id = Column(Integer, primary_key=True) + data = Column(String(50), nullable=True, server_default="default") + + obj = MyObject(id=1, data=None) + session.add(obj) + session.commit() # INSERT with the 'data' column explicitly set to None; + # the ORM still omits it from the statement and the + # database will still persist this as the value 'default' + +The above operation will persist into the ``data`` column the +server default value of ``"default"`` and not SQL NULL, even though ``None`` +was passed; this is a long-standing behavior of the ORM that many applications +hold as an assumption. + +So what if we want to actually put NULL into this column, even though the +column has a default value? There are two approaches. One is that +on a per-instance level, we assign the attribute using the +:obj:`~.expression.null` SQL construct:: + + from sqlalchemy import null + + obj = MyObject(id=1, data=null()) + session.add(obj) + session.commit() # INSERT with the 'data' column explicitly set as null(); + # the ORM uses this directly, bypassing all client- + # and server-side defaults, and the database will + # persist this as the NULL value + +The :obj:`~.expression.null` SQL construct always translates into the SQL +NULL value being directly present in the target INSERT statement. + +If we'd like to be able to use the Python value ``None`` and have this +also be persisted as NULL despite the presence of column defaults, +we can configure this for the ORM using a Core-level modifier +:meth:`.TypeEngine.evaluates_none`, which indicates +a type where the ORM should treat the value ``None`` the same as any other +value and pass it through, rather than omitting it as a "missing" value:: + + class MyObject(Base): + __tablename__ = 'my_table' + id = Column(Integer, primary_key=True) + data = Column( + String(50).evaluates_none(), # indicate that None should always be passed + nullable=True, server_default="default") + + obj = MyObject(id=1, data=None) + session.add(obj) + session.commit() # INSERT with the 'data' column explicitly set to None; + # the ORM uses this directly, bypassing all client- + # and server-side defaults, and the database will + # persist this as the NULL value + +.. topic:: Evaluating None + + The :meth:`.TypeEngine.evaluates_none` modifier is primarily intended to + signal a type where the Python value "None" is significant, the primary + example being a JSON type which may want to persist the JSON ``null`` value + rather than SQL NULL. We are slightly repurposing it here in order to + signal to the ORM that we'd like ``None`` to be passed into the type whenever + present, even though no special type-level behaviors are assigned to it. + +.. versionadded:: 1.1 added the :meth:`.TypeEngine.evaluates_none` method + in order to indicate that a "None" value should be treated as significant. + + .. _session_partitioning: Partitioning Strategies |
