diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-01-14 17:16:49 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-01-14 17:16:49 -0500 |
commit | 8446c89f8c6d1309b17cd3775d0fefc5f00951f9 (patch) | |
tree | a3f0d104298bedd81470017f56e8404cf7e7a7ba | |
parent | 5d12891fefa187a15e7dd6f6539c90ce1813aea9 (diff) | |
download | sqlalchemy-8446c89f8c6d1309b17cd3775d0fefc5f00951f9.tar.gz |
- more migration
-rw-r--r-- | doc/build/changelog/migration_11.rst | 104 | ||||
-rw-r--r-- | doc/build/core/tutorial.rst | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/result.py | 8 | ||||
-rw-r--r-- | test/sql/test_resultset.py | 2 |
4 files changed, 95 insertions, 21 deletions
diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst index 6ee8293f4..119bb00ee 100644 --- a/doc/build/changelog/migration_11.rst +++ b/doc/build/changelog/migration_11.rst @@ -447,8 +447,8 @@ will not have much impact on the behavior of the column during an INSERT. .. _change_3501: -Positional matching of result columns when using textual SQL ------------------------------------------------------------- +ResultSet column matching enhancements; positional column setup for textual SQL +------------------------------------------------------------------------------- A series of improvements were made to the :class:`.ResultProxy` system in the 1.0 series as part of :ticket:`918`, which reorganizes the internals @@ -457,14 +457,20 @@ rather than by matching names, for compiled SQL constructs that contain full information about the result rows to be returned. This allows a dramatic savings on Python overhead as well as much greater accuracy in linking ORM and Core SQL expressions to result rows. In 1.1, this reorganization has been taken -much further internally, and also has been made available to pure-text SQL +further internally, and also has been made available to pure-text SQL constructs via the use of the recently added :meth:`.TextClause.columns` method. -The method now accepts column-based arguments positionally which will be matched -to the target result set positionally as well. The key advantage here is that -textual SQL can now be linked to an ORM-level result set without the need to -deal with ambiguous or duplicate column names, or with having to match labeling -schemes to ORM-level column names. All that's needed now is the position:: +TextAsFrom.columns() now works positionally +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The :meth:`.TextClause.columns` method, added in 0.9, accepts column-based arguments +positionally; in 1.1, when all columns are passed positionally, the correlation +of these columns to the ultimate result set is also performed positionally. +The key advantage here is that textual SQL can now be linked to an ORM- +level result set without the need to deal with ambiguous or duplicate column +names, or with having to match labeling schemes to ORM-level column names. All +that's needed now is the same ordering of columns within the textual SQL +and the column arguments passed to :meth:`.TextClause.columns`:: from sqlalchemy import text @@ -490,12 +496,67 @@ the ``Address.user_id`` column to the ``users.id`` column in textual SQL for fun, and the :class:`.Query` object will receive rows that are correctly targetable as needed, including for an eager load. +This change is **backwards incompatible** with code that passes the columns +to the method with a different ordering than is present in the textual statement. +It is hoped that this impact will be low due to the fact that this +method has always been documented illustrating the columns being passed in the same order as that of the +textual SQL statement, as would seem intuitive, even though the internals +weren't checking for this. The method itself was only added as of 0.9 in +any case and may not yet have widespread use. Notes on exactly how to handle +this behavioral change for applications using it are at :ref:`behavior_change_3501`. + .. seealso:: - :ref:`sqlexpression_text_columns` + :ref:`sqlexpression_text_columns` - in the Core tutorial :ref:`behavior_change_3501` +Positional matching is trusted over name-based matching for Core/ORM SQL constructs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Another aspect of this change is that the rules for matching columns have also been modified +to rely upon "positional" matching more fully for compiled SQL constructs +as well. Given a statement like the following:: + + ua = users.alias('ua') + stmt = select([users.c.user_id, ua.c.user_id]) + +The above statement will compile to:: + + SELECT users.user_id, ua.user_id FROM users, users AS ua + +In 1.0, the above statement when executed would be matched to its original +compiled construct using positional matching, however because the statement +contains the ``'user_id'`` label duplicated, the "ambiguous column" rule +would still get involved and prevent the columns from being fetched from a row. +As of 1.1, the "ambiguous column" rule does not affect an exact match from +a column construct to the SQL column, which is what the ORM uses to +fetch columns:: + + result = conn.execute(stmt) + row = result.first() + + # these both match positionally, so no error + user_id = row[users.c.user_id] + ua_id = row[ua.c.user_id] + + # this still raises, however + user_id = row['user_id'] + +Much less likely to get an "ambiguous column" error message +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As part of this change, the wording of the error message ``Ambiguous column +name '<name>' in result set! try 'use_labels' option on select statement.`` +has been dialed back; as this message should now be extremely rare when using +the ORM or Core compiled SQL constructs, it merely states +``Ambiguous column name '<name>' in result set column descriptions``, and +only when a result column is retrieved using the string name that is actually +ambiguous, e.g. ``row['user_id']`` in the above example. It also now refers +to the actual ambiguous name from the rendered SQL statement itself, +rather than indicating the key or name that was local to the construct being +used for the fetch. + :ticket:`3501` .. _change_2528: @@ -951,10 +1012,20 @@ Key Behavioral Changes - Core .. _behavior_change_3501: -The new behavior of the :meth:`.TextClause.columns` method is that when -columns are passed positionally only, they are linked to the ultimate result set -columns positionally, and no longer on name. Therefore an application that was -using this very recently added method by passing :class:`.Column` objects +TextClause.columns() will match columns positionally, not by name, when passed positionally +------------------------------------------------------------------------------------------- + +The new behavior of the :meth:`.TextClause.columns` method, which itself +was recently added as of the 0.9 series, is that when +columns are passed positionally without any additional keyword arguments, +they are linked to the ultimate result set +columns positionally, and no longer on name. It is hoped that the impact +of this change will be low due to the fact that the method has always been documented +illustrating the columns being passed in the same order as that of the +textual SQL statement, as would seem intuitive, even though the internals +weren't checking for this. + +An application that is using this method by passing :class:`.Column` objects to it positionally must ensure that the position of those :class:`.Column` objects matches the position in which these columns are stated in the textual SQL. @@ -979,11 +1050,10 @@ Possibly more likely, a statement that worked like this:: is now slightly risky, as the "*" specification will generally deliver columns in the order in which they are present in the table itself. If the structure -of the table changes due to a migration, this ordering may no longer be the same. +of the table changes due to schema changes, this ordering may no longer be the same. Therefore when using :meth:`.TextClause.columns`, it's advised to list out -the desired columns explicitly, though it's no longer necessary to worry about -duplicate names or names matching. - +the desired columns explicitly in the textual SQL, though it's no longer +necessary to worry about the names themselves in the textual SQL. .. seealso:: diff --git a/doc/build/core/tutorial.rst b/doc/build/core/tutorial.rst index 0a1398055..04262ac5e 100644 --- a/doc/build/core/tutorial.rst +++ b/doc/build/core/tutorial.rst @@ -805,7 +805,7 @@ using the :meth:`.TextClause.bindparams` method:: The parameters can also be explicitly typed:: stmt = stmt.bindparams(bindparam("x", String), bindparam("y", String)) - result = conn.execute(stmt, {x:"m", y:"z"}) + result = conn.execute(stmt, {"x": "m", "y": "z"}) Typing for bound parameters is necessary when the type requires Python-side or special SQL-side processing provided by the datatype. diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py index 460e771f2..cc4ac74cd 100644 --- a/lib/sqlalchemy/engine/result.py +++ b/lib/sqlalchemy/engine/result.py @@ -186,6 +186,10 @@ class ResultMetaData(object): """Handle cursor.description, applying additional info from an execution context.""" + __slots__ = ( + '_keymap', 'case_sensitive', 'matched_on_name', + '_processors', 'keys', '_orig_processors') + def __init__(self, parent, cursor_description): context = parent.context dialect = context.dialect @@ -252,7 +256,8 @@ class ResultMetaData(object): # if we did a pure positional match, then reset the # original "expression element" back to the "unambiguous" - # entry + # entry. This is a new behavior in 1.1 which impacts + # TextAsFrom but also straight compiled SQL constructs. if not self.matched_on_name: self._keymap.update([ (elem[4][0], (elem[3], elem[4], elem[0])) @@ -591,7 +596,6 @@ class ResultMetaData(object): self.keys = state['keys'] self.case_sensitive = state['case_sensitive'] self.matched_on_name = state['matched_on_name'] - self._echo = False class ResultProxy(object): diff --git a/test/sql/test_resultset.py b/test/sql/test_resultset.py index 952e0433a..d7dc9edc3 100644 --- a/test/sql/test_resultset.py +++ b/test/sql/test_resultset.py @@ -607,8 +607,8 @@ class ResultProxyTest(fixtures.TablesTest): # pure positional targeting; users.c.user_id # and addresses.c.user_id are known! + # works as of 1.1 issue #3501 eq_(r[users.c.user_id], 1) - eq_(r[addresses.c.user_id], None) # try to trick it - fake_table isn't in the result! |