mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-13 04:03:49 -04:00
Merge changes from official sqlalchemy repo
This commit is contained in:
Vendored
+146
-134
File diff suppressed because it is too large
Load Diff
Vendored
+62
-10
@@ -6,6 +6,53 @@
|
||||
.. changelog::
|
||||
:version: 0.8.0b2
|
||||
|
||||
.. change::
|
||||
:tags: sql, bug
|
||||
:tickets: 2618
|
||||
|
||||
The :class:`.DECIMAL` type now honors the "precision" and
|
||||
"scale" arguments when rendering DDL.
|
||||
|
||||
.. change::
|
||||
:tags: orm, bug
|
||||
:tickets: 2624
|
||||
|
||||
The :class:`.MutableComposite` type did not allow for the
|
||||
:meth:`.MutableBase.coerce` method to be used, even though
|
||||
the code seemed to indicate this intent, so this now works
|
||||
and a brief example is added. As a side-effect,
|
||||
the mechanics of this event handler have been changed so that
|
||||
new :class:`.MutableComposite` types no longer add per-type
|
||||
global event handlers. Also in 0.7.10.
|
||||
|
||||
.. change::
|
||||
:tags: sql, bug
|
||||
:tickets: 2621
|
||||
|
||||
Made an adjustment to the "boolean", (i.e. ``__nonzero__``)
|
||||
evaluation of binary expressions, i.e. ``x1 == x2``, such
|
||||
that the "auto-grouping" applied by :class:`.BinaryExpression`
|
||||
in some cases won't get in the way of this comparison.
|
||||
Previously, an expression like::
|
||||
|
||||
expr1 = mycolumn > 2
|
||||
bool(expr1 == expr1)
|
||||
|
||||
Would evaulate as ``False``, even though this is an identity
|
||||
comparison, because ``mycolumn > 2`` would be "grouped" before
|
||||
being placed into the :class:`.BinaryExpression`, thus changing
|
||||
its identity. :class:`.BinaryExpression` now keeps track
|
||||
of the "original" objects passed in.
|
||||
Additionally the ``__nonzero__`` method now only returns if
|
||||
the operator is ``==`` or ``!=`` - all others raise ``TypeError``.
|
||||
|
||||
.. change::
|
||||
:tags: firebird, bug
|
||||
:tickets: 2622
|
||||
|
||||
Added missing import for "fdb" to the experimental
|
||||
"firebird+fdb" dialect.
|
||||
|
||||
.. change::
|
||||
:tags: orm, feature
|
||||
|
||||
@@ -14,17 +61,22 @@
|
||||
|
||||
.. change::
|
||||
:tags: orm, bug
|
||||
:ticket: 2614
|
||||
:tickets: 2614
|
||||
|
||||
Added a new exception to detect the case where two
|
||||
subclasses are being loaded using with_polymorphic(),
|
||||
each subclass contains a relationship attribute of the same
|
||||
name, and eager loading is being applied to one or both.
|
||||
This is an ongoing bug which can't be immediately fixed,
|
||||
so since the results are usually wrong in any case it raises an
|
||||
exception for now. 0.7 has the same issue, so an exception
|
||||
raise here probably means the code was returning the wrong
|
||||
data in 0.7.
|
||||
A second overhaul of aliasing/internal pathing mechanics
|
||||
now allows two subclasses to have different relationships
|
||||
of the same name, supported with subquery or joined eager
|
||||
loading on both simultaneously when a full polymorphic
|
||||
load is used.
|
||||
|
||||
.. change::
|
||||
:tags: orm, bug
|
||||
:tickets: 2617
|
||||
|
||||
Fixed bug whereby a multi-hop subqueryload within
|
||||
a particular with_polymorphic load would produce a KeyError.
|
||||
Takes advantage of the same internal pathing overhaul
|
||||
as :ticket:`2614`.
|
||||
|
||||
.. change::
|
||||
:tags: sql, bug
|
||||
|
||||
Vendored
+1
-1
@@ -9,7 +9,7 @@ API Reference
|
||||
-------------
|
||||
|
||||
.. autoclass:: MutableBase
|
||||
:members: _parents
|
||||
:members: _parents, coerce
|
||||
|
||||
.. autoclass:: Mutable
|
||||
:show-inheritance:
|
||||
|
||||
Vendored
+16
-10
@@ -1295,6 +1295,7 @@ things like unique constraint exceptions::
|
||||
print "Skipped record %s" % record
|
||||
session.commit()
|
||||
|
||||
.. _session_autocommit:
|
||||
|
||||
Autocommit Mode
|
||||
---------------
|
||||
@@ -1309,16 +1310,21 @@ results have been iterated. The :meth:`.Session.flush` operation
|
||||
still occurs within the scope of a single transaction, though this transaction
|
||||
is closed out after the :meth:`.Session.flush` operation completes.
|
||||
|
||||
"autocommit" mode should **not be considered for general use**. While
|
||||
very old versions of SQLAlchemy standardized on this mode, the modern
|
||||
:class:`.Session` benefits highly from being given a clear point of transaction
|
||||
demarcation via :meth:`.Session.rollback` and :meth:`.Session.commit`.
|
||||
The autoflush action can safely emit SQL to the database as needed without
|
||||
implicitly producing permanent effects, the contents of attributes
|
||||
are expired only when a logical series of steps has completed. If the
|
||||
:class:`.Session` were to be used in pure "autocommit" mode without
|
||||
an ongoing transaction, these features should be disabled, that is,
|
||||
``autoflush=False, expire_on_commit=False``.
|
||||
.. warning::
|
||||
|
||||
"autocommit" mode should **not be considered for general use**.
|
||||
If used, it should always be combined with the usage of
|
||||
:meth:`.Session.begin` and :meth:`.Session.commit`, to ensure
|
||||
a transaction demarcation.
|
||||
|
||||
Executing queries outside of a demarcated transaction is a legacy mode
|
||||
of usage, and can in some cases lead to concurrent connection
|
||||
checkouts.
|
||||
|
||||
In the absense of a demarcated transaction, the :class:`.Session`
|
||||
cannot make appropriate decisions as to when autoflush should
|
||||
occur nor when auto-expiration should occur, so these features
|
||||
should be disabled with ``autoflush=False, expire_on_commit=False``.
|
||||
|
||||
Modern usage of "autocommit" is for framework integrations that need to control
|
||||
specifically when the "begin" state occurs. A session which is configured with
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.firebird import base, kinterbasdb
|
||||
from sqlalchemy.dialects.firebird import base, kinterbasdb, fdb
|
||||
|
||||
base.dialect = kinterbasdb.dialect
|
||||
|
||||
|
||||
@@ -245,6 +245,9 @@ class _DispatchDescriptor(object):
|
||||
self._clslevel = util.defaultdict(list)
|
||||
self._empty_listeners = {}
|
||||
|
||||
def _contains(self, cls, evt):
|
||||
return evt in self._clslevel[cls]
|
||||
|
||||
def insert(self, obj, target, propagate):
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
|
||||
@@ -302,6 +302,31 @@ will flag the attribute as "dirty" on the parent object::
|
||||
>>> assert v1 in sess.dirty
|
||||
True
|
||||
|
||||
Coercing Mutable Composites
|
||||
---------------------------
|
||||
|
||||
The :meth:`.MutableBase.coerce` method is also supported on composite types.
|
||||
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
|
||||
method is only called for attribute set operations, not load operations.
|
||||
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
|
||||
to using a :func:`.validates` validation routine for all attributes which
|
||||
make use of the custom composite type::
|
||||
|
||||
class Point(MutableComposite):
|
||||
# other Point methods
|
||||
# ...
|
||||
|
||||
def coerce(cls, key, value):
|
||||
if isinstance(value, tuple):
|
||||
value = Point(*value)
|
||||
elif not isinstance(value, Point):
|
||||
raise ValueError("tuple or Point expected")
|
||||
return value
|
||||
|
||||
.. versionadded:: 0.7.10,0.8.0b2
|
||||
Support for the :meth:`.MutableBase.coerce` method in conjunction with
|
||||
objects of type :class:`.MutableComposite`.
|
||||
|
||||
Supporting Pickling
|
||||
--------------------
|
||||
|
||||
@@ -329,7 +354,7 @@ pickling process of the parent's object-relational state so that the
|
||||
"""
|
||||
from ..orm.attributes import flag_modified
|
||||
from .. import event, types
|
||||
from ..orm import mapper, object_mapper
|
||||
from ..orm import mapper, object_mapper, Mapper
|
||||
from ..util import memoized_property
|
||||
import weakref
|
||||
|
||||
@@ -354,9 +379,27 @@ class MutableBase(object):
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
"""Given a value, coerce it into this type.
|
||||
"""Given a value, coerce it into the target type.
|
||||
|
||||
Can be overridden by custom subclasses to coerce incoming
|
||||
data into a particular type.
|
||||
|
||||
By default, raises ``ValueError``.
|
||||
|
||||
This method is called in different scenarios depending on if
|
||||
the parent class is of type :class:`.Mutable` or of type
|
||||
:class:`.MutableComposite`. In the case of the former, it is called
|
||||
for both attribute-set operations as well as during ORM loading
|
||||
operations. For the latter, it is only called during attribute-set
|
||||
operations; the mechanics of the :func:`.composite` construct
|
||||
handle coercion during load operations.
|
||||
|
||||
|
||||
:param key: string name of the ORM-mapped attribute being set.
|
||||
:param value: the incoming value.
|
||||
:return: the method should return the coerced value, or raise
|
||||
``ValueError`` if the coercion cannot be completed.
|
||||
|
||||
By default raises ValueError.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
@@ -523,11 +566,6 @@ class Mutable(MutableBase):
|
||||
return sqltype
|
||||
|
||||
|
||||
class _MutableCompositeMeta(type):
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
cls._setup_listeners()
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
|
||||
class MutableComposite(MutableBase):
|
||||
"""Mixin that defines transparent propagation of change
|
||||
@@ -536,16 +574,7 @@ class MutableComposite(MutableBase):
|
||||
|
||||
See the example in :ref:`mutable_composites` for usage information.
|
||||
|
||||
.. warning::
|
||||
|
||||
The listeners established by the :class:`.MutableComposite`
|
||||
class are *global* to all mappers, and are *not* garbage
|
||||
collected. Only use :class:`.MutableComposite` for types that are
|
||||
permanent to an application, not with ad-hoc types else this will
|
||||
cause unbounded growth in memory usage.
|
||||
|
||||
"""
|
||||
__metaclass__ = _MutableCompositeMeta
|
||||
|
||||
def changed(self):
|
||||
"""Subclasses should call this method whenever change events occur."""
|
||||
@@ -558,24 +587,16 @@ class MutableComposite(MutableBase):
|
||||
prop._attribute_keys):
|
||||
setattr(parent, attr_name, value)
|
||||
|
||||
@classmethod
|
||||
def _setup_listeners(cls):
|
||||
"""Associate this wrapper with all future mapped composites
|
||||
of the given type.
|
||||
|
||||
This is a convenience method that calls ``associate_with_attribute``
|
||||
automatically.
|
||||
|
||||
"""
|
||||
|
||||
def listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if (hasattr(prop, 'composite_class') and
|
||||
issubclass(prop.composite_class, cls)):
|
||||
cls._listen_on_attribute(
|
||||
getattr(class_, prop.key), False, class_)
|
||||
|
||||
event.listen(mapper, 'mapper_configured', listen_for_type)
|
||||
def _setup_composite_listener():
|
||||
def _listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if (hasattr(prop, 'composite_class') and
|
||||
issubclass(prop.composite_class, MutableComposite)):
|
||||
prop.composite_class._listen_on_attribute(
|
||||
getattr(class_, prop.key), False, class_)
|
||||
if not Mapper.dispatch.mapper_configured._contains(Mapper, _listen_for_type):
|
||||
event.listen(Mapper, 'mapper_configured', _listen_for_type)
|
||||
_setup_composite_listener()
|
||||
|
||||
|
||||
class MutableDict(Mutable, dict):
|
||||
|
||||
@@ -54,6 +54,7 @@ needed for:
|
||||
from ..orm import class_mapper
|
||||
from ..orm.session import Session
|
||||
from ..orm.mapper import Mapper
|
||||
from ..orm.interfaces import MapperProperty
|
||||
from ..orm.attributes import QueryableAttribute
|
||||
from .. import Table, Column
|
||||
from ..engine import Engine
|
||||
@@ -90,6 +91,9 @@ def Serializer(*args, **kw):
|
||||
id = "attribute:" + key + ":" + b64encode(pickle.dumps(cls))
|
||||
elif isinstance(obj, Mapper) and not obj.non_primary:
|
||||
id = "mapper:" + b64encode(pickle.dumps(obj.class_))
|
||||
elif isinstance(obj, MapperProperty) and not obj.parent.non_primary:
|
||||
id = "mapperprop:" + b64encode(pickle.dumps(obj.parent.class_)) + \
|
||||
":" + obj.key
|
||||
elif isinstance(obj, Table):
|
||||
id = "table:" + str(obj)
|
||||
elif isinstance(obj, Column) and isinstance(obj.table, Table):
|
||||
@@ -134,6 +138,10 @@ def Deserializer(file, metadata=None, scoped_session=None, engine=None):
|
||||
elif type_ == "mapper":
|
||||
cls = pickle.loads(b64decode(args))
|
||||
return class_mapper(cls)
|
||||
elif type_ == "mapperprop":
|
||||
mapper, keyname = args.split(':')
|
||||
cls = pickle.loads(b64decode(args))
|
||||
return class_mapper(cls).attrs[keyname]
|
||||
elif type_ == "table":
|
||||
return metadata.tables[args]
|
||||
elif type_ == "column":
|
||||
|
||||
@@ -219,6 +219,10 @@ class MapperProperty(_MappedAttribute, _InspectionAttr):
|
||||
|
||||
return operator(self.comparator, value)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s at 0x%x; %s>' % (
|
||||
self.__class__.__name__,
|
||||
id(self), self.key)
|
||||
|
||||
class PropComparator(operators.ColumnOperators):
|
||||
"""Defines boolean, comparison, and other operators for
|
||||
@@ -413,21 +417,18 @@ class StrategizedProperty(MapperProperty):
|
||||
return None
|
||||
|
||||
def _get_context_strategy(self, context, path):
|
||||
# this is essentially performance inlining.
|
||||
key = ('loaderstrategy', path.reduced_path + (self.key,))
|
||||
cls = None
|
||||
if key in context.attributes:
|
||||
cls = context.attributes[key]
|
||||
else:
|
||||
strategy_cls = path._inlined_get_for(self, context, 'loaderstrategy')
|
||||
|
||||
if not strategy_cls:
|
||||
wc_key = self._wildcard_path
|
||||
if wc_key and wc_key in context.attributes:
|
||||
cls = context.attributes[wc_key]
|
||||
strategy_cls = context.attributes[wc_key]
|
||||
|
||||
if cls:
|
||||
if strategy_cls:
|
||||
try:
|
||||
return self._strategies[cls]
|
||||
return self._strategies[strategy_cls]
|
||||
except KeyError:
|
||||
return self.__init_strategy(cls)
|
||||
return self.__init_strategy(strategy_cls)
|
||||
return self.strategy
|
||||
|
||||
def _get_strategy(self, cls):
|
||||
@@ -528,10 +529,8 @@ class PropertyOption(MapperOption):
|
||||
def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
|
||||
if orm_util._is_aliased_class(mapper):
|
||||
searchfor = mapper
|
||||
isa = False
|
||||
else:
|
||||
searchfor = orm_util._class_to_mapper(mapper)
|
||||
isa = True
|
||||
for ent in query._mapper_entities:
|
||||
if ent.corresponds_to(searchfor):
|
||||
return ent
|
||||
@@ -600,7 +599,7 @@ class PropertyOption(MapperOption):
|
||||
# exhaust current_path before
|
||||
# matching tokens to entities
|
||||
if current_path:
|
||||
if current_path[1] == token:
|
||||
if current_path[1].key == token:
|
||||
current_path = current_path[2:]
|
||||
continue
|
||||
else:
|
||||
@@ -634,7 +633,7 @@ class PropertyOption(MapperOption):
|
||||
# matching tokens to entities
|
||||
if current_path:
|
||||
if current_path[0:2] == \
|
||||
[token._parententity, prop.key]:
|
||||
[token._parententity, prop]:
|
||||
current_path = current_path[2:]
|
||||
continue
|
||||
else:
|
||||
@@ -648,6 +647,7 @@ class PropertyOption(MapperOption):
|
||||
raiseerr)
|
||||
if not entity:
|
||||
return no_result
|
||||
|
||||
path_element = entity.entity_zero
|
||||
mapper = entity.mapper
|
||||
else:
|
||||
@@ -659,7 +659,7 @@ class PropertyOption(MapperOption):
|
||||
raise sa_exc.ArgumentError("Attribute '%s' does not "
|
||||
"link from element '%s'" % (token, path_element))
|
||||
|
||||
path = path[path_element][prop.key]
|
||||
path = path[path_element][prop]
|
||||
|
||||
paths.append(path)
|
||||
|
||||
@@ -670,7 +670,8 @@ class PropertyOption(MapperOption):
|
||||
if not ext_info.is_aliased_class:
|
||||
ac = orm_util.with_polymorphic(
|
||||
ext_info.mapper.base_mapper,
|
||||
ext_info.mapper, aliased=True)
|
||||
ext_info.mapper, aliased=True,
|
||||
_use_mapper_path=True)
|
||||
ext_info = inspect(ac)
|
||||
path.set(query, "path_with_polymorphic", ext_info)
|
||||
else:
|
||||
|
||||
@@ -271,6 +271,7 @@ def instance_processor(mapper, context, path, adapter,
|
||||
new_populators = []
|
||||
existing_populators = []
|
||||
eager_populators = []
|
||||
|
||||
load_path = context.query._current_path + path \
|
||||
if context.query._current_path.path \
|
||||
else path
|
||||
@@ -504,9 +505,12 @@ def _populators(mapper, context, path, row, adapter,
|
||||
delayed_populators = []
|
||||
pops = (new_populators, existing_populators, delayed_populators,
|
||||
eager_populators)
|
||||
|
||||
for prop in mapper._props.itervalues():
|
||||
|
||||
for i, pop in enumerate(prop.create_row_processor(
|
||||
context, path,
|
||||
context,
|
||||
path,
|
||||
mapper, row, adapter)):
|
||||
if pop is not None:
|
||||
pops[i].append((prop.key, pop))
|
||||
@@ -529,17 +533,10 @@ def _configure_subclass_mapper(mapper, context, path, adapter):
|
||||
if sub_mapper is mapper:
|
||||
return None
|
||||
|
||||
# replace the tip of the path info with the subclass mapper
|
||||
# being used, that way accurate "load_path" info is available
|
||||
# for options invoked during deferred loads, e.g.
|
||||
# query(Person).options(defer(Engineer.machines, Machine.name)).
|
||||
# for AliasedClass paths, disregard this step (new in 0.8).
|
||||
return instance_processor(
|
||||
sub_mapper,
|
||||
context,
|
||||
path.parent[sub_mapper]
|
||||
if not path.is_aliased_class
|
||||
else path,
|
||||
path,
|
||||
adapter,
|
||||
polymorphic_from=mapper)
|
||||
return configure_subclass_mapper
|
||||
|
||||
@@ -472,7 +472,7 @@ class Mapper(_InspectionAttr):
|
||||
dispatch = event.dispatcher(events.MapperEvents)
|
||||
|
||||
@util.memoized_property
|
||||
def _sa_path_registry(self):
|
||||
def _path_registry(self):
|
||||
return PathRegistry.per_mapper(self)
|
||||
|
||||
def _configure_inheritance(self):
|
||||
@@ -1403,7 +1403,7 @@ class Mapper(_InspectionAttr):
|
||||
if _new_mappers:
|
||||
configure_mappers()
|
||||
if not self.with_polymorphic:
|
||||
return [self]
|
||||
return []
|
||||
return self._mappers_from_spec(*self.with_polymorphic)
|
||||
|
||||
@_memoized_configured_property
|
||||
@@ -1458,10 +1458,10 @@ class Mapper(_InspectionAttr):
|
||||
return list(self._iterate_polymorphic_properties(
|
||||
self._with_polymorphic_mappers))
|
||||
|
||||
|
||||
def _iterate_polymorphic_properties(self, mappers=None):
|
||||
"""Return an iterator of MapperProperty objects which will render into
|
||||
a SELECT."""
|
||||
|
||||
if mappers is None:
|
||||
mappers = self._with_polymorphic_mappers
|
||||
|
||||
|
||||
+25
-12
@@ -157,7 +157,7 @@ class Query(object):
|
||||
ent.setup_entity(*d[entity])
|
||||
|
||||
def _mapper_loads_polymorphically_with(self, mapper, adapter):
|
||||
for m2 in mapper._with_polymorphic_mappers:
|
||||
for m2 in mapper._with_polymorphic_mappers or [mapper]:
|
||||
self._polymorphic_adapters[m2] = adapter
|
||||
for m in m2.iterate_to_root():
|
||||
self._polymorphic_adapters[m.local_table] = adapter
|
||||
@@ -2744,17 +2744,24 @@ class _MapperEntity(_QueryEntity):
|
||||
self._with_polymorphic = ext_info.with_polymorphic_mappers
|
||||
self._polymorphic_discriminator = \
|
||||
ext_info.polymorphic_on
|
||||
self.entity_zero = ext_info
|
||||
if ext_info.is_aliased_class:
|
||||
self.entity_zero = ext_info.entity
|
||||
self._label_name = self.entity_zero._sa_label_name
|
||||
self._label_name = self.entity_zero.name
|
||||
else:
|
||||
self.entity_zero = self.mapper
|
||||
self._label_name = self.mapper.class_.__name__
|
||||
self.path = self.entity_zero._sa_path_registry
|
||||
self.path = self.entity_zero._path_registry
|
||||
|
||||
def set_with_polymorphic(self, query, cls_or_mappers,
|
||||
selectable, polymorphic_on):
|
||||
"""Receive an update from a call to query.with_polymorphic().
|
||||
|
||||
Note the newer style of using a free standing with_polymporphic()
|
||||
construct doesn't make use of this method.
|
||||
|
||||
|
||||
"""
|
||||
if self.is_aliased_class:
|
||||
# TODO: invalidrequest ?
|
||||
raise NotImplementedError(
|
||||
"Can't use with_polymorphic() against "
|
||||
"an Aliased object"
|
||||
@@ -2785,13 +2792,18 @@ class _MapperEntity(_QueryEntity):
|
||||
return self.entity_zero
|
||||
|
||||
def corresponds_to(self, entity):
|
||||
entity_info = inspect(entity)
|
||||
if entity_info.is_aliased_class or self.is_aliased_class:
|
||||
return entity is self.entity_zero \
|
||||
or \
|
||||
entity in self._with_polymorphic
|
||||
else:
|
||||
return entity.common_parent(self.entity_zero)
|
||||
if entity.is_aliased_class:
|
||||
if self.is_aliased_class:
|
||||
if entity._base_alias is self.entity_zero._base_alias:
|
||||
return True
|
||||
return False
|
||||
elif self.is_aliased_class:
|
||||
if self.entity_zero._use_mapper_path:
|
||||
return entity in self._with_polymorphic
|
||||
else:
|
||||
return entity is self.entity_zero
|
||||
|
||||
return entity.common_parent(self.entity_zero)
|
||||
|
||||
def adapt_to_selectable(self, query, sel):
|
||||
query._entities.append(self)
|
||||
@@ -3008,6 +3020,7 @@ class _ColumnEntity(_QueryEntity):
|
||||
if self.entity_zero is None:
|
||||
return False
|
||||
elif _is_aliased_class(entity):
|
||||
# TODO: polymorphic subclasses ?
|
||||
return entity is self.entity_zero
|
||||
else:
|
||||
return not _is_aliased_class(self.entity_zero) and \
|
||||
|
||||
@@ -457,22 +457,29 @@ class Session(_SessionClassMethods):
|
||||
generate a :class:`.Session`-producing callable with a given
|
||||
set of arguments.
|
||||
|
||||
:param autocommit: Defaults to ``False``. When ``True``, the
|
||||
``Session`` does not keep a persistent transaction running, and
|
||||
:param autocommit:
|
||||
|
||||
.. warning::
|
||||
|
||||
The autocommit flag is **not for general use**, and if it is used,
|
||||
queries should only be invoked within the span of a
|
||||
:meth:`.Session.begin` / :meth:`.Session.commit` pair. Executing
|
||||
queries outside of a demarcated transaction is a legacy mode
|
||||
of usage, and can in some cases lead to concurrent connection
|
||||
checkouts.
|
||||
|
||||
Defaults to ``False``. When ``True``, the
|
||||
:class:`.Session` does not keep a persistent transaction running, and
|
||||
will acquire connections from the engine on an as-needed basis,
|
||||
returning them immediately after their use. Flushes will begin and
|
||||
commit (or possibly rollback) their own transaction if no
|
||||
transaction is present. When using this mode, the
|
||||
`session.begin()` method may be used to begin a transaction
|
||||
explicitly.
|
||||
:meth:`.Session.begin` method is used to explicitly start
|
||||
transactions.
|
||||
|
||||
Leaving it on its default value of ``False`` means that the
|
||||
``Session`` will acquire a connection and begin a transaction the
|
||||
first time it is used, which it will maintain persistently until
|
||||
``rollback()``, ``commit()``, or ``close()`` is called. When the
|
||||
transaction is released by any of these methods, the ``Session``
|
||||
is ready for the next usage, which will again acquire and maintain
|
||||
a new connection/transaction.
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_autocommit`
|
||||
|
||||
:param autoflush: When ``True``, all query operations will issue a
|
||||
``flush()`` call to this ``Session`` before proceeding. This is a
|
||||
|
||||
@@ -303,16 +303,6 @@ class AbstractRelationshipLoader(LoaderStrategy):
|
||||
self.uselist = self.parent_property.uselist
|
||||
|
||||
|
||||
def _warn_existing_path(self):
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"Eager loading cannot currently function correctly when two or "
|
||||
"more "
|
||||
"same-named attributes associated with multiple polymorphic "
|
||||
"classes "
|
||||
"of the same base are present. Encountered more than one "
|
||||
r"eager path for attribute '%s' on mapper '%s'." %
|
||||
(self.key, self.parent.base_mapper, ))
|
||||
|
||||
|
||||
class NoLoader(AbstractRelationshipLoader):
|
||||
"""Provide loading behavior for a :class:`.RelationshipProperty`
|
||||
@@ -564,7 +554,7 @@ class LazyLoader(AbstractRelationshipLoader):
|
||||
q = q.autoflush(False)
|
||||
|
||||
if state.load_path:
|
||||
q = q._with_current_path(state.load_path[self.key])
|
||||
q = q._with_current_path(state.load_path[self.parent_property])
|
||||
|
||||
if state.load_options:
|
||||
q = q._conditional_options(*state.load_options)
|
||||
@@ -694,7 +684,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
|
||||
if not context.query._enable_eagerloads:
|
||||
return
|
||||
|
||||
path = path[self.key]
|
||||
path = path[self.parent_property]
|
||||
|
||||
# build up a path indicating the path from the leftmost
|
||||
# entity to the thing we're subquery loading.
|
||||
@@ -757,22 +747,20 @@ class SubqueryLoader(AbstractRelationshipLoader):
|
||||
|
||||
# add new query to attributes to be picked up
|
||||
# by create_row_processor
|
||||
existing = path.replace(context, "subquery", q)
|
||||
if existing:
|
||||
self._warn_existing_path()
|
||||
path.set(context, "subquery", q)
|
||||
|
||||
def _get_leftmost(self, subq_path):
|
||||
subq_path = subq_path.path
|
||||
subq_mapper = orm_util._class_to_mapper(subq_path[0])
|
||||
|
||||
# determine attributes of the leftmost mapper
|
||||
if self.parent.isa(subq_mapper) and self.key == subq_path[1]:
|
||||
if self.parent.isa(subq_mapper) and self.parent_property is subq_path[1]:
|
||||
leftmost_mapper, leftmost_prop = \
|
||||
self.parent, self.parent_property
|
||||
else:
|
||||
leftmost_mapper, leftmost_prop = \
|
||||
subq_mapper, \
|
||||
subq_mapper._props[subq_path[1]]
|
||||
subq_path[1]
|
||||
|
||||
leftmost_cols = leftmost_prop.local_columns
|
||||
|
||||
@@ -805,23 +793,35 @@ class SubqueryLoader(AbstractRelationshipLoader):
|
||||
# the original query now becomes a subquery
|
||||
# which we'll join onto.
|
||||
embed_q = q.with_labels().subquery()
|
||||
left_alias = orm_util.AliasedClass(leftmost_mapper, embed_q)
|
||||
left_alias = orm_util.AliasedClass(leftmost_mapper, embed_q,
|
||||
use_mapper_path=True)
|
||||
return left_alias
|
||||
|
||||
def _prep_for_joins(self, left_alias, subq_path):
|
||||
subq_path = subq_path.path
|
||||
|
||||
# figure out what's being joined. a.k.a. the fun part
|
||||
to_join = [
|
||||
(subq_path[i], subq_path[i + 1])
|
||||
for i in xrange(0, len(subq_path), 2)
|
||||
]
|
||||
to_join = []
|
||||
pairs = list(subq_path.pairs())
|
||||
|
||||
for i, (mapper, prop) in enumerate(pairs):
|
||||
if i > 0:
|
||||
# look at the previous mapper in the chain -
|
||||
# if it is as or more specific than this prop's
|
||||
# mapper, use that instead.
|
||||
# note we have an assumption here that
|
||||
# the non-first element is always going to be a mapper,
|
||||
# not an AliasedClass
|
||||
|
||||
prev_mapper = pairs[i - 1][1].mapper
|
||||
to_append = prev_mapper if prev_mapper.isa(mapper) else mapper
|
||||
else:
|
||||
to_append = mapper
|
||||
|
||||
to_join.append((to_append, prop.key))
|
||||
|
||||
# determine the immediate parent class we are joining from,
|
||||
# which needs to be aliased.
|
||||
|
||||
if len(to_join) > 1:
|
||||
info = inspect(subq_path[-2])
|
||||
info = inspect(to_join[-1][0])
|
||||
|
||||
if len(to_join) < 2:
|
||||
# in the case of a one level eager load, this is the
|
||||
@@ -833,11 +833,13 @@ class SubqueryLoader(AbstractRelationshipLoader):
|
||||
# in the vast majority of cases, and [ticket:2014]
|
||||
# illustrates a case where sub_path[-2] is a subclass
|
||||
# of self.parent
|
||||
parent_alias = orm_util.AliasedClass(subq_path[-2])
|
||||
parent_alias = orm_util.AliasedClass(to_join[-1][0],
|
||||
use_mapper_path=True)
|
||||
else:
|
||||
# if of_type() were used leading to this relationship,
|
||||
# self.parent is more specific than subq_path[-2]
|
||||
parent_alias = orm_util.AliasedClass(self.parent)
|
||||
parent_alias = orm_util.AliasedClass(self.parent,
|
||||
use_mapper_path=True)
|
||||
|
||||
local_cols = self.parent_property.local_columns
|
||||
|
||||
@@ -916,9 +918,10 @@ class SubqueryLoader(AbstractRelationshipLoader):
|
||||
"population - eager loading cannot be applied." %
|
||||
self)
|
||||
|
||||
path = path[self.key]
|
||||
path = path[self.parent_property]
|
||||
|
||||
subq = path.get(context, 'subquery')
|
||||
|
||||
if subq is None:
|
||||
return None, None, None
|
||||
|
||||
@@ -1000,7 +1003,7 @@ class JoinedLoader(AbstractRelationshipLoader):
|
||||
if not context.query._enable_eagerloads:
|
||||
return
|
||||
|
||||
path = path[self.key]
|
||||
path = path[self.parent_property]
|
||||
|
||||
with_polymorphic = None
|
||||
|
||||
@@ -1040,6 +1043,7 @@ class JoinedLoader(AbstractRelationshipLoader):
|
||||
with_polymorphic = None
|
||||
|
||||
path = path[self.mapper]
|
||||
|
||||
for value in self.mapper._iterate_polymorphic_properties(
|
||||
mappers=with_polymorphic):
|
||||
value.setup(
|
||||
@@ -1079,7 +1083,8 @@ class JoinedLoader(AbstractRelationshipLoader):
|
||||
if with_poly_info:
|
||||
to_adapt = with_poly_info.entity
|
||||
else:
|
||||
to_adapt = orm_util.AliasedClass(self.mapper)
|
||||
to_adapt = orm_util.AliasedClass(self.mapper,
|
||||
use_mapper_path=True)
|
||||
clauses = orm_util.ORMAdapter(
|
||||
to_adapt,
|
||||
equivalents=self.mapper._equivalent_columns,
|
||||
@@ -1104,9 +1109,8 @@ class JoinedLoader(AbstractRelationshipLoader):
|
||||
)
|
||||
|
||||
add_to_collection = context.secondary_columns
|
||||
existing = path.replace(context, "eager_row_processor", clauses)
|
||||
if existing:
|
||||
self._warn_existing_path()
|
||||
path.set(context, "eager_row_processor", clauses)
|
||||
|
||||
return clauses, adapter, add_to_collection, allow_innerjoin
|
||||
|
||||
def _create_eager_join(self, context, entity,
|
||||
@@ -1154,7 +1158,8 @@ class JoinedLoader(AbstractRelationshipLoader):
|
||||
onclause = getattr(
|
||||
orm_util.AliasedClass(
|
||||
self.parent,
|
||||
adapter.selectable
|
||||
adapter.selectable,
|
||||
use_mapper_path=True
|
||||
),
|
||||
self.key, self.parent_property
|
||||
)
|
||||
@@ -1238,7 +1243,7 @@ class JoinedLoader(AbstractRelationshipLoader):
|
||||
"population - eager loading cannot be applied." %
|
||||
self)
|
||||
|
||||
our_path = path[self.key]
|
||||
our_path = path[self.parent_property]
|
||||
|
||||
eager_adapter = self._create_eager_adapter(
|
||||
context,
|
||||
@@ -1391,15 +1396,13 @@ class LoadEagerFromAliasOption(PropertyOption):
|
||||
def process_query_property(self, query, paths):
|
||||
if self.chained:
|
||||
for path in paths[0:-1]:
|
||||
(root_mapper, propname) = path.path[-2:]
|
||||
prop = root_mapper._props[propname]
|
||||
(root_mapper, prop) = path.path[-2:]
|
||||
adapter = query._polymorphic_adapters.get(prop.mapper, None)
|
||||
path.setdefault(query,
|
||||
"user_defined_eager_row_processor",
|
||||
adapter)
|
||||
|
||||
root_mapper, propname = paths[-1].path[-2:]
|
||||
prop = root_mapper._props[propname]
|
||||
root_mapper, prop = paths[-1].path[-2:]
|
||||
if self.alias is not None:
|
||||
if isinstance(self.alias, basestring):
|
||||
self.alias = prop.target.alias(self.alias)
|
||||
|
||||
+134
-57
@@ -245,6 +245,8 @@ class ORMAdapter(sql_util.ColumnAdapter):
|
||||
else:
|
||||
return None
|
||||
|
||||
def _unreduce_path(path):
|
||||
return PathRegistry.deserialize(path)
|
||||
|
||||
class PathRegistry(object):
|
||||
"""Represent query load paths and registry functions.
|
||||
@@ -277,19 +279,13 @@ class PathRegistry(object):
|
||||
self.path == other.path
|
||||
|
||||
def set(self, reg, key, value):
|
||||
reg._attributes[(key, self.reduced_path)] = value
|
||||
|
||||
def replace(self, reg, key, value):
|
||||
path_key = (key, self.reduced_path)
|
||||
existing = reg._attributes.get(path_key, None)
|
||||
reg._attributes[path_key] = value
|
||||
return existing
|
||||
reg._attributes[(key, self.path)] = value
|
||||
|
||||
def setdefault(self, reg, key, value):
|
||||
reg._attributes.setdefault((key, self.reduced_path), value)
|
||||
reg._attributes.setdefault((key, self.path), value)
|
||||
|
||||
def get(self, reg, key, value=None):
|
||||
key = (key, self.reduced_path)
|
||||
key = (key, self.path)
|
||||
if key in reg._attributes:
|
||||
return reg._attributes[key]
|
||||
else:
|
||||
@@ -302,17 +298,25 @@ class PathRegistry(object):
|
||||
def length(self):
|
||||
return len(self.path)
|
||||
|
||||
def pairs(self):
|
||||
path = self.path
|
||||
for i in xrange(0, len(path), 2):
|
||||
yield path[i], path[i + 1]
|
||||
|
||||
def contains_mapper(self, mapper):
|
||||
return mapper in self.path
|
||||
|
||||
def contains(self, reg, key):
|
||||
return (key, self.reduced_path) in reg._attributes
|
||||
return (key, self.path) in reg._attributes
|
||||
|
||||
def __reduce__(self):
|
||||
return _unreduce_path, (self.serialize(), )
|
||||
|
||||
def serialize(self):
|
||||
path = self.path
|
||||
return zip(
|
||||
[m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
|
||||
[path[i] for i in range(1, len(path), 2)] + [None]
|
||||
[path[i].key for i in range(1, len(path), 2)] + [None]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -320,7 +324,10 @@ class PathRegistry(object):
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
p = tuple(chain(*[(class_mapper(mcls), key) for mcls, key in path]))
|
||||
p = tuple(chain(*[(class_mapper(mcls),
|
||||
class_mapper(mcls).attrs[key]
|
||||
if key is not None else None)
|
||||
for mcls, key in path]))
|
||||
if p and p[-1] is None:
|
||||
p = p[0:-1]
|
||||
return cls.coerce(p)
|
||||
@@ -337,7 +344,7 @@ class PathRegistry(object):
|
||||
|
||||
@classmethod
|
||||
def token(cls, token):
|
||||
return KeyRegistry(cls.root, token)
|
||||
return TokenRegistry(cls.root, token)
|
||||
|
||||
def __add__(self, other):
|
||||
return util.reduce(
|
||||
@@ -354,19 +361,36 @@ class RootRegistry(PathRegistry):
|
||||
|
||||
"""
|
||||
path = ()
|
||||
reduced_path = ()
|
||||
|
||||
def __getitem__(self, mapper):
|
||||
return mapper._sa_path_registry
|
||||
def __getitem__(self, entity):
|
||||
return entity._path_registry
|
||||
PathRegistry.root = RootRegistry()
|
||||
|
||||
|
||||
class KeyRegistry(PathRegistry):
|
||||
def __init__(self, parent, key):
|
||||
self.key = key
|
||||
class TokenRegistry(PathRegistry):
|
||||
def __init__(self, parent, token):
|
||||
self.token = token
|
||||
self.parent = parent
|
||||
self.path = parent.path + (key,)
|
||||
self.reduced_path = parent.reduced_path + (key,)
|
||||
self.path = parent.path + (token,)
|
||||
|
||||
def __getitem__(self, entity):
|
||||
raise NotImplementedError()
|
||||
|
||||
class PropRegistry(PathRegistry):
|
||||
def __init__(self, parent, prop):
|
||||
# restate this path in terms of the
|
||||
# given MapperProperty's parent.
|
||||
insp = inspection.inspect(parent[-1])
|
||||
if not insp.is_aliased_class or insp._use_mapper_path:
|
||||
parent = parent.parent[prop.parent]
|
||||
elif insp.is_aliased_class and insp.with_polymorphic_mappers:
|
||||
if prop.parent is not insp.mapper and \
|
||||
prop.parent in insp.with_polymorphic_mappers:
|
||||
subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
|
||||
parent = parent.parent[subclass_entity]
|
||||
|
||||
self.prop = prop
|
||||
self.parent = parent
|
||||
self.path = parent.path + (prop,)
|
||||
|
||||
def __getitem__(self, entity):
|
||||
if isinstance(entity, (int, slice)):
|
||||
@@ -381,15 +405,11 @@ class EntityRegistry(PathRegistry, dict):
|
||||
is_aliased_class = False
|
||||
|
||||
def __init__(self, parent, entity):
|
||||
self.key = reduced_key = entity
|
||||
self.key = entity
|
||||
self.parent = parent
|
||||
if hasattr(entity, 'base_mapper'):
|
||||
reduced_key = entity.base_mapper
|
||||
else:
|
||||
self.is_aliased_class = True
|
||||
self.is_aliased_class = entity.is_aliased_class
|
||||
|
||||
self.path = parent.path + (entity,)
|
||||
self.reduced_path = parent.reduced_path + (reduced_key,)
|
||||
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
@@ -400,8 +420,26 @@ class EntityRegistry(PathRegistry, dict):
|
||||
else:
|
||||
return dict.__getitem__(self, entity)
|
||||
|
||||
def _inlined_get_for(self, prop, context, key):
|
||||
"""an inlined version of:
|
||||
|
||||
cls = path[mapperproperty].get(context, key)
|
||||
|
||||
Skips the isinstance() check in __getitem__
|
||||
and the extra method call for get().
|
||||
Used by StrategizedProperty for its
|
||||
very frequent lookup.
|
||||
|
||||
"""
|
||||
path = dict.__getitem__(self, prop)
|
||||
path_key = (key, path.path)
|
||||
if path_key in context._attributes:
|
||||
return context._attributes[path_key]
|
||||
else:
|
||||
return None
|
||||
|
||||
def __missing__(self, key):
|
||||
self[key] = item = KeyRegistry(self, key)
|
||||
self[key] = item = PropRegistry(self, key)
|
||||
return item
|
||||
|
||||
|
||||
@@ -448,8 +486,11 @@ class AliasedClass(object):
|
||||
def __init__(self, cls, alias=None,
|
||||
name=None,
|
||||
adapt_on_names=False,
|
||||
# TODO: None for default here?
|
||||
with_polymorphic_mappers=(),
|
||||
with_polymorphic_discriminator=None):
|
||||
with_polymorphic_discriminator=None,
|
||||
base_alias=None,
|
||||
use_mapper_path=False):
|
||||
mapper = _class_to_mapper(cls)
|
||||
if alias is None:
|
||||
alias = mapper._with_polymorphic_selectable.alias(name=name)
|
||||
@@ -458,11 +499,19 @@ class AliasedClass(object):
|
||||
mapper,
|
||||
alias,
|
||||
name,
|
||||
with_polymorphic_mappers,
|
||||
with_polymorphic_mappers
|
||||
if with_polymorphic_mappers
|
||||
else mapper.with_polymorphic_mappers,
|
||||
with_polymorphic_discriminator
|
||||
if with_polymorphic_discriminator is not None
|
||||
else mapper.polymorphic_on,
|
||||
base_alias,
|
||||
use_mapper_path
|
||||
)
|
||||
|
||||
self._setup(self._aliased_insp, adapt_on_names)
|
||||
|
||||
|
||||
def _setup(self, aliased_insp, adapt_on_names):
|
||||
self.__adapt_on_names = adapt_on_names
|
||||
mapper = aliased_insp.mapper
|
||||
@@ -473,18 +522,13 @@ class AliasedClass(object):
|
||||
equivalents=mapper._equivalent_columns,
|
||||
adapt_on_names=self.__adapt_on_names)
|
||||
for poly in aliased_insp.with_polymorphic_mappers:
|
||||
setattr(self, poly.class_.__name__,
|
||||
AliasedClass(poly.class_, alias))
|
||||
if poly is not mapper:
|
||||
setattr(self, poly.class_.__name__,
|
||||
AliasedClass(poly.class_, alias, base_alias=self,
|
||||
use_mapper_path=self._aliased_insp._use_mapper_path))
|
||||
|
||||
# used to assign a name to the RowTuple object
|
||||
# returned by Query.
|
||||
self._sa_label_name = aliased_insp.name
|
||||
self.__name__ = 'AliasedClass_%s' % self.__target.__name__
|
||||
|
||||
@util.memoized_property
|
||||
def _sa_path_registry(self):
|
||||
return PathRegistry.per_mapper(self)
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'mapper': self._aliased_insp.mapper,
|
||||
@@ -494,7 +538,9 @@ class AliasedClass(object):
|
||||
'with_polymorphic_mappers':
|
||||
self._aliased_insp.with_polymorphic_mappers,
|
||||
'with_polymorphic_discriminator':
|
||||
self._aliased_insp.polymorphic_on
|
||||
self._aliased_insp.polymorphic_on,
|
||||
'base_alias': self._aliased_insp._base_alias.entity,
|
||||
'use_mapper_path': self._aliased_insp._use_mapper_path
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
@@ -503,8 +549,10 @@ class AliasedClass(object):
|
||||
state['mapper'],
|
||||
state['alias'],
|
||||
state['name'],
|
||||
state.get('with_polymorphic_mappers'),
|
||||
state.get('with_polymorphic_discriminator')
|
||||
state['with_polymorphic_mappers'],
|
||||
state['with_polymorphic_discriminator'],
|
||||
state['base_alias'],
|
||||
state['use_mapper_path']
|
||||
)
|
||||
self._setup(self._aliased_insp, state['adapt_on_names'])
|
||||
|
||||
@@ -521,7 +569,7 @@ class AliasedClass(object):
|
||||
queryattr = attributes.QueryableAttribute(
|
||||
self, key,
|
||||
impl=existing.impl,
|
||||
parententity=self,
|
||||
parententity=self._aliased_insp,
|
||||
comparator=comparator)
|
||||
setattr(self, key, queryattr)
|
||||
return queryattr
|
||||
@@ -558,17 +606,7 @@ class AliasedClass(object):
|
||||
id(self), self.__target.__name__)
|
||||
|
||||
|
||||
AliasedInsp = util.namedtuple("AliasedInsp", [
|
||||
"entity",
|
||||
"mapper",
|
||||
"selectable",
|
||||
"name",
|
||||
"with_polymorphic_mappers",
|
||||
"polymorphic_on"
|
||||
])
|
||||
|
||||
|
||||
class AliasedInsp(_InspectionAttr, AliasedInsp):
|
||||
class AliasedInsp(_InspectionAttr):
|
||||
"""Provide an inspection interface for an
|
||||
:class:`.AliasedClass` object.
|
||||
|
||||
@@ -604,6 +642,22 @@ class AliasedInsp(_InspectionAttr, AliasedInsp):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, entity, mapper, selectable, name,
|
||||
with_polymorphic_mappers, polymorphic_on,
|
||||
_base_alias, _use_mapper_path):
|
||||
self.entity = entity
|
||||
self.mapper = mapper
|
||||
self.selectable = selectable
|
||||
self.name = name
|
||||
self.with_polymorphic_mappers = with_polymorphic_mappers
|
||||
self.polymorphic_on = polymorphic_on
|
||||
|
||||
# a little dance to get serialization to work
|
||||
self._base_alias = _base_alias._aliased_insp if _base_alias \
|
||||
and _base_alias is not entity else self
|
||||
self._use_mapper_path = _use_mapper_path
|
||||
|
||||
|
||||
is_aliased_class = True
|
||||
"always returns True"
|
||||
|
||||
@@ -613,8 +667,29 @@ class AliasedInsp(_InspectionAttr, AliasedInsp):
|
||||
:class:`.AliasedInsp`."""
|
||||
return self.mapper.class_
|
||||
|
||||
@util.memoized_property
|
||||
def _path_registry(self):
|
||||
if self._use_mapper_path:
|
||||
return self.mapper._path_registry
|
||||
else:
|
||||
return PathRegistry.per_mapper(self)
|
||||
|
||||
def _entity_for_mapper(self, mapper):
|
||||
self_poly = self.with_polymorphic_mappers
|
||||
if mapper in self_poly:
|
||||
return getattr(self.entity, mapper.class_.__name__)._aliased_insp
|
||||
elif mapper.isa(self.mapper):
|
||||
return self
|
||||
else:
|
||||
assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<AliasedInsp at 0x%x; %s>' % (
|
||||
id(self), self.class_.__name__)
|
||||
|
||||
|
||||
inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
|
||||
inspection._inspects(AliasedInsp)(lambda target: target)
|
||||
|
||||
|
||||
def aliased(element, alias=None, name=None, adapt_on_names=False):
|
||||
@@ -699,7 +774,7 @@ def aliased(element, alias=None, name=None, adapt_on_names=False):
|
||||
|
||||
def with_polymorphic(base, classes, selectable=False,
|
||||
polymorphic_on=None, aliased=False,
|
||||
innerjoin=False):
|
||||
innerjoin=False, _use_mapper_path=False):
|
||||
"""Produce an :class:`.AliasedClass` construct which specifies
|
||||
columns for descendant mappers of the given base.
|
||||
|
||||
@@ -758,7 +833,8 @@ def with_polymorphic(base, classes, selectable=False,
|
||||
return AliasedClass(base,
|
||||
selectable,
|
||||
with_polymorphic_mappers=mappers,
|
||||
with_polymorphic_discriminator=polymorphic_on)
|
||||
with_polymorphic_discriminator=polymorphic_on,
|
||||
use_mapper_path=_use_mapper_path)
|
||||
|
||||
|
||||
def _orm_annotate(element, exclude=None):
|
||||
@@ -1109,6 +1185,7 @@ def _entity_descriptor(entity, key):
|
||||
description = entity
|
||||
entity = insp.c
|
||||
elif insp.is_aliased_class:
|
||||
entity = insp.entity
|
||||
description = entity
|
||||
elif hasattr(insp, "mapper"):
|
||||
description = entity = insp.mapper.class_
|
||||
|
||||
@@ -2084,7 +2084,15 @@ class GenericTypeCompiler(engine.TypeCompiler):
|
||||
'scale': type_.scale}
|
||||
|
||||
def visit_DECIMAL(self, type_):
|
||||
return "DECIMAL"
|
||||
if type_.precision is None:
|
||||
return "DECIMAL"
|
||||
elif type_.scale is None:
|
||||
return "DECIMAL(%(precision)s)" % \
|
||||
{'precision': type_.precision}
|
||||
else:
|
||||
return "DECIMAL(%(precision)s, %(scale)s)" % \
|
||||
{'precision': type_.precision,
|
||||
'scale': type_.scale}
|
||||
|
||||
def visit_INTEGER(self, type_):
|
||||
return "INTEGER"
|
||||
|
||||
@@ -3723,6 +3723,7 @@ class BinaryExpression(ColumnElement):
|
||||
# refer to BinaryExpression directly and pass strings
|
||||
if isinstance(operator, basestring):
|
||||
operator = operators.custom_op(operator)
|
||||
self._orig = (left, right)
|
||||
self.left = _literal_as_text(left).self_group(against=operator)
|
||||
self.right = _literal_as_text(right).self_group(against=operator)
|
||||
self.operator = operator
|
||||
@@ -3735,9 +3736,9 @@ class BinaryExpression(ColumnElement):
|
||||
self.modifiers = modifiers
|
||||
|
||||
def __nonzero__(self):
|
||||
try:
|
||||
return self.operator(hash(self.left), hash(self.right))
|
||||
except:
|
||||
if self.operator in (operator.eq, operator.ne):
|
||||
return self.operator(hash(self._orig[0]), hash(self._orig[1]))
|
||||
else:
|
||||
raise TypeError("Boolean value of this clause is not defined")
|
||||
|
||||
@property
|
||||
|
||||
@@ -426,12 +426,14 @@ class MemUsageTest(EnsureZeroed):
|
||||
metadata = MetaData()
|
||||
a = Table("a", metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('foo', Integer),
|
||||
Column('bar', Integer)
|
||||
)
|
||||
m1 = mapper(A, a)
|
||||
@profile_memory()
|
||||
def go():
|
||||
ma = aliased(A)
|
||||
m1._sa_path_registry['foo'][ma]['bar']
|
||||
ma = sa.inspect(aliased(A))
|
||||
m1._path_registry[m1.attrs.foo][ma][m1.attrs.bar]
|
||||
go()
|
||||
clear_mappers()
|
||||
|
||||
|
||||
+95
-24
@@ -59,35 +59,35 @@ class _MutableDictTestBase(object):
|
||||
assert_raises_message(
|
||||
ValueError,
|
||||
"Attribute 'data' does not accept objects of type",
|
||||
Foo, data=set([1,2,3])
|
||||
Foo, data=set([1, 2, 3])
|
||||
)
|
||||
|
||||
def test_in_place_mutation(self):
|
||||
sess = Session()
|
||||
|
||||
f1 = Foo(data={'a':'b'})
|
||||
f1 = Foo(data={'a': 'b'})
|
||||
sess.add(f1)
|
||||
sess.commit()
|
||||
|
||||
f1.data['a'] = 'c'
|
||||
sess.commit()
|
||||
|
||||
eq_(f1.data, {'a':'c'})
|
||||
eq_(f1.data, {'a': 'c'})
|
||||
|
||||
def test_replace(self):
|
||||
sess = Session()
|
||||
f1 = Foo(data={'a':'b'})
|
||||
f1 = Foo(data={'a': 'b'})
|
||||
sess.add(f1)
|
||||
sess.flush()
|
||||
|
||||
f1.data = {'b':'c'}
|
||||
f1.data = {'b': 'c'}
|
||||
sess.commit()
|
||||
eq_(f1.data, {'b':'c'})
|
||||
eq_(f1.data, {'b': 'c'})
|
||||
|
||||
def test_pickle_parent(self):
|
||||
sess = Session()
|
||||
|
||||
f1 = Foo(data={'a':'b'})
|
||||
f1 = Foo(data={'a': 'b'})
|
||||
sess.add(f1)
|
||||
sess.commit()
|
||||
f1.data
|
||||
@@ -102,7 +102,7 @@ class _MutableDictTestBase(object):
|
||||
|
||||
def test_unrelated_flush(self):
|
||||
sess = Session()
|
||||
f1 = Foo(data={"a":"b"}, unrelated_data="unrelated")
|
||||
f1 = Foo(data={"a": "b"}, unrelated_data="unrelated")
|
||||
sess.add(f1)
|
||||
sess.flush()
|
||||
f1.unrelated_data = "unrelated 2"
|
||||
@@ -114,14 +114,14 @@ class _MutableDictTestBase(object):
|
||||
def _test_non_mutable(self):
|
||||
sess = Session()
|
||||
|
||||
f1 = Foo(non_mutable_data={'a':'b'})
|
||||
f1 = Foo(non_mutable_data={'a': 'b'})
|
||||
sess.add(f1)
|
||||
sess.commit()
|
||||
|
||||
f1.non_mutable_data['a'] = 'c'
|
||||
sess.commit()
|
||||
|
||||
eq_(f1.non_mutable_data, {'a':'b'})
|
||||
eq_(f1.non_mutable_data, {'a': 'b'})
|
||||
|
||||
class MutableWithScalarPickleTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
@classmethod
|
||||
@@ -142,7 +142,7 @@ class MutableWithScalarPickleTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
|
||||
class MutableWithScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
# json introduced in 2.6
|
||||
__skip_if__ = lambda : sys.version_info < (2, 6),
|
||||
__skip_if__ = lambda: sys.version_info < (2, 6),
|
||||
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
@@ -177,7 +177,6 @@ class MutableWithScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
class MutableAssocWithAttrInheritTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
MutableDict = cls._type_fixture()
|
||||
|
||||
Table('foo', metadata,
|
||||
Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
|
||||
@@ -201,24 +200,24 @@ class MutableAssocWithAttrInheritTest(_MutableDictTestBase, fixtures.MappedTest)
|
||||
def test_in_place_mutation(self):
|
||||
sess = Session()
|
||||
|
||||
f1 = SubFoo(data={'a':'b'})
|
||||
f1 = SubFoo(data={'a': 'b'})
|
||||
sess.add(f1)
|
||||
sess.commit()
|
||||
|
||||
f1.data['a'] = 'c'
|
||||
sess.commit()
|
||||
|
||||
eq_(f1.data, {'a':'c'})
|
||||
eq_(f1.data, {'a': 'c'})
|
||||
|
||||
def test_replace(self):
|
||||
sess = Session()
|
||||
f1 = SubFoo(data={'a':'b'})
|
||||
f1 = SubFoo(data={'a': 'b'})
|
||||
sess.add(f1)
|
||||
sess.flush()
|
||||
|
||||
f1.data = {'b':'c'}
|
||||
f1.data = {'b': 'c'}
|
||||
sess.commit()
|
||||
eq_(f1.data, {'b':'c'})
|
||||
eq_(f1.data, {'b': 'c'})
|
||||
|
||||
class MutableAssociationScalarPickleTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
@classmethod
|
||||
@@ -235,7 +234,7 @@ class MutableAssociationScalarPickleTest(_MutableDictTestBase, fixtures.MappedTe
|
||||
|
||||
class MutableAssociationScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest):
|
||||
# json introduced in 2.6
|
||||
__skip_if__ = lambda : sys.version_info < (2, 6),
|
||||
__skip_if__ = lambda: sys.version_info < (2, 6),
|
||||
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
@@ -259,7 +258,8 @@ class MutableAssociationScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest
|
||||
MutableDict.associate_with(JSONEncodedDict)
|
||||
|
||||
Table('foo', metadata,
|
||||
Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
|
||||
Column('id', Integer, primary_key=True,
|
||||
test_needs_autoincrement=True),
|
||||
Column('data', JSONEncodedDict),
|
||||
Column('unrelated_data', String(50))
|
||||
)
|
||||
@@ -269,12 +269,19 @@ class _CompositeTestBase(object):
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
Table('foo', metadata,
|
||||
Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
|
||||
Column('id', Integer, primary_key=True,
|
||||
test_needs_autoincrement=True),
|
||||
Column('x', Integer),
|
||||
Column('y', Integer),
|
||||
Column('unrelated_data', String(50))
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
from sqlalchemy.ext import mutable
|
||||
mutable._setup_composite_listener()
|
||||
super(_CompositeTestBase, self).setup()
|
||||
|
||||
|
||||
def teardown(self):
|
||||
# clear out mapper events
|
||||
Mapper.dispatch._clear()
|
||||
@@ -284,7 +291,6 @@ class _CompositeTestBase(object):
|
||||
@classmethod
|
||||
def _type_fixture(cls):
|
||||
|
||||
from sqlalchemy.ext.mutable import Mutable
|
||||
from sqlalchemy.ext.mutable import MutableComposite
|
||||
|
||||
global Point
|
||||
@@ -322,7 +328,7 @@ class MutableCompositesUnpickleTest(_CompositeTestBase, fixtures.MappedTest):
|
||||
cls.Point = cls._type_fixture()
|
||||
|
||||
mapper(FooWithEq, foo, properties={
|
||||
'data':composite(cls.Point, foo.c.x, foo.c.y)
|
||||
'data': composite(cls.Point, foo.c.x, foo.c.y)
|
||||
})
|
||||
|
||||
def test_unpickle_modified_eq(self):
|
||||
@@ -339,7 +345,7 @@ class MutableCompositesTest(_CompositeTestBase, fixtures.MappedTest):
|
||||
Point = cls._type_fixture()
|
||||
|
||||
mapper(Foo, foo, properties={
|
||||
'data':composite(Point, foo.c.x, foo.c.y)
|
||||
'data': composite(Point, foo.c.x, foo.c.y)
|
||||
})
|
||||
|
||||
def test_in_place_mutation(self):
|
||||
@@ -403,6 +409,71 @@ class MutableCompositesTest(_CompositeTestBase, fixtures.MappedTest):
|
||||
|
||||
eq_(f1.data.x, 5)
|
||||
|
||||
class MutableCompositeCustomCoerceTest(_CompositeTestBase, fixtures.MappedTest):
|
||||
@classmethod
|
||||
def _type_fixture(cls):
|
||||
|
||||
from sqlalchemy.ext.mutable import MutableComposite
|
||||
|
||||
global Point
|
||||
|
||||
class Point(MutableComposite):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
if isinstance(value, tuple):
|
||||
value = Point(*value)
|
||||
return value
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
object.__setattr__(self, key, value)
|
||||
self.changed()
|
||||
|
||||
def __composite_values__(self):
|
||||
return self.x, self.y
|
||||
|
||||
def __getstate__(self):
|
||||
return self.x, self.y
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.x, self.y = state
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Point) and \
|
||||
other.x == self.x and \
|
||||
other.y == self.y
|
||||
return Point
|
||||
|
||||
|
||||
@classmethod
|
||||
def setup_mappers(cls):
|
||||
foo = cls.tables.foo
|
||||
|
||||
Point = cls._type_fixture()
|
||||
|
||||
mapper(Foo, foo, properties={
|
||||
'data': composite(Point, foo.c.x, foo.c.y)
|
||||
})
|
||||
|
||||
def test_custom_coerce(self):
|
||||
f = Foo()
|
||||
f.data = (3, 4)
|
||||
eq_(f.data, Point(3, 4))
|
||||
|
||||
def test_round_trip_ok(self):
|
||||
sess = Session()
|
||||
f = Foo()
|
||||
f.data = (3, 4)
|
||||
|
||||
sess.add(f)
|
||||
sess.commit()
|
||||
|
||||
eq_(f.data, Point(3, 4))
|
||||
|
||||
|
||||
class MutableInheritedCompositesTest(_CompositeTestBase, fixtures.MappedTest):
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
@@ -423,7 +494,7 @@ class MutableInheritedCompositesTest(_CompositeTestBase, fixtures.MappedTest):
|
||||
Point = cls._type_fixture()
|
||||
|
||||
mapper(Foo, foo, properties={
|
||||
'data':composite(Point, foo.c.x, foo.c.y)
|
||||
'data': composite(Point, foo.c.x, foo.c.y)
|
||||
})
|
||||
mapper(SubFoo, subfoo, inherits=Foo)
|
||||
|
||||
|
||||
@@ -496,41 +496,10 @@ class _PolymorphicTestBase(object):
|
||||
.all(),
|
||||
expected)
|
||||
|
||||
# TODO: this fails due to the change
|
||||
# in _configure_subclass_mapper. however we might not
|
||||
# need it anymore.
|
||||
def test_polymorphic_option(self):
|
||||
"""
|
||||
Test that polymorphic loading sets state.load_path with its
|
||||
actual mapper on a subclass, and not the superclass mapper.
|
||||
|
||||
This only works for non-aliased mappers.
|
||||
"""
|
||||
paths = []
|
||||
class MyOption(interfaces.MapperOption):
|
||||
propagate_to_loaders = True
|
||||
def process_query_conditionally(self, query):
|
||||
paths.append(query._current_path.path)
|
||||
|
||||
sess = create_session()
|
||||
names = ['dilbert', 'pointy haired boss']
|
||||
dilbert, boss = (
|
||||
sess.query(Person)
|
||||
.options(MyOption())
|
||||
.filter(Person.name.in_(names))
|
||||
.order_by(Person.name).all())
|
||||
|
||||
dilbert.machines
|
||||
boss.paperwork
|
||||
|
||||
eq_(paths,
|
||||
[(class_mapper(Engineer), 'machines'),
|
||||
(class_mapper(Boss), 'paperwork')])
|
||||
|
||||
def test_subclass_option_pathing(self):
|
||||
from sqlalchemy.orm import defer
|
||||
sess = create_session()
|
||||
names = ['dilbert', 'pointy haired boss']
|
||||
dilbert = sess.query(Person).\
|
||||
options(defer(Engineer.machines, Machine.name)).\
|
||||
filter(Person.name == 'dilbert').first()
|
||||
@@ -963,7 +932,7 @@ class _PolymorphicTestBase(object):
|
||||
.filter(palias.name.in_(['dilbert', 'wally'])).all(),
|
||||
[e1, e2])
|
||||
|
||||
def test_self_referential(self):
|
||||
def test_self_referential_one(self):
|
||||
sess = create_session()
|
||||
palias = aliased(Person)
|
||||
expected = [(m1, e1), (m1, e2), (m1, b1)]
|
||||
@@ -975,6 +944,11 @@ class _PolymorphicTestBase(object):
|
||||
.order_by(Person.person_id, palias.person_id).all(),
|
||||
expected)
|
||||
|
||||
def test_self_referential_two(self):
|
||||
sess = create_session()
|
||||
palias = aliased(Person)
|
||||
expected = [(m1, e1), (m1, e2), (m1, b1)]
|
||||
|
||||
eq_(sess.query(Person, palias)
|
||||
.filter(Person.company_id == palias.company_id)
|
||||
.filter(Person.name == 'dogbert')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from sqlalchemy.orm import create_session, relationship, mapper, \
|
||||
contains_eager, joinedload, subqueryload, subqueryload_all,\
|
||||
Session, aliased
|
||||
Session, aliased, with_polymorphic
|
||||
|
||||
from sqlalchemy import Integer, String, ForeignKey
|
||||
from sqlalchemy.engine import default
|
||||
@@ -717,15 +717,29 @@ class EagerToSubclassTest(fixtures.MappedTest):
|
||||
|
||||
def test_subq_through_related(self):
|
||||
Parent = self.classes.Parent
|
||||
Sub = self.classes.Sub
|
||||
Base = self.classes.Base
|
||||
sess = Session()
|
||||
|
||||
def go():
|
||||
eq_(sess.query(Parent)
|
||||
.options(subqueryload_all(Parent.children, Sub.related))
|
||||
.options(subqueryload_all(Parent.children, Base.related))
|
||||
.order_by(Parent.data).all(),
|
||||
[p1, p2])
|
||||
self.assert_sql_count(testing.db, go, 3)
|
||||
|
||||
def test_subq_through_related_aliased(self):
|
||||
Parent = self.classes.Parent
|
||||
Base = self.classes.Base
|
||||
pa = aliased(Parent)
|
||||
sess = Session()
|
||||
|
||||
def go():
|
||||
eq_(sess.query(pa)
|
||||
.options(subqueryload_all(pa.children, Base.related))
|
||||
.order_by(pa.data).all(),
|
||||
[p1, p2])
|
||||
self.assert_sql_count(testing.db, go, 3)
|
||||
|
||||
class SubClassEagerToSubClassTest(fixtures.MappedTest):
|
||||
"""Test joinedloads from subclass to subclass mappers"""
|
||||
|
||||
@@ -876,3 +890,226 @@ class SubClassEagerToSubClassTest(fixtures.MappedTest):
|
||||
[p1, p2])
|
||||
self.assert_sql_count(testing.db, go, 2)
|
||||
|
||||
class SameNamedPropTwoPolymorphicSubClassesTest(fixtures.MappedTest):
|
||||
"""test pathing when two subclasses contain a different property
|
||||
for the same name, and polymorphic loading is used.
|
||||
|
||||
#2614
|
||||
|
||||
"""
|
||||
run_setup_classes = 'once'
|
||||
run_setup_mappers = 'once'
|
||||
run_inserts = 'once'
|
||||
run_deletes = None
|
||||
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
Table('a', metadata,
|
||||
Column('id', Integer, primary_key=True,
|
||||
test_needs_autoincrement=True),
|
||||
Column('type', String(10))
|
||||
)
|
||||
Table('b', metadata,
|
||||
Column('id', Integer, ForeignKey('a.id'), primary_key=True)
|
||||
)
|
||||
Table('btod', metadata,
|
||||
Column('bid', Integer, ForeignKey('b.id'), nullable=False),
|
||||
Column('did', Integer, ForeignKey('d.id'), nullable=False)
|
||||
)
|
||||
Table('c', metadata,
|
||||
Column('id', Integer, ForeignKey('a.id'), primary_key=True)
|
||||
)
|
||||
Table('ctod', metadata,
|
||||
Column('cid', Integer, ForeignKey('c.id'), nullable=False),
|
||||
Column('did', Integer, ForeignKey('d.id'), nullable=False)
|
||||
)
|
||||
Table('d', metadata,
|
||||
Column('id', Integer, primary_key=True,
|
||||
test_needs_autoincrement=True)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def setup_classes(cls):
|
||||
class A(cls.Comparable):
|
||||
pass
|
||||
class B(A):
|
||||
pass
|
||||
class C(A):
|
||||
pass
|
||||
class D(cls.Comparable):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def setup_mappers(cls):
|
||||
A = cls.classes.A
|
||||
B = cls.classes.B
|
||||
C = cls.classes.C
|
||||
D = cls.classes.D
|
||||
|
||||
mapper(A, cls.tables.a, polymorphic_on=cls.tables.a.c.type)
|
||||
mapper(B, cls.tables.b, inherits=A, polymorphic_identity='b',
|
||||
properties={
|
||||
'related': relationship(D, secondary=cls.tables.btod)
|
||||
})
|
||||
mapper(C, cls.tables.c, inherits=A, polymorphic_identity='c',
|
||||
properties={
|
||||
'related': relationship(D, secondary=cls.tables.ctod)
|
||||
})
|
||||
mapper(D, cls.tables.d)
|
||||
|
||||
|
||||
@classmethod
|
||||
def insert_data(cls):
|
||||
B = cls.classes.B
|
||||
C = cls.classes.C
|
||||
D = cls.classes.D
|
||||
|
||||
session = Session()
|
||||
|
||||
d = D()
|
||||
session.add_all([
|
||||
B(related=[d]),
|
||||
C(related=[d])
|
||||
])
|
||||
session.commit()
|
||||
|
||||
def test_free_w_poly_subquery(self):
|
||||
A = self.classes.A
|
||||
B = self.classes.B
|
||||
C = self.classes.C
|
||||
D = self.classes.D
|
||||
|
||||
session = Session()
|
||||
d = session.query(D).one()
|
||||
a_poly = with_polymorphic(A, [B, C])
|
||||
def go():
|
||||
for a in session.query(a_poly).\
|
||||
options(
|
||||
subqueryload(a_poly.B.related),
|
||||
subqueryload(a_poly.C.related)):
|
||||
eq_(a.related, [d])
|
||||
self.assert_sql_count(testing.db, go, 3)
|
||||
|
||||
def test_fixed_w_poly_subquery(self):
|
||||
A = self.classes.A
|
||||
B = self.classes.B
|
||||
C = self.classes.C
|
||||
D = self.classes.D
|
||||
|
||||
session = Session()
|
||||
d = session.query(D).one()
|
||||
def go():
|
||||
for a in session.query(A).with_polymorphic([B, C]).\
|
||||
options(subqueryload(B.related), subqueryload(C.related)):
|
||||
eq_(a.related, [d])
|
||||
self.assert_sql_count(testing.db, go, 3)
|
||||
|
||||
def test_free_w_poly_joined(self):
|
||||
A = self.classes.A
|
||||
B = self.classes.B
|
||||
C = self.classes.C
|
||||
D = self.classes.D
|
||||
|
||||
session = Session()
|
||||
d = session.query(D).one()
|
||||
a_poly = with_polymorphic(A, [B, C])
|
||||
def go():
|
||||
for a in session.query(a_poly).\
|
||||
options(
|
||||
joinedload(a_poly.B.related),
|
||||
joinedload(a_poly.C.related)):
|
||||
eq_(a.related, [d])
|
||||
self.assert_sql_count(testing.db, go, 1)
|
||||
|
||||
def test_fixed_w_poly_joined(self):
|
||||
A = self.classes.A
|
||||
B = self.classes.B
|
||||
C = self.classes.C
|
||||
D = self.classes.D
|
||||
|
||||
session = Session()
|
||||
d = session.query(D).one()
|
||||
def go():
|
||||
for a in session.query(A).with_polymorphic([B, C]).\
|
||||
options(joinedload(B.related), joinedload(C.related)):
|
||||
eq_(a.related, [d])
|
||||
self.assert_sql_count(testing.db, go, 1)
|
||||
|
||||
|
||||
class SubClassToSubClassFromParentTest(fixtures.MappedTest):
|
||||
"""test #2617
|
||||
|
||||
"""
|
||||
run_setup_classes = 'once'
|
||||
run_setup_mappers = 'once'
|
||||
run_inserts = 'once'
|
||||
run_deletes = None
|
||||
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
Table('z', metadata,
|
||||
Column('id', Integer, primary_key=True,
|
||||
test_needs_autoincrement=True)
|
||||
)
|
||||
Table('a', metadata,
|
||||
Column('id', Integer, primary_key=True,
|
||||
test_needs_autoincrement=True),
|
||||
Column('type', String(10)),
|
||||
Column('z_id', Integer, ForeignKey('z.id'))
|
||||
)
|
||||
Table('b', metadata,
|
||||
Column('id', Integer, ForeignKey('a.id'), primary_key=True)
|
||||
)
|
||||
Table('d', metadata,
|
||||
Column('id', Integer, ForeignKey('a.id'), primary_key=True),
|
||||
Column('b_id', Integer, ForeignKey('b.id'))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def setup_classes(cls):
|
||||
class Z(cls.Comparable):
|
||||
pass
|
||||
class A(cls.Comparable):
|
||||
pass
|
||||
class B(A):
|
||||
pass
|
||||
class D(A):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def setup_mappers(cls):
|
||||
Z = cls.classes.Z
|
||||
A = cls.classes.A
|
||||
B = cls.classes.B
|
||||
D = cls.classes.D
|
||||
|
||||
mapper(Z, cls.tables.z)
|
||||
mapper(A, cls.tables.a, polymorphic_on=cls.tables.a.c.type,
|
||||
with_polymorphic='*',
|
||||
properties={
|
||||
'zs': relationship(Z, lazy="subquery")
|
||||
})
|
||||
mapper(B, cls.tables.b, inherits=A, polymorphic_identity='b',
|
||||
properties={
|
||||
'related': relationship(D, lazy="subquery",
|
||||
primaryjoin=cls.tables.d.c.b_id ==
|
||||
cls.tables.b.c.id)
|
||||
})
|
||||
mapper(D, cls.tables.d, inherits=A, polymorphic_identity='d')
|
||||
|
||||
|
||||
@classmethod
|
||||
def insert_data(cls):
|
||||
B = cls.classes.B
|
||||
|
||||
session = Session()
|
||||
session.add(B())
|
||||
session.commit()
|
||||
|
||||
def test_2617(self):
|
||||
A = self.classes.A
|
||||
session = Session()
|
||||
def go():
|
||||
a1 = session.query(A).first()
|
||||
eq_(a1.related, [])
|
||||
self.assert_sql_count(testing.db, go, 3)
|
||||
|
||||
@@ -278,7 +278,8 @@ class RelationshipFromSingleTest(testing.AssertsCompiledSQL, fixtures.MappedTest
|
||||
|
||||
sess = create_session()
|
||||
context = sess.query(Manager).options(subqueryload('stuff'))._compile_context()
|
||||
subq = context.attributes[('subquery', (class_mapper(Employee), 'stuff'))]
|
||||
subq = context.attributes[('subquery',
|
||||
(class_mapper(Manager), class_mapper(Manager).attrs.stuff))]
|
||||
|
||||
self.assert_compile(subq,
|
||||
'SELECT employee_stuff.id AS '
|
||||
|
||||
@@ -311,6 +311,8 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
|
||||
})
|
||||
mapper(Keyword, keywords)
|
||||
|
||||
|
||||
|
||||
for opt, count in [
|
||||
((
|
||||
joinedload(User.orders, Order.items),
|
||||
@@ -2662,128 +2664,3 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
|
||||
assert len(list(session)) == 3
|
||||
|
||||
|
||||
|
||||
class WarnFor2614TestBase(object):
|
||||
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
Table('a', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('type', String(50)),
|
||||
)
|
||||
Table('b', metadata,
|
||||
Column('id', Integer, ForeignKey('a.id'), primary_key=True),
|
||||
)
|
||||
Table('c', metadata,
|
||||
Column('id', Integer, ForeignKey('a.id'), primary_key=True),
|
||||
)
|
||||
Table('d', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('bid', Integer, ForeignKey('b.id')),
|
||||
Column('cid', Integer, ForeignKey('c.id')),
|
||||
)
|
||||
|
||||
def _mapping(self, lazy_b=True, lazy_c=True):
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
class B(A):
|
||||
pass
|
||||
|
||||
class C(A):
|
||||
pass
|
||||
|
||||
class D(object):
|
||||
pass
|
||||
|
||||
mapper(A, self.tables.a, polymorphic_on=self.tables.a.c.type)
|
||||
mapper(B, self.tables.b, inherits=A, polymorphic_identity='b',
|
||||
properties={
|
||||
'ds': relationship(D, lazy=lazy_b)
|
||||
})
|
||||
mapper(C, self.tables.c, inherits=A, polymorphic_identity='c',
|
||||
properties={
|
||||
'ds': relationship(D, lazy=lazy_c)
|
||||
})
|
||||
mapper(D, self.tables.d)
|
||||
|
||||
|
||||
return A, B, C, D
|
||||
|
||||
def _assert_raises(self, fn):
|
||||
assert_raises_message(
|
||||
sa.exc.InvalidRequestError,
|
||||
"Eager loading cannot currently function correctly when two or more "
|
||||
r"same\-named attributes associated with multiple polymorphic classes "
|
||||
"of the same base are present. Encountered more than one "
|
||||
r"eager path for attribute 'ds' on mapper 'Mapper\|A\|a'.",
|
||||
fn
|
||||
)
|
||||
|
||||
def test_poly_both_eager(self):
|
||||
A, B, C, D = self._mapping(lazy_b=self.eager_name,
|
||||
lazy_c=self.eager_name)
|
||||
|
||||
session = Session()
|
||||
self._assert_raises(
|
||||
session.query(A).with_polymorphic('*').all
|
||||
)
|
||||
|
||||
def test_poly_one_eager(self):
|
||||
A, B, C, D = self._mapping(lazy_b=self.eager_name, lazy_c=True)
|
||||
|
||||
session = Session()
|
||||
session.query(A).with_polymorphic('*').all()
|
||||
|
||||
def test_poly_both_option(self):
|
||||
A, B, C, D = self._mapping()
|
||||
|
||||
session = Session()
|
||||
self._assert_raises(
|
||||
session.query(A).with_polymorphic('*').options(
|
||||
self.eager_option(B.ds), self.eager_option(C.ds)).all
|
||||
)
|
||||
|
||||
def test_poly_one_option_bs(self):
|
||||
A, B, C, D = self._mapping()
|
||||
|
||||
session = Session()
|
||||
|
||||
# sucks, can't even do eager() on just one of them, as B.ds
|
||||
# hits for both
|
||||
self._assert_raises(
|
||||
session.query(A).with_polymorphic('*').\
|
||||
options(self.eager_option(B.ds)).all
|
||||
)
|
||||
|
||||
def test_poly_one_option_cs(self):
|
||||
A, B, C, D = self._mapping()
|
||||
|
||||
session = Session()
|
||||
|
||||
# sucks, can't even do eager() on just one of them, as B.ds
|
||||
# hits for both
|
||||
self._assert_raises(
|
||||
session.query(A).with_polymorphic('*').\
|
||||
options(self.eager_option(C.ds)).all
|
||||
)
|
||||
|
||||
def test_single_poly_one_option_bs(self):
|
||||
A, B, C, D = self._mapping()
|
||||
|
||||
session = Session()
|
||||
|
||||
session.query(A).with_polymorphic(B).\
|
||||
options(self.eager_option(B.ds)).all()
|
||||
|
||||
def test_lazy_True(self):
|
||||
A, B, C, D = self._mapping()
|
||||
|
||||
session = Session()
|
||||
session.query(A).with_polymorphic('*').all()
|
||||
|
||||
class WarnFor2614Test(WarnFor2614TestBase, fixtures.MappedTest):
|
||||
eager_name = "joined"
|
||||
|
||||
def eager_option(self, arg):
|
||||
return joinedload(arg)
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
def test_with_polymorphic(self):
|
||||
User = self.classes.User
|
||||
insp = inspect(User)
|
||||
eq_(insp.with_polymorphic_mappers, [insp])
|
||||
eq_(insp.with_polymorphic_mappers, [])
|
||||
|
||||
def test_col_property(self):
|
||||
User = self.classes.User
|
||||
@@ -198,7 +198,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
is_(prop._parentmapper, class_mapper(User))
|
||||
is_(prop.mapper, class_mapper(Address))
|
||||
|
||||
is_(prop._parententity, ua)
|
||||
is_(prop._parententity, inspect(ua))
|
||||
|
||||
def test_insp_column_prop(self):
|
||||
User = self.classes.User
|
||||
@@ -222,7 +222,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
|
||||
assert not hasattr(prop, "mapper")
|
||||
|
||||
is_(prop._parententity, ua)
|
||||
is_(prop._parententity, inspect(ua))
|
||||
|
||||
def test_rel_accessors(self):
|
||||
User = self.classes.User
|
||||
|
||||
+29
-1
@@ -1922,7 +1922,7 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
|
||||
)
|
||||
|
||||
|
||||
def test_multiple_explicit_entities(self):
|
||||
def test_multiple_explicit_entities_one(self):
|
||||
Node = self.classes.Node
|
||||
|
||||
sess = create_session()
|
||||
@@ -1938,6 +1938,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
|
||||
(Node(data='n122'), Node(data='n12'), Node(data='n1'))
|
||||
)
|
||||
|
||||
def test_multiple_explicit_entities_two(self):
|
||||
Node = self.classes.Node
|
||||
|
||||
sess = create_session()
|
||||
|
||||
parent = aliased(Node)
|
||||
grandparent = aliased(Node)
|
||||
eq_(
|
||||
sess.query(Node, parent, grandparent).\
|
||||
join(parent, Node.parent).\
|
||||
@@ -1947,6 +1954,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
|
||||
(Node(data='n122'), Node(data='n12'), Node(data='n1'))
|
||||
)
|
||||
|
||||
def test_multiple_explicit_entities_three(self):
|
||||
Node = self.classes.Node
|
||||
|
||||
sess = create_session()
|
||||
|
||||
parent = aliased(Node)
|
||||
grandparent = aliased(Node)
|
||||
# same, change order around
|
||||
eq_(
|
||||
sess.query(parent, grandparent, Node).\
|
||||
@@ -1957,6 +1971,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
|
||||
(Node(data='n12'), Node(data='n1'), Node(data='n122'))
|
||||
)
|
||||
|
||||
def test_multiple_explicit_entities_four(self):
|
||||
Node = self.classes.Node
|
||||
|
||||
sess = create_session()
|
||||
|
||||
parent = aliased(Node)
|
||||
grandparent = aliased(Node)
|
||||
eq_(
|
||||
sess.query(Node, parent, grandparent).\
|
||||
join(parent, Node.parent).\
|
||||
@@ -1967,6 +1988,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
|
||||
(Node(data='n122'), Node(data='n12'), Node(data='n1'))
|
||||
)
|
||||
|
||||
def test_multiple_explicit_entities_five(self):
|
||||
Node = self.classes.Node
|
||||
|
||||
sess = create_session()
|
||||
|
||||
parent = aliased(Node)
|
||||
grandparent = aliased(Node)
|
||||
eq_(
|
||||
sess.query(Node, parent, grandparent).\
|
||||
join(parent, Node.parent).\
|
||||
|
||||
@@ -3278,4 +3278,3 @@ class MagicNamesTest(fixtures.MappedTest):
|
||||
reserved: maps.c.state})
|
||||
|
||||
|
||||
|
||||
|
||||
+28
-17
@@ -2,6 +2,7 @@ import operator
|
||||
from sqlalchemy import MetaData, null, exists, text, union, literal, \
|
||||
literal_column, func, between, Unicode, desc, and_, bindparam, \
|
||||
select, distinct, or_, collate
|
||||
from sqlalchemy import inspect
|
||||
from sqlalchemy import exc as sa_exc, util
|
||||
from sqlalchemy.sql import compiler, table, column
|
||||
from sqlalchemy.sql import expression
|
||||
@@ -2309,6 +2310,9 @@ class OptionsTest(QueryTest):
|
||||
if i % 2 == 0:
|
||||
if isinstance(item, type):
|
||||
item = class_mapper(item)
|
||||
else:
|
||||
if isinstance(item, basestring):
|
||||
item = inspect(r[-1]).mapper.attrs[item]
|
||||
r.append(item)
|
||||
return tuple(r)
|
||||
|
||||
@@ -2351,7 +2355,8 @@ class OptionsTest(QueryTest):
|
||||
q = sess.query(User)
|
||||
opt = self._option_fixture('email_address', 'id')
|
||||
q = sess.query(Address)._with_current_path(
|
||||
orm_util.PathRegistry.coerce([class_mapper(User), 'addresses'])
|
||||
orm_util.PathRegistry.coerce([inspect(User),
|
||||
inspect(User).attrs.addresses])
|
||||
)
|
||||
self._assert_path_result(opt, q, [])
|
||||
|
||||
@@ -2462,13 +2467,13 @@ class OptionsTest(QueryTest):
|
||||
class SubAddr(Address):
|
||||
pass
|
||||
mapper(SubAddr, inherits=Address, properties={
|
||||
'flub':relationship(Dingaling)
|
||||
'flub': relationship(Dingaling)
|
||||
})
|
||||
|
||||
q = sess.query(Address)
|
||||
opt = self._option_fixture(SubAddr.flub)
|
||||
|
||||
self._assert_path_result(opt, q, [(Address, 'flub')])
|
||||
self._assert_path_result(opt, q, [(SubAddr, 'flub')])
|
||||
|
||||
def test_from_subclass_to_subclass_attr(self):
|
||||
Dingaling, Address = self.classes.Dingaling, self.classes.Address
|
||||
@@ -2477,7 +2482,7 @@ class OptionsTest(QueryTest):
|
||||
class SubAddr(Address):
|
||||
pass
|
||||
mapper(SubAddr, inherits=Address, properties={
|
||||
'flub':relationship(Dingaling)
|
||||
'flub': relationship(Dingaling)
|
||||
})
|
||||
|
||||
q = sess.query(SubAddr)
|
||||
@@ -2492,13 +2497,14 @@ class OptionsTest(QueryTest):
|
||||
class SubAddr(Address):
|
||||
pass
|
||||
mapper(SubAddr, inherits=Address, properties={
|
||||
'flub':relationship(Dingaling)
|
||||
'flub': relationship(Dingaling)
|
||||
})
|
||||
|
||||
q = sess.query(Address)
|
||||
opt = self._option_fixture(SubAddr.user)
|
||||
|
||||
self._assert_path_result(opt, q, [(Address, 'user')])
|
||||
self._assert_path_result(opt, q,
|
||||
[(Address, inspect(Address).attrs.user)])
|
||||
|
||||
def test_of_type(self):
|
||||
User, Address = self.classes.User, self.classes.Address
|
||||
@@ -2511,9 +2517,11 @@ class OptionsTest(QueryTest):
|
||||
q = sess.query(User)
|
||||
opt = self._option_fixture(User.addresses.of_type(SubAddr), SubAddr.user)
|
||||
|
||||
u_mapper = inspect(User)
|
||||
a_mapper = inspect(Address)
|
||||
self._assert_path_result(opt, q, [
|
||||
(User, 'addresses'),
|
||||
(User, 'addresses', SubAddr, 'user')
|
||||
(u_mapper, u_mapper.attrs.addresses),
|
||||
(u_mapper, u_mapper.attrs.addresses, a_mapper, a_mapper.attrs.user)
|
||||
])
|
||||
|
||||
def test_of_type_plus_level(self):
|
||||
@@ -2525,15 +2533,17 @@ class OptionsTest(QueryTest):
|
||||
class SubAddr(Address):
|
||||
pass
|
||||
mapper(SubAddr, inherits=Address, properties={
|
||||
'flub':relationship(Dingaling)
|
||||
'flub': relationship(Dingaling)
|
||||
})
|
||||
|
||||
q = sess.query(User)
|
||||
opt = self._option_fixture(User.addresses.of_type(SubAddr), SubAddr.flub)
|
||||
|
||||
u_mapper = inspect(User)
|
||||
sa_mapper = inspect(SubAddr)
|
||||
self._assert_path_result(opt, q, [
|
||||
(User, 'addresses'),
|
||||
(User, 'addresses', SubAddr, 'flub')
|
||||
(u_mapper, u_mapper.attrs.addresses),
|
||||
(u_mapper, u_mapper.attrs.addresses, sa_mapper, sa_mapper.attrs.flub)
|
||||
])
|
||||
|
||||
def test_aliased_single(self):
|
||||
@@ -2543,7 +2553,7 @@ class OptionsTest(QueryTest):
|
||||
ualias = aliased(User)
|
||||
q = sess.query(ualias)
|
||||
opt = self._option_fixture(ualias.addresses)
|
||||
self._assert_path_result(opt, q, [(ualias, 'addresses')])
|
||||
self._assert_path_result(opt, q, [(inspect(ualias), 'addresses')])
|
||||
|
||||
def test_with_current_aliased_single(self):
|
||||
User, Address = self.classes.User, self.classes.Address
|
||||
@@ -2554,7 +2564,7 @@ class OptionsTest(QueryTest):
|
||||
self._make_path_registry([Address, 'user'])
|
||||
)
|
||||
opt = self._option_fixture(Address.user, ualias.addresses)
|
||||
self._assert_path_result(opt, q, [(ualias, 'addresses')])
|
||||
self._assert_path_result(opt, q, [(inspect(ualias), 'addresses')])
|
||||
|
||||
def test_with_current_aliased_single_nonmatching_option(self):
|
||||
User, Address = self.classes.User, self.classes.Address
|
||||
@@ -2828,8 +2838,8 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
|
||||
cls.tables.addresses, cls.classes.Address,
|
||||
cls.tables.orders, cls.classes.Order)
|
||||
mapper(User, users, properties={
|
||||
'addresses':relationship(Address),
|
||||
'orders':relationship(Order)
|
||||
'addresses': relationship(Address),
|
||||
'orders': relationship(Order)
|
||||
})
|
||||
mapper(Address, addresses)
|
||||
mapper(Order, orders)
|
||||
@@ -2839,7 +2849,7 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
|
||||
cls.classes.Keyword,
|
||||
cls.classes.Item)
|
||||
mapper(Keyword, keywords, properties={
|
||||
"keywords":column_property(keywords.c.name + "some keyword")
|
||||
"keywords": column_property(keywords.c.name + "some keyword")
|
||||
})
|
||||
mapper(Item, items,
|
||||
properties=dict(keywords=relationship(Keyword,
|
||||
@@ -2850,7 +2860,7 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
|
||||
|
||||
q = create_session().query(*entity_list).\
|
||||
options(joinedload(option))
|
||||
key = ('loaderstrategy', (class_mapper(Item), 'keywords'))
|
||||
key = ('loaderstrategy', (inspect(Item), inspect(Item).attrs.keywords))
|
||||
assert key in q._attributes
|
||||
|
||||
def _assert_eager_with_entity_exception(self, entity_list, options,
|
||||
@@ -2865,3 +2875,4 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
|
||||
assert_raises_message(sa.exc.ArgumentError, message,
|
||||
create_session().query(column).options,
|
||||
joinedload(eager_option))
|
||||
|
||||
|
||||
@@ -1226,24 +1226,24 @@ class InheritanceToRelatedTest(fixtures.MappedTest):
|
||||
@classmethod
|
||||
def fixtures(cls):
|
||||
return dict(
|
||||
foo = [
|
||||
foo=[
|
||||
('id', 'type', 'related_id'),
|
||||
(1, 'bar', 1),
|
||||
(2, 'bar', 2),
|
||||
(3, 'baz', 1),
|
||||
(4, 'baz', 2),
|
||||
],
|
||||
bar = [
|
||||
bar=[
|
||||
('id', ),
|
||||
(1,),
|
||||
(2,)
|
||||
],
|
||||
baz = [
|
||||
baz=[
|
||||
('id', ),
|
||||
(3,),
|
||||
(4,)
|
||||
],
|
||||
related = [
|
||||
related=[
|
||||
('id', ),
|
||||
(1,),
|
||||
(2,)
|
||||
@@ -1252,7 +1252,7 @@ class InheritanceToRelatedTest(fixtures.MappedTest):
|
||||
@classmethod
|
||||
def setup_mappers(cls):
|
||||
mapper(cls.classes.Foo, cls.tables.foo, properties={
|
||||
'related':relationship(cls.classes.Related)
|
||||
'related': relationship(cls.classes.Related)
|
||||
}, polymorphic_on=cls.tables.foo.c.type)
|
||||
mapper(cls.classes.Bar, cls.tables.bar, polymorphic_identity='bar',
|
||||
inherits=cls.classes.Foo)
|
||||
@@ -1260,22 +1260,43 @@ class InheritanceToRelatedTest(fixtures.MappedTest):
|
||||
inherits=cls.classes.Foo)
|
||||
mapper(cls.classes.Related, cls.tables.related)
|
||||
|
||||
def test_caches_query_per_base(self):
|
||||
def test_caches_query_per_base_subq(self):
|
||||
Foo, Bar, Baz, Related = self.classes.Foo, self.classes.Bar, \
|
||||
self.classes.Baz, self.classes.Related
|
||||
s = Session(testing.db)
|
||||
def go():
|
||||
eq_(
|
||||
s.query(Foo).with_polymorphic([Bar, Baz]).order_by(Foo.id).options(subqueryload(Foo.related)).all(),
|
||||
s.query(Foo).with_polymorphic([Bar, Baz]).\
|
||||
order_by(Foo.id).\
|
||||
options(subqueryload(Foo.related)).all(),
|
||||
[
|
||||
Bar(id=1,related=Related(id=1)),
|
||||
Bar(id=2,related=Related(id=2)),
|
||||
Baz(id=3,related=Related(id=1)),
|
||||
Baz(id=4,related=Related(id=2))
|
||||
Bar(id=1, related=Related(id=1)),
|
||||
Bar(id=2, related=Related(id=2)),
|
||||
Baz(id=3, related=Related(id=1)),
|
||||
Baz(id=4, related=Related(id=2))
|
||||
]
|
||||
)
|
||||
self.assert_sql_count(testing.db, go, 2)
|
||||
|
||||
def test_caches_query_per_base_joined(self):
|
||||
# technically this should be in test_eager_relations
|
||||
Foo, Bar, Baz, Related = self.classes.Foo, self.classes.Bar, \
|
||||
self.classes.Baz, self.classes.Related
|
||||
s = Session(testing.db)
|
||||
def go():
|
||||
eq_(
|
||||
s.query(Foo).with_polymorphic([Bar, Baz]).\
|
||||
order_by(Foo.id).\
|
||||
options(joinedload(Foo.related)).all(),
|
||||
[
|
||||
Bar(id=1, related=Related(id=1)),
|
||||
Bar(id=2, related=Related(id=2)),
|
||||
Baz(id=3, related=Related(id=1)),
|
||||
Baz(id=4, related=Related(id=2))
|
||||
]
|
||||
)
|
||||
self.assert_sql_count(testing.db, go, 1)
|
||||
|
||||
class CyclicalInheritingEagerTestOne(fixtures.MappedTest):
|
||||
|
||||
@classmethod
|
||||
@@ -1344,14 +1365,13 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
|
||||
|
||||
def test_from_subclass(self):
|
||||
Director = self.classes.Director
|
||||
PersistentObject = self.classes.PersistentObject
|
||||
|
||||
|
||||
s = create_session()
|
||||
|
||||
ctx = s.query(Director).options(subqueryload('*'))._compile_context()
|
||||
|
||||
q = ctx.attributes[('subquery', (inspect(PersistentObject), 'movies'))]
|
||||
q = ctx.attributes[('subquery',
|
||||
(inspect(Director), inspect(Director).attrs.movies))]
|
||||
self.assert_compile(q,
|
||||
"SELECT anon_1.movie_id AS anon_1_movie_id, "
|
||||
"anon_1.persistent_id AS anon_1_persistent_id, "
|
||||
@@ -1384,10 +1404,3 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
|
||||
d = session.query(Director).options(subqueryload('*')).first()
|
||||
assert len(list(session)) == 3
|
||||
|
||||
from . import test_eager_relations
|
||||
|
||||
class WarnFor2614Test(test_eager_relations.WarnFor2614TestBase, fixtures.MappedTest):
|
||||
eager_name = "subquery"
|
||||
|
||||
def eager_option(self, arg):
|
||||
return subqueryload(arg)
|
||||
|
||||
+196
-58
@@ -4,7 +4,7 @@ from sqlalchemy import Column
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy.orm import aliased
|
||||
from sqlalchemy.orm import aliased, with_polymorphic
|
||||
from sqlalchemy.orm import mapper, create_session
|
||||
from sqlalchemy.testing import fixtures
|
||||
from test.orm import _fixtures
|
||||
@@ -228,6 +228,7 @@ class IdentityKeyTest(_fixtures.FixtureTest):
|
||||
key = util.identity_key(User, row=row)
|
||||
eq_(key, (User, (1,)))
|
||||
|
||||
|
||||
class PathRegistryTest(_fixtures.FixtureTest):
|
||||
run_setup_mappers = 'once'
|
||||
run_inserts = None
|
||||
@@ -242,7 +243,7 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
is_(
|
||||
util.RootRegistry()[umapper],
|
||||
umapper._sa_path_registry
|
||||
umapper._path_registry
|
||||
)
|
||||
eq_(
|
||||
util.RootRegistry()[umapper],
|
||||
@@ -255,9 +256,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
path = PathRegistry.coerce((umapper,))
|
||||
|
||||
eq_(
|
||||
path['addresses'][amapper]['email_address'],
|
||||
PathRegistry.coerce((umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
path[umapper.attrs.addresses][amapper]
|
||||
[amapper.attrs.email_address],
|
||||
PathRegistry.coerce((umapper, umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
)
|
||||
|
||||
def test_entity_boolean(self):
|
||||
@@ -267,47 +269,48 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
|
||||
def test_key_boolean(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
path = PathRegistry.coerce((umapper, 'addresses'))
|
||||
path = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
is_(bool(path), True)
|
||||
|
||||
def test_aliased_class(self):
|
||||
User = self.classes.User
|
||||
ua = aliased(User)
|
||||
path = PathRegistry.coerce((ua, 'addresses'))
|
||||
ua_insp = inspect(ua)
|
||||
path = PathRegistry.coerce((ua_insp, ua_insp.mapper.attrs.addresses))
|
||||
assert path.parent.is_aliased_class
|
||||
|
||||
def test_indexed_entity(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
path = PathRegistry.coerce((umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
path = PathRegistry.coerce((umapper, umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
is_(path[0], umapper)
|
||||
is_(path[2], amapper)
|
||||
|
||||
def test_indexed_key(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
path = PathRegistry.coerce((umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
eq_(path[1], 'addresses')
|
||||
eq_(path[3], 'email_address')
|
||||
path = PathRegistry.coerce((umapper, umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
eq_(path[1], umapper.attrs.addresses)
|
||||
eq_(path[3], amapper.attrs.email_address)
|
||||
|
||||
def test_slice(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
path = PathRegistry.coerce((umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
eq_(path[1:3], ('addresses', amapper))
|
||||
path = PathRegistry.coerce((umapper, umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
eq_(path[1:3], (umapper.attrs.addresses, amapper))
|
||||
|
||||
def test_addition(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((amapper, 'email_address'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
|
||||
eq_(
|
||||
p1 + p2,
|
||||
PathRegistry.coerce((umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
PathRegistry.coerce((umapper, umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
)
|
||||
|
||||
def test_length(self):
|
||||
@@ -315,10 +318,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
amapper = inspect(self.classes.Address)
|
||||
pneg1 = PathRegistry.coerce(())
|
||||
p0 = PathRegistry.coerce((umapper,))
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
|
||||
eq_(len(pneg1), 0)
|
||||
eq_(len(p0), 1)
|
||||
@@ -334,14 +337,17 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
def test_eq(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p3 = PathRegistry.coerce((umapper, 'other'))
|
||||
p4 = PathRegistry.coerce((amapper, 'addresses'))
|
||||
p5 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p6 = PathRegistry.coerce((amapper, 'user', umapper, 'addresses'))
|
||||
p7 = PathRegistry.coerce((amapper, 'user', umapper, 'addresses',
|
||||
amapper, 'email_address'))
|
||||
u_alias = inspect(aliased(self.classes.User))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p3 = PathRegistry.coerce((umapper, umapper.attrs.name))
|
||||
p4 = PathRegistry.coerce((u_alias, umapper.attrs.addresses))
|
||||
p5 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p6 = PathRegistry.coerce((amapper, amapper.attrs.user, umapper,
|
||||
umapper.attrs.addresses))
|
||||
p7 = PathRegistry.coerce((amapper, amapper.attrs.user, umapper,
|
||||
umapper.attrs.addresses,
|
||||
amapper, amapper.attrs.email_address))
|
||||
|
||||
is_(p1 == p2, True)
|
||||
is_(p1 == p3, False)
|
||||
@@ -358,7 +364,7 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
def test_contains_mapper(self):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
assert p1.contains_mapper(umapper)
|
||||
assert not p1.contains_mapper(amapper)
|
||||
|
||||
@@ -373,18 +379,18 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((amapper, 'email_address'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
|
||||
|
||||
eq_(
|
||||
p1.path, (umapper, 'addresses')
|
||||
p1.path, (umapper, umapper.attrs.addresses)
|
||||
)
|
||||
eq_(
|
||||
p2.path, (umapper, 'addresses', amapper)
|
||||
p2.path, (umapper, umapper.attrs.addresses, amapper)
|
||||
)
|
||||
eq_(
|
||||
p3.path, (amapper, 'email_address')
|
||||
p3.path, (amapper, amapper.attrs.email_address)
|
||||
)
|
||||
|
||||
def test_registry_set(self):
|
||||
@@ -392,9 +398,9 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((amapper, 'email_address'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
|
||||
|
||||
p1.set(reg, "p1key", "p1value")
|
||||
p2.set(reg, "p2key", "p2value")
|
||||
@@ -413,9 +419,9 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((amapper, 'email_address'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
|
||||
reg.update(
|
||||
{
|
||||
('p1key', p1.path): 'p1value',
|
||||
@@ -435,9 +441,9 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((amapper, 'email_address'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
|
||||
reg.update(
|
||||
{
|
||||
('p1key', p1.path): 'p1value',
|
||||
@@ -455,8 +461,8 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
reg.update(
|
||||
{
|
||||
('p1key', p1.path): 'p1value',
|
||||
@@ -481,10 +487,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
umapper = inspect(self.classes.User)
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses', amapper,
|
||||
'email_address'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper,
|
||||
amapper.attrs.email_address))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
eq_(
|
||||
p1.serialize(),
|
||||
[(User, "addresses"), (Address, "email_address")]
|
||||
@@ -505,10 +511,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
amapper = inspect(self.classes.Address)
|
||||
|
||||
|
||||
p1 = PathRegistry.coerce((umapper, 'addresses', amapper,
|
||||
'email_address'))
|
||||
p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
|
||||
p3 = PathRegistry.coerce((umapper, 'addresses'))
|
||||
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper,
|
||||
amapper.attrs.email_address))
|
||||
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
|
||||
p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
|
||||
|
||||
eq_(
|
||||
PathRegistry.deserialize([(User, "addresses"),
|
||||
@@ -523,3 +529,135 @@ class PathRegistryTest(_fixtures.FixtureTest):
|
||||
PathRegistry.deserialize([(User, "addresses")]),
|
||||
p3
|
||||
)
|
||||
|
||||
from .inheritance import _poly_fixtures
|
||||
class PathRegistryInhTest(_poly_fixtures._Polymorphic):
|
||||
run_setup_mappers = 'once'
|
||||
run_inserts = None
|
||||
run_deletes = None
|
||||
|
||||
def test_plain(self):
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
pmapper = inspect(Person)
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
p1 = PathRegistry.coerce((pmapper, emapper.attrs.machines))
|
||||
|
||||
# given a mapper and an attribute on a subclass,
|
||||
# the path converts what you get to be against that subclass
|
||||
eq_(
|
||||
p1.path,
|
||||
(emapper, emapper.attrs.machines)
|
||||
)
|
||||
|
||||
def test_plain_compound(self):
|
||||
Company = _poly_fixtures.Company
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
cmapper = inspect(Company)
|
||||
pmapper = inspect(Person)
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
p1 = PathRegistry.coerce((cmapper, cmapper.attrs.employees,
|
||||
pmapper, emapper.attrs.machines))
|
||||
|
||||
# given a mapper and an attribute on a subclass,
|
||||
# the path converts what you get to be against that subclass
|
||||
eq_(
|
||||
p1.path,
|
||||
(cmapper, cmapper.attrs.employees, emapper, emapper.attrs.machines)
|
||||
)
|
||||
|
||||
def test_plain_aliased(self):
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
p_alias = aliased(Person)
|
||||
p_alias = inspect(p_alias)
|
||||
|
||||
p1 = PathRegistry.coerce((p_alias, emapper.attrs.machines))
|
||||
# plain AliasedClass - the path keeps that AliasedClass directly
|
||||
# as is in the path
|
||||
eq_(
|
||||
p1.path,
|
||||
(p_alias, emapper.attrs.machines)
|
||||
)
|
||||
|
||||
def test_plain_aliased_compound(self):
|
||||
Company = _poly_fixtures.Company
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
cmapper = inspect(Company)
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
c_alias = aliased(Company)
|
||||
p_alias = aliased(Person)
|
||||
|
||||
c_alias = inspect(c_alias)
|
||||
p_alias = inspect(p_alias)
|
||||
|
||||
p1 = PathRegistry.coerce((c_alias, cmapper.attrs.employees,
|
||||
p_alias, emapper.attrs.machines))
|
||||
# plain AliasedClass - the path keeps that AliasedClass directly
|
||||
# as is in the path
|
||||
eq_(
|
||||
p1.path,
|
||||
(c_alias, cmapper.attrs.employees, p_alias, emapper.attrs.machines)
|
||||
)
|
||||
|
||||
def test_with_poly_sub(self):
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
p_poly = with_polymorphic(Person, [Engineer])
|
||||
e_poly = inspect(p_poly.Engineer)
|
||||
p_poly = inspect(p_poly)
|
||||
|
||||
p1 = PathRegistry.coerce((p_poly, emapper.attrs.machines))
|
||||
|
||||
# polymorphic AliasedClass - the path uses _entity_for_mapper()
|
||||
# to get the most specific sub-entity
|
||||
eq_(
|
||||
p1.path,
|
||||
(e_poly, emapper.attrs.machines)
|
||||
)
|
||||
|
||||
def test_with_poly_base(self):
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
pmapper = inspect(Person)
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
p_poly = with_polymorphic(Person, [Engineer])
|
||||
p_poly = inspect(p_poly)
|
||||
|
||||
# "name" is actually on Person, not Engineer
|
||||
p1 = PathRegistry.coerce((p_poly, emapper.attrs.name))
|
||||
|
||||
# polymorphic AliasedClass - because "name" is on Person,
|
||||
# we get Person, not Engineer
|
||||
eq_(
|
||||
p1.path,
|
||||
(p_poly, pmapper.attrs.name)
|
||||
)
|
||||
|
||||
def test_with_poly_use_mapper(self):
|
||||
Person = _poly_fixtures.Person
|
||||
Engineer = _poly_fixtures.Engineer
|
||||
emapper = inspect(Engineer)
|
||||
|
||||
p_poly = with_polymorphic(Person, [Engineer], _use_mapper_path=True)
|
||||
p_poly = inspect(p_poly)
|
||||
|
||||
p1 = PathRegistry.coerce((p_poly, emapper.attrs.machines))
|
||||
|
||||
# polymorphic AliasedClass with the "use_mapper_path" flag -
|
||||
# the AliasedClass acts just like the base mapper
|
||||
eq_(
|
||||
p1.path,
|
||||
(emapper, emapper.attrs.machines)
|
||||
)
|
||||
|
||||
|
||||
@@ -833,6 +833,58 @@ class ComparisonOperatorTest(fixtures.TestBase, testing.AssertsCompiledSQL):
|
||||
def test_comparison_operators_ge(self):
|
||||
self._test_comparison_op(operator.ge, '>=', '<=')
|
||||
|
||||
class NonZeroTest(fixtures.TestBase):
|
||||
def _raises(self, expr):
|
||||
assert_raises_message(
|
||||
TypeError,
|
||||
"Boolean value of this clause is not defined",
|
||||
bool, expr
|
||||
)
|
||||
|
||||
def _assert_true(self, expr):
|
||||
is_(bool(expr), True)
|
||||
|
||||
def _assert_false(self, expr):
|
||||
is_(bool(expr), False)
|
||||
|
||||
def test_column_identity_eq(self):
|
||||
c1 = column('c1')
|
||||
self._assert_true(c1 == c1)
|
||||
|
||||
def test_column_identity_gt(self):
|
||||
c1 = column('c1')
|
||||
self._raises(c1 > c1)
|
||||
|
||||
def test_column_compare_eq(self):
|
||||
c1, c2 = column('c1'), column('c2')
|
||||
self._assert_false(c1 == c2)
|
||||
|
||||
def test_column_compare_gt(self):
|
||||
c1, c2 = column('c1'), column('c2')
|
||||
self._raises(c1 > c2)
|
||||
|
||||
def test_binary_identity_eq(self):
|
||||
c1 = column('c1')
|
||||
expr = c1 > 5
|
||||
self._assert_true(expr == expr)
|
||||
|
||||
def test_labeled_binary_identity_eq(self):
|
||||
c1 = column('c1')
|
||||
expr = (c1 > 5).label(None)
|
||||
self._assert_true(expr == expr)
|
||||
|
||||
def test_annotated_binary_identity_eq(self):
|
||||
c1 = column('c1')
|
||||
expr1 = (c1 > 5)
|
||||
expr2 = expr1._annotate({"foo": "bar"})
|
||||
self._assert_true(expr1 == expr2)
|
||||
|
||||
def test_labeled_binary_compare_gt(self):
|
||||
c1 = column('c1')
|
||||
expr1 = (c1 > 5).label(None)
|
||||
expr2 = (c1 > 5).label(None)
|
||||
self._assert_false(expr1 == expr2)
|
||||
|
||||
class NegationTest(fixtures.TestBase, testing.AssertsCompiledSQL):
|
||||
__dialect__ = 'default'
|
||||
|
||||
|
||||
@@ -1287,7 +1287,9 @@ class AnnotationsTest(fixtures.TestBase):
|
||||
t.c.x,
|
||||
a,
|
||||
s,
|
||||
s2
|
||||
s2,
|
||||
t.c.x > 1,
|
||||
(t.c.x > 1).label(None)
|
||||
]:
|
||||
annot = obj._annotate({})
|
||||
eq_(set([obj]), set([annot]))
|
||||
|
||||
@@ -1306,6 +1306,23 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
|
||||
dialects.mysql.INTEGER(display_width=5), "INTEGER(5)",
|
||||
allow_dialect_select=True)
|
||||
|
||||
def test_numeric_plain(self):
|
||||
self.assert_compile(types.NUMERIC(), 'NUMERIC')
|
||||
|
||||
def test_numeric_precision(self):
|
||||
self.assert_compile(types.NUMERIC(2), 'NUMERIC(2)')
|
||||
|
||||
def test_numeric_scale(self):
|
||||
self.assert_compile(types.NUMERIC(2, 4), 'NUMERIC(2, 4)')
|
||||
|
||||
def test_decimal_plain(self):
|
||||
self.assert_compile(types.DECIMAL(), 'DECIMAL')
|
||||
|
||||
def test_decimal_precision(self):
|
||||
self.assert_compile(types.DECIMAL(2), 'DECIMAL(2)')
|
||||
|
||||
def test_decimal_scale(self):
|
||||
self.assert_compile(types.DECIMAL(2, 4), 'DECIMAL(2, 4)')
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user