summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-08-08 15:52:50 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-08-08 15:52:50 -0400
commit56e628ea203219a7492c17e60eb58cd55c7d5caf (patch)
tree4b775d48f16c2be50c608b996f098f26850d0db7
parent9aa5d574fe743bae63d300953752c395a8dfe1b5 (diff)
parentbb3be98d3bee4b2bcef791be022ddb2510b9cf9c (diff)
downloadsqlalchemy-56e628ea203219a7492c17e60eb58cd55c7d5caf.tar.gz
- merge tip
- fixes
-rw-r--r--CHANGES17
-rw-r--r--doc/build/ormtutorial.rst39
-rw-r--r--doc/build/sqlexpression.rst45
-rw-r--r--doc/build/static/docs.css3
-rw-r--r--lib/sqlalchemy/orm/mapper.py2
-rw-r--r--lib/sqlalchemy/orm/properties.py4
-rw-r--r--lib/sqlalchemy/orm/util.py2
-rw-r--r--lib/sqlalchemy/sql/expression.py20
-rw-r--r--test/engine/test_transaction.py1
-rw-r--r--test/orm/test_query.py16
-rw-r--r--test/sql/test_selectable.py30
11 files changed, 156 insertions, 23 deletions
diff --git a/CHANGES b/CHANGES
index 9baf90487..0f35f9261 100644
--- a/CHANGES
+++ b/CHANGES
@@ -61,6 +61,13 @@ CHANGES
when joinedload() or subqueryload() options
are applied to a dynamic attribute, instead
of failure / silent failure. [ticket:1864]
+
+ - Fixed bug whereby generating a Query derived
+ from one which had the same column repeated
+ with different label names, typically
+ in some UNION situations, would fail to
+ propagate the inner columns completely to
+ the outer query. [ticket:1852]
- sql
- Changed the scheme used to generate truncated
@@ -111,6 +118,16 @@ CHANGES
columns in a reflected table would cause an attempt
to remove the reflected constraint from the table
a second time, raising a KeyError. [ticket:1865]
+
+ - the _Label construct, i.e. the one that is produced
+ whenever you say somecol.label(), now counts itself
+ in its "proxy_set" unioned with that of it's
+ contained column's proxy set, instead of
+ directly returning that of the contained column.
+ This allows column correspondence
+ operations which depend on the identity of the
+ _Labels themselves to return the correct result
+ - fixes ORM bug [ticket:1852].
- declarative
- if @classproperty is used with a regular class-bound
diff --git a/doc/build/ormtutorial.rst b/doc/build/ormtutorial.rst
index b5d7c83f0..2d6a3325e 100644
--- a/doc/build/ormtutorial.rst
+++ b/doc/build/ormtutorial.rst
@@ -3,7 +3,44 @@
==========================
Object Relational Tutorial
==========================
-In this tutorial we will cover a basic SQLAlchemy object-relational mapping scenario, where we store and retrieve Python objects from a database representation. The tutorial is in doctest format, meaning each ``>>>`` line represents something you can type at a Python command prompt, and the following text represents the expected return value.
+
+Introduction
+============
+
+The SQLAlchemy Object Relational Mapper presents a method of associating
+user-defined Python classes with database tables, and instances of those
+classes (objects) with rows in their corresponding tables. It includes a
+system that transparently synchronizes all changes in state between objects
+and their related rows, called a `unit of work
+<http://martinfowler.com/eaaCatalog/unitOfWork.html>`_, as well as a system
+for expressing database queries in terms of the user defined classes and their
+defined relationships between each other.
+
+The ORM is in contrast to the SQLAlchemy Expression Language, upon which the
+ORM is constructed. Whereas the SQL Expression Language, introduced in
+:ref:`sqlexpression_toplevel`, presents a system of representing the primitive
+constructs of the relational database directly without opinion, the ORM
+presents a high level and abstracted pattern of usage, which itself is an
+example of applied usage of the Expression Language.
+
+While there is overlap among the usage patterns of the ORM and the Expression
+Language, the similarities are more superficial than they may at first appear.
+One approaches the structure and content of data from the perspective of a
+user-defined `domain model
+<http://en.wikipedia.org/wiki/Domain_model>`_ which is transparently
+persisted and refreshed from its underlying storage model. The other
+approaches it from the perspective of literal schema and SQL expression
+representations which are explicitly composed into messages consumed
+individually by the database.
+
+A successful application may be constructed using the Object Relational Mapper
+exclusively. In advanced situations, an application constructed with the ORM
+may make occasional usage of the Expression Language directly in certain areas
+where specific database interactions are required.
+
+The following tutorial is in doctest format, meaning each ``>>>`` line
+represents something you can type at a Python command prompt, and the
+following text represents the expected return value.
Version Check
=============
diff --git a/doc/build/sqlexpression.rst b/doc/build/sqlexpression.rst
index 15116a273..23190a143 100644
--- a/doc/build/sqlexpression.rst
+++ b/doc/build/sqlexpression.rst
@@ -4,7 +4,50 @@
SQL Expression Language Tutorial
================================
-This tutorial will cover SQLAlchemy SQL Expressions, which are Python constructs that represent SQL statements. The tutorial is in doctest format, meaning each ``>>>`` line represents something you can type at a Python command prompt, and the following text represents the expected return value. The tutorial has no prerequisites.
+Introduction
+=============
+
+The SQLAlchemy Expression Language presents a system of representing
+relational database structures and expressions using Python constructs. These
+constructs are modeled to resemble those of the underlying database as closely
+as possible, while providing a modicum of abstraction of the various
+implementation differences between database backends. While the constructs
+attempt to represent equivalent concepts between backends with consistent
+structures, they do not conceal useful concepts that are unique to particular
+subsets of backends. The Expression Language therefore presents a method of
+writing backend-neutral SQL expressions, but does not attempt to enforce that
+expressions are backend-neutral.
+
+The Expression Language is in contrast to the Object Relational Mapper, which
+is a distinct API that builds on top of the Expression Language. Whereas the
+ORM, introduced in :ref:`ormtutorial_toplevel`, presents a high level and
+abstracted pattern of usage, which itself is an example of applied usage of
+the Expression Language, the Expression Language presents a system of
+representing the primitive constructs of the relational database directly
+without opinion.
+
+While there is overlap among the usage patterns of the ORM and the Expression
+Language, the similarities are more superficial than they may at first appear.
+One approaches the structure and content of data from the perspective of a
+user-defined `domain model
+<http://en.wikipedia.org/wiki/Domain_model>`_ which is transparently
+persisted and refreshed from its underlying storage model. The other
+approaches it from the perspective of literal schema and SQL expression
+representations which are explicitly composed into messages consumed
+individually by the database.
+
+A successful application may be constructed using the Expression Language
+exclusively, though the application will need to define its own system of
+translating application concepts into individual database messages and from
+individual database result sets. Alternatively, an application constructed
+with the ORM may, in advanced scenarios, make occasional usage of the
+Expression Language directly in certain areas where specific database
+interactions are required.
+
+The following tutorial is in doctest format, meaning each ``>>>`` line
+represents something you can type at a Python command prompt, and the
+following text represents the expected return value. The tutorial has no
+prerequisites.
Version Check
=============
diff --git a/doc/build/static/docs.css b/doc/build/static/docs.css
index 9127e980b..c0d634557 100644
--- a/doc/build/static/docs.css
+++ b/doc/build/static/docs.css
@@ -159,7 +159,8 @@ div.note, div.warning {
background-color:#EEFFEF;
}
-div.admonition {
+
+div.admonition, div.topic {
border:1px solid #CCCCCC;
margin:5px 5px 5px 5px;
padding:5px 5px 5px 35px;
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 1895798b1..0a89ac780 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -1075,7 +1075,7 @@ class Mapper(object):
def _is_userland_descriptor(self, obj):
return not isinstance(obj,
- (MapperProperty, attributes.InstrumentedAttribute)) and \
+ (MapperProperty, attributes.QueryableAttribute)) and \
hasattr(obj, '__get__') and not \
isinstance(obj.__get__(None, obj),
attributes.QueryableAttribute)
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 09c5042ff..f50d1e077 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -237,7 +237,6 @@ class CompositeProperty(ColumnProperty):
def __str__(self):
return str(self.parent.class_.__name__) + "." + self.key
-
class DescriptorProperty(MapperProperty):
""":class:`MapperProperty` which proxies access to a
user-defined descriptor."""
@@ -307,7 +306,7 @@ class DescriptorProperty(MapperProperty):
def merge(self, session, source_state, source_dict,
dest_state, dest_dict, load, _recursive):
pass
-
+
class ConcreteInheritedProperty(DescriptorProperty):
"""A 'do nothing' :class:`MapperProperty` that disables
an attribute on a concrete subclass that is only present
@@ -377,6 +376,7 @@ class SynonymProperty(DescriptorProperty):
def set_parent(self, parent, init):
if self.map_column:
+ # implement the 'map_column' option.
if self.key not in parent.mapped_table.c:
raise sa_exc.ArgumentError(
"Can't compile synonym '%s': no column on table "
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 49f572572..be2b024a2 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -546,7 +546,7 @@ def _entity_descriptor(entity, key):
"""
if not isinstance(entity, (AliasedClass, type)):
entity = entity.class_
-
+
try:
return getattr(entity, key)
except AttributeError:
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 4b8df74c6..0a5edb42f 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -3187,7 +3187,8 @@ class _Label(ColumnElement):
self._element = element
self._type = type_
self.quote = element.quote
-
+ self.proxies = [element]
+
@util.memoized_property
def type(self):
return sqltypes.to_instance(
@@ -3198,17 +3199,13 @@ class _Label(ColumnElement):
def element(self):
return self._element.self_group(against=operators.as_)
- def _proxy_attr(name):
- get = attrgetter(name)
- def attr(self):
- return get(self.element)
- return property(attr)
+ @property
+ def primary_key(self):
+ return self.element.primary_key
- proxies = _proxy_attr('proxies')
- base_columns = _proxy_attr('base_columns')
- proxy_set = _proxy_attr('proxy_set')
- primary_key = _proxy_attr('primary_key')
- foreign_keys = _proxy_attr('foreign_keys')
+ @property
+ def foreign_keys(self):
+ return self.element.foreign_keys
def get_children(self, **kwargs):
return self.element,
@@ -3225,6 +3222,7 @@ class _Label(ColumnElement):
e = self.element._make_proxy(selectable, name=self.name)
else:
e = column(self.name)._make_proxy(selectable=selectable)
+
e.proxies.append(self)
return e
diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py
index bc0985bec..e7e2fe1b8 100644
--- a/test/engine/test_transaction.py
+++ b/test/engine/test_transaction.py
@@ -868,6 +868,7 @@ class TLTransactionTest(TestBase):
assert r2.connection.closed
assert tlengine.closed
+ @testing.crashes('oracle+cx_oracle', 'intermittent failures on the buildbot')
def test_dispose(self):
eng = create_engine(testing.db.url, strategy='threadlocal')
result = eng.execute(select([1]))
diff --git a/test/orm/test_query.py b/test/orm/test_query.py
index 5fa316b76..30114f354 100644
--- a/test/orm/test_query.py
+++ b/test/orm/test_query.py
@@ -1132,6 +1132,22 @@ class SetOpsTest(QueryTest, AssertsCompiledSQL):
(User(id=10, name=u'chuck'), u'y')
]
)
+
+ c1, c2 = column('c1'), column('c2')
+ q1 = s.query(User, c1.label('foo'), c1.label('bar'))
+ q2 = s.query(User, c1.label('foo'), c2.label('bar'))
+ q3 = q1.union(q2)
+ self.assert_compile(
+ q3,
+ "SELECT anon_1.users_id AS anon_1_users_id, "
+ "anon_1.users_name AS anon_1_users_name, "
+ "anon_1.foo AS anon_1_foo, anon_1.bar AS anon_1_bar "
+ "FROM (SELECT users.id AS users_id, users.name AS users_name, "
+ "c1 AS foo, c1 AS bar FROM users UNION SELECT users.id AS "
+ "users_id, users.name AS users_name, c1 AS foo, c2 AS bar "
+ "FROM users) AS anon_1",
+ use_default_dialect=True
+ )
@testing.fails_on('mysql', "mysql doesn't support intersect")
def test_intersect(self):
diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py
index 062ed5f1b..5bebbe05f 100644
--- a/test/sql/test_selectable.py
+++ b/test/sql/test_selectable.py
@@ -28,20 +28,40 @@ table2 = Table('table2', metadata,
class SelectableTest(TestBase, AssertsExecutionResults):
- def test_distance_on_labels(self):
-
+ def test_indirect_correspondence_on_labels(self):
+ # this test depends upon 'distance' to
+ # get the right result
+
# same column three times
s = select([table1.c.col1.label('c2'), table1.c.col1,
table1.c.col1.label('c1')])
- # didnt do this yet...col.label().make_proxy() has same
- # "distance" as col.make_proxy() so far assert
- # s.corresponding_column(table1.c.col1) is s.c.col1
+ # this tests the same thing as
+ # test_direct_correspondence_on_labels below -
+ # that the presence of label() affects the 'distance'
+ assert s.corresponding_column(table1.c.col1) is s.c.col1
assert s.corresponding_column(s.c.col1) is s.c.col1
assert s.corresponding_column(s.c.c1) is s.c.c1
+ def test_direct_correspondence_on_labels(self):
+ # this test depends on labels being part
+ # of the proxy set to get the right result
+
+ l1, l2 = table1.c.col1.label('foo'), table1.c.col1.label('bar')
+ sel = select([l1, l2])
+
+ sel2 = sel.alias()
+ assert sel2.corresponding_column(l1) is sel2.c.foo
+ assert sel2.corresponding_column(l2) is sel2.c.bar
+
+ sel2 = select([table1.c.col1.label('foo'), table1.c.col2.label('bar')])
+
+ sel3 = sel.union(sel2).alias()
+ assert sel3.corresponding_column(l1) is sel3.c.foo
+ assert sel3.corresponding_column(l2) is sel3.c.bar
+
def test_distance_on_aliases(self):
a1 = table1.alias('a1')
for s in (select([a1, table1], use_labels=True),