summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-10-15 20:07:13 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-10-15 20:07:13 -0400
commit2484ef34c27f3342e62bd6285bb3668e2c913090 (patch)
treee7e329fb3e06e903c793f6944a1facd72b0bd4ef /lib/sqlalchemy
parentaf3c8a75c8e9eba593f6568187226548f1b8735d (diff)
downloadsqlalchemy-2484ef34c27f3342e62bd6285bb3668e2c913090.tar.gz
- [feature] The Query can now load entity/scalar-mixed
"tuple" rows that contain types which aren't hashable, by setting the flag "hashable=False" on the corresponding TypeEngine object in use. Custom types that return unhashable types (typically lists) can set this flag to False. [ticket:2592] - [bug] Applying a column expression to a select statement using a label with or without other modifying constructs will no longer "target" that expression to the underlying Column; this affects ORM operations that rely upon Column targeting in order to retrieve results. That is, a query like query(User.id, User.id.label('foo')) will now track the value of each "User.id" expression separately instead of munging them together. It is not expected that any users will be impacted by this; however, a usage that uses select() in conjunction with query.from_statement() and attempts to load fully composed ORM entities may not function as expected if the select() named Column objects with arbitrary .label() names, as these will no longer target to the Column objects mapped by that entity. [ticket:2591]
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/engine/result.py8
-rw-r--r--lib/sqlalchemy/orm/query.py14
-rw-r--r--lib/sqlalchemy/sql/compiler.py4
-rw-r--r--lib/sqlalchemy/types.py6
4 files changed, 25 insertions, 7 deletions
diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py
index 9fb735f46..6962a4d1e 100644
--- a/lib/sqlalchemy/engine/result.py
+++ b/lib/sqlalchemy/engine/result.py
@@ -228,16 +228,20 @@ class ResultMetaData(object):
# is interpreted later as an AmbiguousColumnError,
# but only when actually accessed. Columns
# colliding by name is not a problem if those names
- # aren't used; integer and ColumnElement access is always
+ # aren't used; integer access is always
# unambiguous.
primary_keymap[name
if self.case_sensitive
- else name.lower()] = rec = (processor, obj, None)
+ else name.lower()] = rec = (None, obj, None)
self.keys.append(colname)
if obj:
for o in obj:
keymap[o] = rec
+ # technically we should be doing this but we
+ # are saving on callcounts by not doing so.
+ # if keymap.setdefault(o, rec) is not rec:
+ # keymap[o] = (None, obj, None)
if translate_colname and \
untranslated:
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 35d32651f..a6d20a973 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -29,7 +29,8 @@ from .util import (
_is_aliased_class, _is_mapped_class, _orm_columns,
join as orm_join,with_parent, aliased
)
-from .. import sql, util, log, exc as sa_exc, inspect, inspection
+from .. import sql, util, log, exc as sa_exc, inspect, inspection, \
+ types as sqltypes
from ..sql.expression import _interpret_as_from
from ..sql import (
util as sql_util,
@@ -2930,12 +2931,20 @@ class _ColumnEntity(_QueryEntity):
if c is not column:
return
+
if not isinstance(column, sql.ColumnElement):
raise sa_exc.InvalidRequestError(
"SQL expression, column, or mapped entity "
"expected - got '%r'" % (column, )
)
+ type_ = column.type
+ if type_.hashable:
+ self.filter_fn = lambda item: item
+ else:
+ counter = util.counter()
+ self.filter_fn = lambda item: counter()
+
# If the Column is unnamed, give it a
# label() so that mutable column expressions
# can be located in the result even
@@ -2972,6 +2981,7 @@ class _ColumnEntity(_QueryEntity):
else:
self.entity_zero = None
+
@property
def entity_zero_or_selectable(self):
if self.entity_zero is not None:
@@ -2985,8 +2995,6 @@ class _ColumnEntity(_QueryEntity):
def type(self):
return self.column.type
- def filter_fn(self, item):
- return item
def adapt_to_selectable(self, query, sel):
c = _ColumnEntity(query, sel.corresponding_column(self.column))
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index f705a216e..5fe30a8ff 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -158,7 +158,7 @@ class _CompileLabel(visitors.Visitable):
def __init__(self, col, name, alt_names=()):
self.element = col
self.name = name
- self._alt_names = alt_names
+ self._alt_names = (col,) + alt_names
@property
def proxy_set(self):
@@ -391,7 +391,7 @@ class SQLCompiler(engine.Compiled):
add_to_result_map(
labelname,
label.name,
- (label, label.element, labelname, ) + label._alt_names,
+ (label, labelname, ) + label._alt_names,
label.type
)
diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py
index 71bd39ba6..ebcffca6e 100644
--- a/lib/sqlalchemy/types.py
+++ b/lib/sqlalchemy/types.py
@@ -57,6 +57,12 @@ class TypeEngine(AbstractType):
def __reduce__(self):
return _reconstitute_comparator, (self.expr, )
+ hashable = True
+ """Flag, if False, means values from this type aren't hashable.
+
+ Used by the ORM when uniquing result lists.
+
+ """
comparator_factory = Comparator
"""A :class:`.TypeEngine.Comparator` class which will apply