summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-12-08 11:23:21 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2012-12-08 11:23:21 -0500
commit1ee4736beaadeb9053f8886503b64ee04fa4b557 (patch)
treee67e2b6347a11a5eef84ff11d76c142ba53b10ff /lib/sqlalchemy
parentd57c1c2ddd654a1077ab04ba7277828d9030c23d (diff)
parentf8caf05593a00a61d5ef6467c334c1e594fbae86 (diff)
downloadsqlalchemy-1ee4736beaadeb9053f8886503b64ee04fa4b557.tar.gz
merge latest default
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py70
-rw-r--r--lib/sqlalchemy/engine/base.py14
-rw-r--r--lib/sqlalchemy/engine/reflection.py16
-rw-r--r--lib/sqlalchemy/engine/result.py10
-rw-r--r--lib/sqlalchemy/event.py3
-rw-r--r--lib/sqlalchemy/ext/compiler.py11
-rw-r--r--lib/sqlalchemy/ext/mutable.py91
-rw-r--r--lib/sqlalchemy/ext/serializer.py8
-rw-r--r--lib/sqlalchemy/orm/deprecated_interfaces.py2
-rw-r--r--lib/sqlalchemy/orm/interfaces.py33
-rw-r--r--lib/sqlalchemy/orm/loading.py15
-rw-r--r--lib/sqlalchemy/orm/mapper.py6
-rw-r--r--lib/sqlalchemy/orm/query.py37
-rw-r--r--lib/sqlalchemy/orm/strategies.py83
-rw-r--r--lib/sqlalchemy/orm/util.py189
-rw-r--r--lib/sqlalchemy/schema.py23
-rw-r--r--lib/sqlalchemy/sql/compiler.py10
-rw-r--r--lib/sqlalchemy/sql/expression.py11
18 files changed, 408 insertions, 224 deletions
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
index bee730800..c5d9e8a89 100644
--- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py
+++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
@@ -80,8 +80,40 @@ To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_en
Two Phase Transaction Support
-----------------------------
-Two Phase transactions are implemented using XA transactions. Success has been reported
-with this feature but it should be regarded as experimental.
+Two Phase transactions are implemented using XA transactions, and are known
+to work in a rudimental fashion with recent versions of cx_Oracle
+as of SQLAlchemy 0.8.0b2, 0.7.10. However, the mechanism is not yet
+considered to be robust and should still be regarded as experimental.
+
+In particular, the cx_Oracle DBAPI as recently as 5.1.2 has a bug regarding
+two phase which prevents
+a particular DBAPI connection from being consistently usable in both
+prepared transactions as well as traditional DBAPI usage patterns; therefore
+once a particular connection is used via :meth:`.Connection.begin_prepared`,
+all subsequent usages of the underlying DBAPI connection must be within
+the context of prepared transactions.
+
+The default behavior of :class:`.Engine` is to maintain a pool of DBAPI
+connections. Therefore, due to the above glitch, a DBAPI connection that has
+been used in a two-phase operation, and is then returned to the pool, will
+not be usable in a non-two-phase context. To avoid this situation,
+the application can make one of several choices:
+
+* Disable connection pooling using :class:`.NullPool`
+
+* Ensure that the particular :class:`.Engine` in use is only used
+ for two-phase operations. A :class:`.Engine` bound to an ORM
+ :class:`.Session` which includes ``twophase=True`` will consistently
+ use the two-phase transaction style.
+
+* For ad-hoc two-phase operations without disabling pooling, the DBAPI
+ connection in use can be evicted from the connection pool using the
+ :class:`.Connection.detach` method.
+
+.. versionchanged:: 0.8.0b2,0.7.10
+ Support for cx_oracle prepared transactions has been implemented
+ and tested.
+
Precision Numerics
------------------
@@ -150,8 +182,9 @@ a period "." as the decimal character.
"""
-from .base import OracleCompiler, OracleDialect, \
- RESERVED_WORDS, OracleExecutionContext
+from __future__ import absolute_import
+
+from .base import OracleCompiler, OracleDialect, OracleExecutionContext
from . import base as oracle
from ...engine import result as _result
from sqlalchemy import types as sqltypes, util, exc, processors
@@ -270,6 +303,13 @@ class _OracleText(_LOBMixin, sqltypes.Text):
return dbapi.CLOB
+class _OracleLong(oracle.LONG):
+ # a raw LONG is a text type, but does *not*
+ # get the LobMixin with cx_oracle.
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.LONG_STRING
+
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
pass
@@ -499,6 +539,10 @@ class OracleDialect_cx_oracle(OracleDialect):
sqltypes.UnicodeText: _OracleUnicodeText,
sqltypes.CHAR: _OracleChar,
+ # a raw LONG is a text type, but does *not*
+ # get the LobMixin with cx_oracle.
+ oracle.LONG: _OracleLong,
+
# this is only needed for OUT parameters.
# it would be nice if we could not use it otherwise.
sqltypes.Integer: _OracleInteger,
@@ -779,15 +823,23 @@ class OracleDialect_cx_oracle(OracleDialect):
connection.connection.begin(*xid)
def do_prepare_twophase(self, connection, xid):
- connection.connection.prepare()
+ result = connection.connection.prepare()
+ connection.info['cx_oracle_prepared'] = result
- def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False):
+ def do_rollback_twophase(self, connection, xid, is_prepared=True,
+ recover=False):
self.do_rollback(connection.connection)
- def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False):
- self.do_commit(connection.connection)
+ def do_commit_twophase(self, connection, xid, is_prepared=True,
+ recover=False):
+ if not is_prepared:
+ self.do_commit(connection.connection)
+ else:
+ oci_prepared = connection.info['cx_oracle_prepared']
+ if oci_prepared:
+ self.do_commit(connection.connection)
def do_recover_twophase(self, connection):
- pass
+ connection.info.pop('cx_oracle_prepared', None)
dialect = OracleDialect_cx_oracle
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 626fee8c6..e3b09e63a 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -121,7 +121,7 @@ class Connection(Connectable):
Note that any key/value can be passed to
:meth:`.Connection.execution_options`, and it will be stored in the
- ``_execution_options`` dictionary of the :class:`.Connnection`. It
+ ``_execution_options`` dictionary of the :class:`.Connection`. It
is suitable for usage by end-user schemes to communicate with
event listeners, for example.
@@ -1295,8 +1295,8 @@ class TwoPhaseTransaction(Transaction):
class Engine(Connectable, log.Identified):
"""
Connects a :class:`~sqlalchemy.pool.Pool` and
- :class:`~sqlalchemy.engine.base.Dialect` together to provide a source
- of database connectivity and behavior.
+ :class:`~sqlalchemy.engine.interfaces.Dialect` together to provide a
+ source of database connectivity and behavior.
An :class:`.Engine` object is instantiated publicly using the
:func:`~sqlalchemy.create_engine` function.
@@ -1426,15 +1426,15 @@ class Engine(Connectable, log.Identified):
@property
def name(self):
- """String name of the :class:`~sqlalchemy.engine.Dialect` in use by
- this ``Engine``."""
+ """String name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
+ in use by this :class:`Engine`."""
return self.dialect.name
@property
def driver(self):
- """Driver name of the :class:`~sqlalchemy.engine.Dialect` in use by
- this ``Engine``."""
+ """Driver name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
+ in use by this :class:`Engine`."""
return self.dialect.driver
diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py
index 8367d8761..f1681d616 100644
--- a/lib/sqlalchemy/engine/reflection.py
+++ b/lib/sqlalchemy/engine/reflection.py
@@ -55,7 +55,7 @@ class Inspector(object):
"""Performs database schema inspection.
The Inspector acts as a proxy to the reflection methods of the
- :class:`~sqlalchemy.engine.base.Dialect`, providing a
+ :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
consistent interface as well as caching support for previously
fetched metadata.
@@ -72,7 +72,7 @@ class Inspector(object):
engine = create_engine('...')
insp = Inspector.from_engine(engine)
- Where above, the :class:`~sqlalchemy.engine.base.Dialect` may opt
+ Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` may opt
to return an :class:`.Inspector` subclass that provides additional
methods specific to the dialect's target database.
@@ -81,13 +81,13 @@ class Inspector(object):
def __init__(self, bind):
"""Initialize a new :class:`.Inspector`.
- :param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
+ :param bind: a :class:`~sqlalchemy.engine.Connectable`,
which is typically an instance of
:class:`~sqlalchemy.engine.Engine` or
:class:`~sqlalchemy.engine.Connection`.
For a dialect-specific instance of :class:`.Inspector`, see
- :meth:`Inspector.from_engine`
+ :meth:`.Inspector.from_engine`
"""
# this might not be a connection, it could be an engine.
@@ -111,16 +111,16 @@ class Inspector(object):
"""Construct a new dialect-specific Inspector object from the given
engine or connection.
- :param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
+ :param bind: a :class:`~sqlalchemy.engine.Connectable`,
which is typically an instance of
:class:`~sqlalchemy.engine.Engine` or
:class:`~sqlalchemy.engine.Connection`.
This method differs from direct a direct constructor call of
:class:`.Inspector` in that the
- :class:`~sqlalchemy.engine.base.Dialect` is given a chance to provide
- a dialect-specific :class:`.Inspector` instance, which may provide
- additional methods.
+ :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
+ provide a dialect-specific :class:`.Inspector` instance, which may
+ provide additional methods.
See the example at :class:`.Inspector`.
diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py
index 98b0ea4b2..dcfd5ac31 100644
--- a/lib/sqlalchemy/engine/result.py
+++ b/lib/sqlalchemy/engine/result.py
@@ -557,7 +557,7 @@ class ResultProxy(object):
supports "returning" and the insert statement executed
with the "implicit returning" enabled.
- Raises :class:`.InvalidRequestError` if the executed
+ Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
statement is not a compiled expression construct
or is not an insert() construct.
@@ -583,7 +583,7 @@ class ResultProxy(object):
"""Return the collection of updated parameters from this
execution.
- Raises :class:`.InvalidRequestError` if the executed
+ Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
statement is not a compiled expression construct
or is not an update() construct.
@@ -605,7 +605,7 @@ class ResultProxy(object):
"""Return the collection of inserted parameters from this
execution.
- Raises :class:`.InvalidRequestError` if the executed
+ Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
statement is not a compiled expression construct
or is not an insert() construct.
@@ -639,7 +639,7 @@ class ResultProxy(object):
See :class:`.ExecutionContext` for details.
- Raises :class:`.InvalidRequestError` if the executed
+ Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
statement is not a compiled expression construct
or is not an insert() or update() construct.
@@ -661,7 +661,7 @@ class ResultProxy(object):
See :class:`.ExecutionContext` for details.
- Raises :class:`.InvalidRequestError` if the executed
+ Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
statement is not a compiled expression construct
or is not an insert() or update() construct.
diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py
index bf996ae3c..6453a3987 100644
--- a/lib/sqlalchemy/event.py
+++ b/lib/sqlalchemy/event.py
@@ -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."
diff --git a/lib/sqlalchemy/ext/compiler.py b/lib/sqlalchemy/ext/compiler.py
index 93984d0d1..25de2c0b6 100644
--- a/lib/sqlalchemy/ext/compiler.py
+++ b/lib/sqlalchemy/ext/compiler.py
@@ -65,11 +65,12 @@ dialect is used.
Compiling sub-elements of a custom expression construct
=======================================================
-The ``compiler`` argument is the :class:`~sqlalchemy.engine.base.Compiled`
-object in use. This object can be inspected for any information about the
-in-progress compilation, including ``compiler.dialect``,
-``compiler.statement`` etc. The :class:`~sqlalchemy.sql.compiler.SQLCompiler`
-and :class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
+The ``compiler`` argument is the
+:class:`~sqlalchemy.engine.interfaces.Compiled` object in use. This object
+can be inspected for any information about the in-progress compilation,
+including ``compiler.dialect``, ``compiler.statement`` etc. The
+:class:`~sqlalchemy.sql.compiler.SQLCompiler` and
+:class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
method which can be used for compilation of embedded attributes::
from sqlalchemy.sql.expression import Executable, ClauseElement
diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py
index 36d60d6d5..e290a93e2 100644
--- a/lib/sqlalchemy/ext/mutable.py
+++ b/lib/sqlalchemy/ext/mutable.py
@@ -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):
diff --git a/lib/sqlalchemy/ext/serializer.py b/lib/sqlalchemy/ext/serializer.py
index 3ed41f48a..c129b0dcc 100644
--- a/lib/sqlalchemy/ext/serializer.py
+++ b/lib/sqlalchemy/ext/serializer.py
@@ -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":
diff --git a/lib/sqlalchemy/orm/deprecated_interfaces.py b/lib/sqlalchemy/orm/deprecated_interfaces.py
index bc9b352d4..d5b3d848e 100644
--- a/lib/sqlalchemy/orm/deprecated_interfaces.py
+++ b/lib/sqlalchemy/orm/deprecated_interfaces.py
@@ -385,7 +385,7 @@ class SessionExtension(object):
:class:`.SessionEvents`.
Subclasses may be installed into a :class:`.Session` (or
- :func:`.sessionmaker`) using the ``extension`` keyword
+ :class:`.sessionmaker`) using the ``extension`` keyword
argument::
from sqlalchemy.orm.interfaces import SessionExtension
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index c91746da0..55a980b2e 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -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:
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
index a5d156a1f..4bd80c388 100644
--- a/lib/sqlalchemy/orm/loading.py
+++ b/lib/sqlalchemy/orm/loading.py
@@ -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
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index b89163340..626105b5e 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -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
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index ca334e273..d6847177f 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -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 \
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 586ec4b4e..05c7ef37b 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -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)
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index e5e725138..04838cb64 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -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 TokenRegistry(PathRegistry):
+ def __init__(self, parent, token):
+ self.token = token
+ self.parent = parent
+ self.path = parent.path + (token,)
-class KeyRegistry(PathRegistry):
- def __init__(self, parent, key):
- self.key = key
+ 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 + (key,)
- self.reduced_path = parent.reduced_path + (key,)
+ 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_
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index f6a6b83b4..2fb542a43 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -146,7 +146,7 @@ class Table(SchemaItem, expression.TableClause):
table. The metadata is used as a point of association of this table
with other tables which are referenced via foreign key. It also
may be used to associate this table with a particular
- :class:`~sqlalchemy.engine.base.Connectable`.
+ :class:`.Connectable`.
:param \*args: Additional positional arguments are used primarily
to add the list of :class:`.Column` objects contained within this
@@ -240,7 +240,7 @@ class Table(SchemaItem, expression.TableClause):
This alternate hook to :func:`.event.listen` allows the establishment
of a listener function specific to this :class:`.Table` before
the "autoload" process begins. Particularly useful for
- the :meth:`.events.column_reflect` event::
+ the :meth:`.DDLEvents.column_reflect` event::
def listen_for_reflect(table, column_info):
"handle the column reflection event"
@@ -254,7 +254,7 @@ class Table(SchemaItem, expression.TableClause):
])
:param mustexist: When ``True``, indicates that this Table must already
- be present in the given :class:`.MetaData`` collection, else
+ be present in the given :class:`.MetaData` collection, else
an exception is raised.
:param prefixes:
@@ -2600,9 +2600,8 @@ class MetaData(SchemaItem):
in this ``MetaData`` no longer exists in the database.
:param bind:
- A :class:`~sqlalchemy.engine.base.Connectable` used to access the
- database; if None, uses the existing bind on this ``MetaData``, if
- any.
+ A :class:`.Connectable` used to access the database; if None, uses
+ the existing bind on this ``MetaData``, if any.
:param schema:
Optional, query and reflect tables from an alterate schema.
@@ -2689,7 +2688,7 @@ class MetaData(SchemaItem):
present in the target database.
:param bind:
- A :class:`~sqlalchemy.engine.base.Connectable` used to access the
+ A :class:`.Connectable` used to access the
database; if None, uses the existing bind on this ``MetaData``, if
any.
@@ -2716,7 +2715,7 @@ class MetaData(SchemaItem):
the target database.
:param bind:
- A :class:`~sqlalchemy.engine.base.Connectable` used to access the
+ A :class:`.Connectable` used to access the
database; if None, uses the existing bind on this ``MetaData``, if
any.
@@ -2858,14 +2857,14 @@ class DDLElement(expression.Executable, _DDLCompiles):
"""Execute this DDL immediately.
Executes the DDL statement in isolation using the supplied
- :class:`~sqlalchemy.engine.base.Connectable` or
- :class:`~sqlalchemy.engine.base.Connectable` assigned to the ``.bind``
+ :class:`.Connectable` or
+ :class:`.Connectable` assigned to the ``.bind``
property, if not supplied. If the DDL has a conditional ``on``
criteria, it will be invoked with None as the event.
:param bind:
Optional, an ``Engine`` or ``Connection``. If not supplied, a valid
- :class:`~sqlalchemy.engine.base.Connectable` must be present in the
+ :class:`.Connectable` must be present in the
``.bind`` property.
:param target:
@@ -3146,7 +3145,7 @@ class DDL(DDLElement):
available for use in string substitutions on the DDL statement.
:param bind:
- Optional. A :class:`~sqlalchemy.engine.base.Connectable`, used by
+ Optional. A :class:`.Connectable`, used by
default when ``execute()`` is invoked without a bind argument.
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 3856499fc..b7dc03414 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -2100,7 +2100,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"
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 42d6393ed..d3379bce5 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -1866,10 +1866,10 @@ class ClauseElement(Visitable):
def compile(self, bind=None, dialect=None, **kw):
"""Compile this SQL expression.
- The return value is a :class:`~sqlalchemy.engine.Compiled` object.
+ The return value is a :class:`~.Compiled` object.
Calling ``str()`` or ``unicode()`` on the returned value will yield a
string representation of the result. The
- :class:`~sqlalchemy.engine.Compiled` object also can return a
+ :class:`~.Compiled` object also can return a
dictionary of bind parameter names and values
using the ``params`` accessor.
@@ -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