From: Mike Bayer Date: Thu, 12 Dec 2013 01:19:56 +0000 (-0500) Subject: move things that are 90% behavioral improvements to that section. the list of things X-Git-Tag: rel_0_9_0~37^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9bef2001a604b520a82abc65cf95790211d36106;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git move things that are 90% behavioral improvements to that section. the list of things that can definitely people should be more focused. --- diff --git a/doc/build/changelog/migration_09.rst b/doc/build/changelog/migration_09.rst index cb658903a7..a8be06fda5 100644 --- a/doc/build/changelog/migration_09.rst +++ b/doc/build/changelog/migration_09.rst @@ -157,76 +157,6 @@ to 0.9 without issue. :ticket:`2736` -.. _migration_2789: - -Backref handlers can now propagate more than one level deep ------------------------------------------------------------ - -The mechanism by which attribute events pass along their "initiator", that is -the object associated with the start of the event, has been changed; instead -of a :class:`.AttributeImpl` being passed, a new object :class:`.attributes.Event` -is passed instead; this object refers to the :class:`.AttributeImpl` as well as -to an "operation token", representing if the operation is an append, remove, -or replace operation. - -The attribute event system no longer looks at this "initiator" object in order to halt a -recursive series of attribute events. Instead, the system of preventing endless -recursion due to mutually-dependent backref handlers has been moved -to the ORM backref event handlers specifically, which now take over the role -of ensuring that a chain of mutually-dependent events (such as append to collection -A.bs, set many-to-one attribute B.a in response) doesn't go into an endless recursion -stream. The rationale here is that the backref system, given more detail and control -over event propagation, can finally allow operations more than one level deep -to occur; the typical scenario is when a collection append results in a many-to-one -replacement operation, which in turn should cause the item to be removed from a -previous collection:: - - class Parent(Base): - __tablename__ = 'parent' - - id = Column(Integer, primary_key=True) - children = relationship("Child", backref="parent") - - class Child(Base): - __tablename__ = 'child' - - id = Column(Integer, primary_key=True) - parent_id = Column(ForeignKey('parent.id')) - - p1 = Parent() - p2 = Parent() - c1 = Child() - - p1.children.append(c1) - - assert c1.parent is p1 # backref event establishes c1.parent as p1 - - p2.children.append(c1) - - assert c1.parent is p2 # backref event establishes c1.parent as p2 - assert c1 not in p1.children # second backref event removes c1 from p1.children - -Above, prior to this change, the ``c1`` object would still have been present -in ``p1.children``, even though it is also present in ``p2.children`` at the -same time; the backref handlers would have stopped at replacing ``c1.parent`` with -``p2`` instead of ``p1``. In 0.9, using the more detailed :class:`.Event` -object as well as letting the backref handlers make more detailed decisions about -these objects, the propagation can continue onto removing ``c1`` from ``p1.children`` -while maintaining a check against the propagation from going into an endless -recursive loop. - -End-user code which a. makes use of the :meth:`.AttributeEvents.set`, -:meth:`.AttributeEvents.append`, or :meth:`.AttributeEvents.remove` events, -and b. initiates further attribute modification operations as a result of these -events may need to be modified to prevent recursive loops, as the attribute system -no longer stops a chain of events from propagating endlessly in the absense of the backref -event handlers. Additionally, code which depends upon the value of the ``initiator`` -will need to be adjusted to the new API, and furthermore must be ready for the -value of ``initiator`` to change from its original value within a string of -backref-initiated events, as the backref handlers may now swap in a -new ``initiator`` value for some operations. - -:ticket:`2789` .. _migration_2833: @@ -551,315 +481,128 @@ generated:: :ticket:`2879` -.. _migration_2850: -A bindparam() construct with no type gets upgraded via copy when a type is available ------------------------------------------------------------------------------------- -The logic which "upgrades" a :func:`.bindparam` construct to take on the -type of the enclosing expression has been improved in two ways. First, the -:func:`.bindparam` object is **copied** before the new type is assigned, so that -the given :func:`.bindparam` is not mutated in place. Secondly, this same -operation occurs when an :class:`.Insert` or :class:`.Update` construct is compiled, -regarding the "values" that were set in the statement via the :meth:`.ValuesBase.values` -method. +.. _migration_2878: -If given an untyped :func:`.bindparam`:: +Postgresql CREATE TYPE AS ENUM now applies quoting to values +---------------------------------------------------------------- - bp = bindparam("some_col") +The :class:`.postgresql.ENUM` type will now apply escaping to single quote +signs within the enumerated values:: -If we use this parameter as follows:: + >>> from sqlalchemy.dialects import postgresql + >>> type = postgresql.ENUM('one', 'two', "three's", name="myenum") + >>> from sqlalchemy.dialects.postgresql import base + >>> print base.CreateEnumType(type).compile(dialect=postgresql.dialect()) + CREATE TYPE myenum AS ENUM ('one','two','three''s') - expr = mytable.c.col == bp +Existing workarounds which already escape single quote signs will need to be +modified, else they will now double-escape. -The type for ``bp`` remains as ``NullType``, however if ``mytable.c.col`` -is of type ``String``, then ``expr.right``, that is the right side of the -binary expression, will take on the ``String`` type. Previously, ``bp`` itself -would have been changed in place to have ``String`` as its type. +:ticket:`2878` -Similarly, this operation occurs in an :class:`.Insert` or :class:`.Update`:: +New Features +============ - stmt = mytable.update().values(col=bp) +.. _feature_2268: -Above, ``bp`` remains unchanged, but the ``String`` type will be used when -the statement is executed, which we can see by examining the ``binds`` dictionary:: +Event Removal API +----------------- - >>> compiled = stmt.compile() - >>> compiled.binds['some_col'].type - String +Events established using :func:`.event.listen` or :func:`.event.listens_for` +can now be removed using the new :func:`.event.remove` function. The ``target``, +``identifier`` and ``fn`` arguments sent to :func:`.event.remove` need to match +exactly those which were sent for listening, and the event will be removed +from all locations in which it had been established:: -The feature allows custom types to take their expected effect within INSERT/UPDATE -statements without needing to explicitly specify those types within every -:func:`.bindparam` expression. + @event.listens_for(MyClass, "before_insert", propagate=True) + def my_before_insert(mapper, connection, target): + """listen for before_insert""" + # ... -The potentially backwards-compatible changes involve two unlikely -scenarios. Since the the bound parameter is -**cloned**, users should not be relying upon making in-place changes to a -:func:`.bindparam` construct once created. Additionally, code which uses -:func:`.bindparam` within an :class:`.Insert` or :class:`.Update` statement -which is relying on the fact that the :func:`.bindparam` is not typed according -to the column being assigned towards will no longer function in that way. + event.remove(MyClass, "before_insert", my_before_insert) -:ticket:`2850` +In the example above, the ``propagate=True`` flag is set. This +means ``my_before_insert()`` is established as a listener for ``MyClass`` +as well as all subclasses of ``MyClass``. +The system tracks everywhere that the ``my_before_insert()`` +listener function had been placed as a result of this call and removes it as +a result of calling :func:`.event.remove`. -.. _change_2838: +The removal system uses a registry to associate arguments passed to +:func:`.event.listen` with collections of event listeners, which are in many +cases wrapped versions of the original user-supplied function. This registry +makes heavy use of weak references in order to allow all the contained contents, +such as listener targets, to be garbage collected when they go out of scope. -The typing system now handles the task of rendering "literal bind" values -------------------------------------------------------------------------- +:ticket:`2268` -A new method is added to :class:`.TypeEngine` :meth:`.TypeEngine.literal_processor` -as well as :meth:`.TypeDecorator.process_literal_param` for :class:`.TypeDecorator` -which take on the task of rendering so-called "inline literal paramters" - parameters -that normally render as "bound" values, but are instead being rendered inline -into the SQL statement due to the compiler configuration. This feature is used -when generating DDL for constructs such as :class:`.CheckConstraint`, as well -as by Alembic when using constructs such as ``op.inline_literal()``. Previously, -a simple "isinstance" check checked for a few basic types, and the "bind processor" -was used unconditionally, leading to such issues as strings being encoded into utf-8 -prematurely. +.. _feature_1418: -Custom types written with :class:`.TypeDecorator` should continue to work in -"inline literal" scenarios, as the :meth:`.TypeDecorator.process_literal_param` -falls back to :meth:`.TypeDecorator.process_bind_param` by default, as these methods -usually handle a data manipulation, not as much how the data is presented to the -database. :meth:`.TypeDecorator.process_literal_param` can be specified to -specifically produce a string representing how a value should be rendered -into an inline DDL statement. +New Query Options API; ``load_only()`` option +--------------------------------------------- -:ticket:`2838` +The system of loader options such as :func:`.orm.joinedload`, +:func:`.orm.subqueryload`, :func:`.orm.lazyload`, :func:`.orm.defer`, etc. +all build upon a new system known as :class:`.Load`. :class:`.Load` provides +a "method chained" (a.k.a. :term:`generative`) approach to loader options, so that +instead of joining together long paths using dots or multiple attribute names, +an explicit loader style is given for each path. -.. _change_2812: +While the new way is slightly more verbose, it is simpler to understand +in that there is no ambiguity in what options are being applied to which paths; +it simplifies the method signatures of the options and provides greater flexibility +particularly for column-based options. The old systems are to remain functional +indefinitely as well and all styles can be mixed. -Schema identifiers now carry along their own quoting information ---------------------------------------------------------------------- +**Old Way** -This change simplifies the Core's usage of so-called "quote" flags, such -as the ``quote`` flag passed to :class:`.Table` and :class:`.Column`. The flag -is now internalized within the string name itself, which is now represented -as an instance of :class:`.quoted_name`, a string subclass. The -:class:`.IdentifierPreparer` now relies solely on the quoting preferences -reported by the :class:`.quoted_name` object rather than checking for any -explicit ``quote`` flags in most cases. The issue resolved here includes -that various case-sensitive methods such as :meth:`.Engine.has_table` as well -as similar methods within dialects now function with explicitly quoted names, -without the need to complicate or introduce backwards-incompatible changes -to those APIs (many of which are 3rd party) with the details of quoting flags - -in particular, a wider range of identifiers now function correctly with the -so-called "uppercase" backends like Oracle, Firebird, and DB2 (backends that -store and report upon table and column names using all uppercase for case -insensitive names). +To set a certain style of loading along every link in a multi-element path, the ``_all()`` +option has to be used:: -The :class:`.quoted_name` object is used internally as needed; however if -other keywords require fixed quoting preferences, the class is available -publically. + query(User).options(joinedload_all("orders.items.keywords")) -:ticket:`2812` +**New Way** -.. _migration_2804: +Loader options are now chainable, so the same ``joinedload(x)`` method is applied +equally to each link, without the need to keep straight between +:func:`.joinedload` and :func:`.joinedload_all`:: -Improved rendering of Boolean constants, NULL constants, conjunctions ----------------------------------------------------------------------- + query(User).options(joinedload("orders").joinedload("items").joinedload("keywords")) -New capabilities have been added to the :func:`.true` and :func:`.false` -constants, in particular in conjunction with :func:`.and_` and :func:`.or_` -functions as well as the behavior of the WHERE/HAVING clauses in conjunction -with these types, boolean types overall, and the :func:`.null` constant. +**Old Way** -Starting with a table such as this:: +Setting an option on path that is based on a subclass requires that all +links in the path be spelled out as class bound attributes, since the +:meth:`.PropComparator.of_type` method needs to be called:: - from sqlalchemy import Table, Boolean, Integer, Column, MetaData + session.query(Company).\ + options( + subqueryload_all( + Company.employees.of_type(Engineer), + Engineer.machines + ) + ) - t1 = Table('t', MetaData(), Column('x', Boolean()), Column('y', Integer)) +**New Way** -A select construct will now render the boolean column as a binary expression -on backends that don't feature ``true``/``false`` constant beahvior:: +Only those elements in the path that actually need :meth:`.PropComparator.of_type` +need to be set as a class-bound attribute, string-based names can be resumed +afterwards:: - >>> from sqlalchemy import select, and_, false, true - >>> from sqlalchemy.dialects import mysql, postgresql + session.query(Company).\ + options( + subqueryload(Company.employees.of_type(Engineer)). + subqueryload("machines") + ) + ) - >>> print select([t1]).where(t1.c.x).compile(dialect=mysql.dialect()) - SELECT t.x, t.y FROM t WHERE t.x = 1 +**Old Way** -The :func:`.and_` and :func:`.or_` constructs will now exhibit quasi -"short circuit" behavior, that is truncating a rendered expression, when a -:func:`.true` or :func:`.false` constant is present:: - - >>> print select([t1]).where(and_(t1.c.y > 5, false())).compile( - ... dialect=postgresql.dialect()) - SELECT t.x, t.y FROM t WHERE false - -:func:`.true` can be used as the base to build up an expression:: - - >>> expr = true() - >>> expr = expr & (t1.c.y > 5) - >>> print select([t1]).where(expr) - SELECT t.x, t.y FROM t WHERE t.y > :y_1 - -The boolean constants :func:`.true` and :func:`.false` themselves render as -``0 = 1`` and ``1 = 1`` for a backend with no boolean constants:: - - >>> print select([t1]).where(and_(t1.c.y > 5, false())).compile( - ... dialect=mysql.dialect()) - SELECT t.x, t.y FROM t WHERE 0 = 1 - -Interpretation of ``None``, while not particularly valid SQL, is at least -now consistent:: - - >>> print select([t1.c.x]).where(None) - SELECT t.x FROM t WHERE NULL - - >>> print select([t1.c.x]).where(None).where(None) - SELECT t.x FROM t WHERE NULL AND NULL - - >>> print select([t1.c.x]).where(and_(None, None)) - SELECT t.x FROM t WHERE NULL AND NULL - -:ticket:`2804` - -.. _migration_2848: - -``RowProxy`` now has tuple-sorting behavior -------------------------------------------- - -The :class:`.RowProxy` object acts much like a tuple, but up until now -would not sort as a tuple if a list of them were sorted using ``sorted()``. -The ``__eq__()`` method now compares both sides as a tuple and also -an ``__lt__()`` method has been added:: - - users.insert().execute( - dict(user_id=1, user_name='foo'), - dict(user_id=2, user_name='bar'), - dict(user_id=3, user_name='def'), - ) - - rows = users.select().order_by(users.c.user_name).execute().fetchall() - - eq_(rows, [(2, 'bar'), (3, 'def'), (1, 'foo')]) - - eq_(sorted(rows), [(1, 'foo'), (2, 'bar'), (3, 'def')]) - -:ticket:`2848` - -.. _migration_2878: - -Postgresql CREATE TYPE AS ENUM now applies quoting to values ----------------------------------------------------------------- - -The :class:`.postgresql.ENUM` type will now apply escaping to single quote -signs within the enumerated values:: - - >>> from sqlalchemy.dialects import postgresql - >>> type = postgresql.ENUM('one', 'two', "three's", name="myenum") - >>> from sqlalchemy.dialects.postgresql import base - >>> print base.CreateEnumType(type).compile(dialect=postgresql.dialect()) - CREATE TYPE myenum AS ENUM ('one','two','three''s') - -Existing workarounds which already escape single quote signs will need to be -modified, else they will now double-escape. - -:ticket:`2878` - -New Features -============ - -.. _feature_2268: - -Event Removal API ------------------ - -Events established using :func:`.event.listen` or :func:`.event.listens_for` -can now be removed using the new :func:`.event.remove` function. The ``target``, -``identifier`` and ``fn`` arguments sent to :func:`.event.remove` need to match -exactly those which were sent for listening, and the event will be removed -from all locations in which it had been established:: - - @event.listens_for(MyClass, "before_insert", propagate=True) - def my_before_insert(mapper, connection, target): - """listen for before_insert""" - # ... - - event.remove(MyClass, "before_insert", my_before_insert) - -In the example above, the ``propagate=True`` flag is set. This -means ``my_before_insert()`` is established as a listener for ``MyClass`` -as well as all subclasses of ``MyClass``. -The system tracks everywhere that the ``my_before_insert()`` -listener function had been placed as a result of this call and removes it as -a result of calling :func:`.event.remove`. - -The removal system uses a registry to associate arguments passed to -:func:`.event.listen` with collections of event listeners, which are in many -cases wrapped versions of the original user-supplied function. This registry -makes heavy use of weak references in order to allow all the contained contents, -such as listener targets, to be garbage collected when they go out of scope. - -:ticket:`2268` - -.. _feature_1418: - -New Query Options API; ``load_only()`` option ---------------------------------------------- - -The system of loader options such as :func:`.orm.joinedload`, -:func:`.orm.subqueryload`, :func:`.orm.lazyload`, :func:`.orm.defer`, etc. -all build upon a new system known as :class:`.Load`. :class:`.Load` provides -a "method chained" (a.k.a. :term:`generative`) approach to loader options, so that -instead of joining together long paths using dots or multiple attribute names, -an explicit loader style is given for each path. - -While the new way is slightly more verbose, it is simpler to understand -in that there is no ambiguity in what options are being applied to which paths; -it simplifies the method signatures of the options and provides greater flexibility -particularly for column-based options. The old systems are to remain functional -indefinitely as well and all styles can be mixed. - -**Old Way** - -To set a certain style of loading along every link in a multi-element path, the ``_all()`` -option has to be used:: - - query(User).options(joinedload_all("orders.items.keywords")) - -**New Way** - -Loader options are now chainable, so the same ``joinedload(x)`` method is applied -equally to each link, without the need to keep straight between -:func:`.joinedload` and :func:`.joinedload_all`:: - - query(User).options(joinedload("orders").joinedload("items").joinedload("keywords")) - -**Old Way** - -Setting an option on path that is based on a subclass requires that all -links in the path be spelled out as class bound attributes, since the -:meth:`.PropComparator.of_type` method needs to be called:: - - session.query(Company).\ - options( - subqueryload_all( - Company.employees.of_type(Engineer), - Engineer.machines - ) - ) - -**New Way** - -Only those elements in the path that actually need :meth:`.PropComparator.of_type` -need to be set as a class-bound attribute, string-based names can be resumed -afterwards:: - - session.query(Company).\ - options( - subqueryload(Company.employees.of_type(Engineer)). - subqueryload("machines") - ) - ) - -**Old Way** - -Setting the loader option on the last link in a long path uses a syntax -that looks a lot like it should be setting the option for all links in the -path, causing confusion:: +Setting the loader option on the last link in a long path uses a syntax +that looks a lot like it should be setting the option for all links in the +path, causing confusion:: query(User).options(subqueryload("orders.items.keywords")) @@ -1180,8 +923,9 @@ from a backref:: Behavioral Improvements ======================= -Improvements that should produce no compatibility issues, but are good -to be aware of in case there are unexpected issues. +Improvements that should produce no compatibility issues except in exceedingly +rare and unusual hypothetical cases, but are good to be aware of in case there are +unexpected issues. .. _feature_joins_09: @@ -1409,6 +1153,191 @@ be present. :ticket:`2836` +.. _migration_2789: + +Backref handlers can now propagate more than one level deep +----------------------------------------------------------- + +The mechanism by which attribute events pass along their "initiator", that is +the object associated with the start of the event, has been changed; instead +of a :class:`.AttributeImpl` being passed, a new object :class:`.attributes.Event` +is passed instead; this object refers to the :class:`.AttributeImpl` as well as +to an "operation token", representing if the operation is an append, remove, +or replace operation. + +The attribute event system no longer looks at this "initiator" object in order to halt a +recursive series of attribute events. Instead, the system of preventing endless +recursion due to mutually-dependent backref handlers has been moved +to the ORM backref event handlers specifically, which now take over the role +of ensuring that a chain of mutually-dependent events (such as append to collection +A.bs, set many-to-one attribute B.a in response) doesn't go into an endless recursion +stream. The rationale here is that the backref system, given more detail and control +over event propagation, can finally allow operations more than one level deep +to occur; the typical scenario is when a collection append results in a many-to-one +replacement operation, which in turn should cause the item to be removed from a +previous collection:: + + class Parent(Base): + __tablename__ = 'parent' + + id = Column(Integer, primary_key=True) + children = relationship("Child", backref="parent") + + class Child(Base): + __tablename__ = 'child' + + id = Column(Integer, primary_key=True) + parent_id = Column(ForeignKey('parent.id')) + + p1 = Parent() + p2 = Parent() + c1 = Child() + + p1.children.append(c1) + + assert c1.parent is p1 # backref event establishes c1.parent as p1 + + p2.children.append(c1) + + assert c1.parent is p2 # backref event establishes c1.parent as p2 + assert c1 not in p1.children # second backref event removes c1 from p1.children + +Above, prior to this change, the ``c1`` object would still have been present +in ``p1.children``, even though it is also present in ``p2.children`` at the +same time; the backref handlers would have stopped at replacing ``c1.parent`` with +``p2`` instead of ``p1``. In 0.9, using the more detailed :class:`.Event` +object as well as letting the backref handlers make more detailed decisions about +these objects, the propagation can continue onto removing ``c1`` from ``p1.children`` +while maintaining a check against the propagation from going into an endless +recursive loop. + +End-user code which a. makes use of the :meth:`.AttributeEvents.set`, +:meth:`.AttributeEvents.append`, or :meth:`.AttributeEvents.remove` events, +and b. initiates further attribute modification operations as a result of these +events may need to be modified to prevent recursive loops, as the attribute system +no longer stops a chain of events from propagating endlessly in the absense of the backref +event handlers. Additionally, code which depends upon the value of the ``initiator`` +will need to be adjusted to the new API, and furthermore must be ready for the +value of ``initiator`` to change from its original value within a string of +backref-initiated events, as the backref handlers may now swap in a +new ``initiator`` value for some operations. + +:ticket:`2789` + +.. _change_2838: + +The typing system now handles the task of rendering "literal bind" values +------------------------------------------------------------------------- + +A new method is added to :class:`.TypeEngine` :meth:`.TypeEngine.literal_processor` +as well as :meth:`.TypeDecorator.process_literal_param` for :class:`.TypeDecorator` +which take on the task of rendering so-called "inline literal paramters" - parameters +that normally render as "bound" values, but are instead being rendered inline +into the SQL statement due to the compiler configuration. This feature is used +when generating DDL for constructs such as :class:`.CheckConstraint`, as well +as by Alembic when using constructs such as ``op.inline_literal()``. Previously, +a simple "isinstance" check checked for a few basic types, and the "bind processor" +was used unconditionally, leading to such issues as strings being encoded into utf-8 +prematurely. + +Custom types written with :class:`.TypeDecorator` should continue to work in +"inline literal" scenarios, as the :meth:`.TypeDecorator.process_literal_param` +falls back to :meth:`.TypeDecorator.process_bind_param` by default, as these methods +usually handle a data manipulation, not as much how the data is presented to the +database. :meth:`.TypeDecorator.process_literal_param` can be specified to +specifically produce a string representing how a value should be rendered +into an inline DDL statement. + +:ticket:`2838` + + +.. _change_2812: + +Schema identifiers now carry along their own quoting information +--------------------------------------------------------------------- + +This change simplifies the Core's usage of so-called "quote" flags, such +as the ``quote`` flag passed to :class:`.Table` and :class:`.Column`. The flag +is now internalized within the string name itself, which is now represented +as an instance of :class:`.quoted_name`, a string subclass. The +:class:`.IdentifierPreparer` now relies solely on the quoting preferences +reported by the :class:`.quoted_name` object rather than checking for any +explicit ``quote`` flags in most cases. The issue resolved here includes +that various case-sensitive methods such as :meth:`.Engine.has_table` as well +as similar methods within dialects now function with explicitly quoted names, +without the need to complicate or introduce backwards-incompatible changes +to those APIs (many of which are 3rd party) with the details of quoting flags - +in particular, a wider range of identifiers now function correctly with the +so-called "uppercase" backends like Oracle, Firebird, and DB2 (backends that +store and report upon table and column names using all uppercase for case +insensitive names). + +The :class:`.quoted_name` object is used internally as needed; however if +other keywords require fixed quoting preferences, the class is available +publically. + +:ticket:`2812` + +.. _migration_2804: + +Improved rendering of Boolean constants, NULL constants, conjunctions +---------------------------------------------------------------------- + +New capabilities have been added to the :func:`.true` and :func:`.false` +constants, in particular in conjunction with :func:`.and_` and :func:`.or_` +functions as well as the behavior of the WHERE/HAVING clauses in conjunction +with these types, boolean types overall, and the :func:`.null` constant. + +Starting with a table such as this:: + + from sqlalchemy import Table, Boolean, Integer, Column, MetaData + + t1 = Table('t', MetaData(), Column('x', Boolean()), Column('y', Integer)) + +A select construct will now render the boolean column as a binary expression +on backends that don't feature ``true``/``false`` constant beahvior:: + + >>> from sqlalchemy import select, and_, false, true + >>> from sqlalchemy.dialects import mysql, postgresql + + >>> print select([t1]).where(t1.c.x).compile(dialect=mysql.dialect()) + SELECT t.x, t.y FROM t WHERE t.x = 1 + +The :func:`.and_` and :func:`.or_` constructs will now exhibit quasi +"short circuit" behavior, that is truncating a rendered expression, when a +:func:`.true` or :func:`.false` constant is present:: + + >>> print select([t1]).where(and_(t1.c.y > 5, false())).compile( + ... dialect=postgresql.dialect()) + SELECT t.x, t.y FROM t WHERE false + +:func:`.true` can be used as the base to build up an expression:: + + >>> expr = true() + >>> expr = expr & (t1.c.y > 5) + >>> print select([t1]).where(expr) + SELECT t.x, t.y FROM t WHERE t.y > :y_1 + +The boolean constants :func:`.true` and :func:`.false` themselves render as +``0 = 1`` and ``1 = 1`` for a backend with no boolean constants:: + + >>> print select([t1]).where(and_(t1.c.y > 5, false())).compile( + ... dialect=mysql.dialect()) + SELECT t.x, t.y FROM t WHERE 0 = 1 + +Interpretation of ``None``, while not particularly valid SQL, is at least +now consistent:: + + >>> print select([t1.c.x]).where(None) + SELECT t.x FROM t WHERE NULL + + >>> print select([t1.c.x]).where(None).where(None) + SELECT t.x FROM t WHERE NULL AND NULL + + >>> print select([t1.c.x]).where(and_(None, None)) + SELECT t.x FROM t WHERE NULL AND NULL + +:ticket:`2804` .. _migration_1068: @@ -1441,13 +1370,93 @@ And now renders as:: SELECT foo(t.c1) + t.c2 AS expr FROM t ORDER BY expr -The ORDER BY only renders the label if the label isn't further embedded into an expression within the ORDER BY, other than a simple ``ASC`` or ``DESC``. +The ORDER BY only renders the label if the label isn't further +embedded into an expression within the ORDER BY, other than a simple +``ASC`` or ``DESC``. -The above format works on all databases tested, but might have compatibility issues with older database versions (MySQL 4? Oracle 8? etc.). Based on user reports we can add rules -that will disable the feature based on database version detection. +The above format works on all databases tested, but might have +compatibility issues with older database versions (MySQL 4? Oracle 8? +etc.). Based on user reports we can add rules that will disable the +feature based on database version detection. :ticket:`1068` +.. _migration_2848: + +``RowProxy`` now has tuple-sorting behavior +------------------------------------------- + +The :class:`.RowProxy` object acts much like a tuple, but up until now +would not sort as a tuple if a list of them were sorted using ``sorted()``. +The ``__eq__()`` method now compares both sides as a tuple and also +an ``__lt__()`` method has been added:: + + users.insert().execute( + dict(user_id=1, user_name='foo'), + dict(user_id=2, user_name='bar'), + dict(user_id=3, user_name='def'), + ) + + rows = users.select().order_by(users.c.user_name).execute().fetchall() + + eq_(rows, [(2, 'bar'), (3, 'def'), (1, 'foo')]) + + eq_(sorted(rows), [(1, 'foo'), (2, 'bar'), (3, 'def')]) + +:ticket:`2848` + +.. _migration_2850: + +A bindparam() construct with no type gets upgraded via copy when a type is available +------------------------------------------------------------------------------------ + +The logic which "upgrades" a :func:`.bindparam` construct to take on the +type of the enclosing expression has been improved in two ways. First, the +:func:`.bindparam` object is **copied** before the new type is assigned, so that +the given :func:`.bindparam` is not mutated in place. Secondly, this same +operation occurs when an :class:`.Insert` or :class:`.Update` construct is compiled, +regarding the "values" that were set in the statement via the :meth:`.ValuesBase.values` +method. + +If given an untyped :func:`.bindparam`:: + + bp = bindparam("some_col") + +If we use this parameter as follows:: + + expr = mytable.c.col == bp + +The type for ``bp`` remains as ``NullType``, however if ``mytable.c.col`` +is of type ``String``, then ``expr.right``, that is the right side of the +binary expression, will take on the ``String`` type. Previously, ``bp`` itself +would have been changed in place to have ``String`` as its type. + +Similarly, this operation occurs in an :class:`.Insert` or :class:`.Update`:: + + stmt = mytable.update().values(col=bp) + +Above, ``bp`` remains unchanged, but the ``String`` type will be used when +the statement is executed, which we can see by examining the ``binds`` dictionary:: + + >>> compiled = stmt.compile() + >>> compiled.binds['some_col'].type + String + +The feature allows custom types to take their expected effect within INSERT/UPDATE +statements without needing to explicitly specify those types within every +:func:`.bindparam` expression. + +The potentially backwards-compatible changes involve two unlikely +scenarios. Since the the bound parameter is +**cloned**, users should not be relying upon making in-place changes to a +:func:`.bindparam` construct once created. Additionally, code which uses +:func:`.bindparam` within an :class:`.Insert` or :class:`.Update` statement +which is relying on the fact that the :func:`.bindparam` is not typed according +to the column being assigned towards will no longer function in that way. + +:ticket:`2850` + + .. _migration_1765: Columns can reliably get their type from a column referred to via ForeignKey