diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-10-01 17:38:41 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-10-04 15:58:29 -0400 |
| commit | 485216dea6d7a5814d200b4f14b8a363ed0f8caa (patch) | |
| tree | 18885a1c53b59f36a75f6507b33747b8852605e7 /lib/sqlalchemy/engine | |
| parent | 60e64a2c35e7e5a0125c5fefbf0caf531eeb2eda (diff) | |
| download | sqlalchemy-485216dea6d7a5814d200b4f14b8a363ed0f8caa.tar.gz | |
Deprecate textual column matching in Row
Deprecate query.instances() without a context
Deprecate string alias with contains_eager()
Deprecated the behavior by which a :class:`.Column` can be used as the key
in a result set row lookup, when that :class:`.Column` is not part of the
SQL selectable that is being selected; that is, it is only matched on name.
A deprecation warning is now emitted for this case. Various ORM use
cases, such as those involving :func:`.text` constructs, have been improved
so that this fallback logic is avoided in most cases.
Calling the :meth:`.Query.instances` method without passing a
:class:`.QueryContext` is deprecated. The original use case for this was
that a :class:`.Query` could yield ORM objects when given only the entities
to be selected as well as a DBAPI cursor object. However, for this to work
correctly there is essential metadata that is passed from a SQLAlchemy
:class:`.ResultProxy` that is derived from the mapped column expressions,
which comes originally from the :class:`.QueryContext`. To retrieve ORM
results from arbitrary SELECT statements, the :meth:`.Query.from_statement`
method should be used.
Note there is a small bump in test_zoomark because the
column._label is being calculated for each of those columns within
baseline_3_properties, as it is now part of the result map.
This label can't be calculated when the column is attached
to the table because it needs to have all the columns present
to do this correctly. Another approach here would be to
pre-load the _label before the test runs however the zoomark
tests don't have an easy place for this to happen and it's
not really worth it.
Fixes: #4877
Fixes: #4719
Change-Id: I9bd29e72e6dce7c855651d69ba68d7383469acbc
Diffstat (limited to 'lib/sqlalchemy/engine')
| -rw-r--r-- | lib/sqlalchemy/engine/default.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/result.py | 73 |
2 files changed, 57 insertions, 17 deletions
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index eac593125..5a6e5c72e 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -653,6 +653,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): compiled._result_columns, compiled._ordered_columns, compiled._textual_ordered_columns, + compiled._loose_column_name_matching, ) self.unicode_statement = util.text_type(compiled) diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py index 90c884f94..af5303658 100644 --- a/lib/sqlalchemy/engine/result.py +++ b/lib/sqlalchemy/engine/result.py @@ -254,12 +254,15 @@ class ResultMetaData(object): result_columns, cols_are_ordered, textual_ordered, + loose_column_name_matching, ) = context.result_column_struct num_ctx_cols = len(result_columns) else: result_columns = ( cols_are_ordered - ) = num_ctx_cols = textual_ordered = False + ) = ( + num_ctx_cols + ) = loose_column_name_matching = textual_ordered = False # merge cursor.description with the column info # present in the compiled structure, if any @@ -270,6 +273,7 @@ class ResultMetaData(object): num_ctx_cols, cols_are_ordered, textual_ordered, + loose_column_name_matching, ) self._keymap = {} @@ -388,6 +392,7 @@ class ResultMetaData(object): num_ctx_cols, cols_are_ordered, textual_ordered, + loose_column_name_matching, ): """Merge a cursor.description with compiled result column information. @@ -482,7 +487,10 @@ class ResultMetaData(object): # compiled SQL with a mismatch of description cols # vs. compiled cols, or textual w/ unordered columns raw_iterator = self._merge_cols_by_name( - context, cursor_description, result_columns + context, + cursor_description, + result_columns, + loose_column_name_matching, ) else: # no compiled SQL, just a raw string @@ -587,13 +595,18 @@ class ResultMetaData(object): yield idx, colname, mapped_type, coltype, obj, untranslated - def _merge_cols_by_name(self, context, cursor_description, result_columns): + def _merge_cols_by_name( + self, + context, + cursor_description, + result_columns, + loose_column_name_matching, + ): dialect = context.dialect case_sensitive = dialect.case_sensitive match_map = self._create_description_match_map( - result_columns, case_sensitive + result_columns, case_sensitive, loose_column_name_matching ) - self.matched_on_name = True for ( idx, @@ -622,7 +635,10 @@ class ResultMetaData(object): @classmethod def _create_description_match_map( - cls, result_columns, case_sensitive=True + cls, + result_columns, + case_sensitive=True, + loose_column_name_matching=False, ): """when matching cursor.description to a set of names that are present in a Compiled object, as is the case with TextualSelect, get all the @@ -631,22 +647,29 @@ class ResultMetaData(object): d = {} for elem in result_columns: - key, rec = ( - elem[RM_RENDERED_NAME], - (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE]), - ) + key = elem[RM_RENDERED_NAME] if not case_sensitive: key = key.lower() if key in d: - # conflicting keyname, just double up the list - # of objects. this will cause an "ambiguous name" - # error if an attempt is made by the result set to - # access. + # conflicting keyname - just add the column-linked objects + # to the existing record. if there is a duplicate column + # name in the cursor description, this will allow all of those + # objects to raise an ambiguous column error e_name, e_obj, e_type = d[key] - d[key] = e_name, e_obj + rec[1], e_type + d[key] = e_name, e_obj + elem[RM_OBJECTS], e_type else: - d[key] = rec - + d[key] = (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE]) + + if loose_column_name_matching: + # when using a textual statement with an unordered set + # of columns that line up, we are expecting the user + # to be using label names in the SQL that match to the column + # expressions. Enable more liberal matching for this case; + # duplicate keys that are ambiguous will be fixed later. + for r_key in elem[RM_OBJECTS]: + d.setdefault( + r_key, (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE]) + ) return d def _key_fallback(self, key, raiseerr=True): @@ -688,6 +711,22 @@ class ResultMetaData(object): break else: result = None + if result is not None: + if result[MD_OBJECTS] is _UNPICKLED: + util.warn_deprecated( + "Retreiving row values using Column objects from a " + "row that was unpickled is deprecated; adequate " + "state cannot be pickled for this to be efficient. " + "This usage will raise KeyError in a future release." + ) + else: + util.warn_deprecated( + "Retreiving row values using Column objects with only " + "matching names as keys is deprecated, and will raise " + "KeyError in a future release; only Column " + "objects that are explicitly part of the statement " + "object should be used." + ) if result is None: if raiseerr: raise exc.NoSuchColumnError( |
