diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-08-08 15:52:50 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-08-08 15:52:50 -0400 |
| commit | 56e628ea203219a7492c17e60eb58cd55c7d5caf (patch) | |
| tree | 4b775d48f16c2be50c608b996f098f26850d0db7 | |
| parent | 9aa5d574fe743bae63d300953752c395a8dfe1b5 (diff) | |
| parent | bb3be98d3bee4b2bcef791be022ddb2510b9cf9c (diff) | |
| download | sqlalchemy-56e628ea203219a7492c17e60eb58cd55c7d5caf.tar.gz | |
- merge tip
- fixes
| -rw-r--r-- | CHANGES | 17 | ||||
| -rw-r--r-- | doc/build/ormtutorial.rst | 39 | ||||
| -rw-r--r-- | doc/build/sqlexpression.rst | 45 | ||||
| -rw-r--r-- | doc/build/static/docs.css | 3 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/expression.py | 20 | ||||
| -rw-r--r-- | test/engine/test_transaction.py | 1 | ||||
| -rw-r--r-- | test/orm/test_query.py | 16 | ||||
| -rw-r--r-- | test/sql/test_selectable.py | 30 |
11 files changed, 156 insertions, 23 deletions
@@ -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), |
