]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- fixed clause_element/expression_element change from preivous checkin
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 26 Jul 2007 18:37:53 +0000 (18:37 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 26 Jul 2007 18:37:53 +0000 (18:37 +0000)
- starting to add "out" params for oracle
- implemented partial check for "rejoin on conflicting paths" for [ticket:687]

CHANGES
lib/sqlalchemy/databases/oracle.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql.py
test/orm/query.py

diff --git a/CHANGES b/CHANGES
index eee6730b536975148c8070b792576072e1a9b798..01c245038ba99cfcb1bd906d411e3b921881ffe1 100644 (file)
--- a/CHANGES
+++ b/CHANGES
     from SelectResults isn't present anymore, need to use join(). 
 - postgres
   - Added PGArray datatype for using postgres array datatypes
-  
+
+0.3.11
+- orm
+    - added a check for joining from A->B using join(), along two
+      different m2m tables.  this raises an error in 0.3 but is 
+      possible in 0.4 when aliases are used. [ticket:687]
+- mssql
+    - added support for TIME columns (simulated using DATETIME) [ticket:679]
+    - index names are now quoted when dropping from reflected tables [ticket:684]
+      
 0.3.10
 - general
     - a new mutex that was added in 0.3.9 causes the pool_timeout
 - postgres
   - fixed max identifier length (63) [ticket:571]
 
-- mssql
-    - added support for TIME columns (simulated using DATETIME) [ticket:679]
-    - index names are now quoted when dropping from reflected tables [ticket:684]
   
 0.3.9
 - general
index ad60eabc13935dd09c4f6b4072d51d65d92ac43c..38a7b50de81fa5f1177b82d7e3ac0adac4fb501a 100644 (file)
@@ -519,6 +519,7 @@ class OracleCompiler(ansisql.ANSICompiler):
             
     def visit_outer_join_column(self, vc):
         return self.process(vc.column) + "(+)"
+        
     def uses_sequences_for_inserts(self):
         return True
 
index 8028f0fc81e4b5e2c6d356ed8f20577f2a48477b..47ff260853d570f4ab9523e2eb54cf8a0435f626 100644 (file)
@@ -84,6 +84,9 @@ class InstrumentedAttribute(interfaces.PropComparator):
 
     def clause_element(self):
         return self.comparator.clause_element()
+
+    def expression_element(self):
+        return self.comparator.expression_element()
         
     def operate(self, op, other, **kwargs):
         return op(self.comparator, other, **kwargs)
index 30a608890887b42f86505352c18822080b8497a0..aeb8a23fa114e3bec963b4fd9f51c4bfd018eec6 100644 (file)
@@ -345,7 +345,10 @@ class MapperProperty(object):
 
 class PropComparator(sql.ColumnOperators):
     """defines comparison operations for MapperProperty objects"""
-
+    
+    def expression_element(self):
+        return self.clause_element()
+        
     def contains_op(a, b):
         return a.contains(b)
     contains_op = staticmethod(contains_op)
index 7b3f27a614be5f21f0e306aaa3259e94254c0946..284653b5c5aaedc4b389690cd9687a49e67e5eb5 100644 (file)
@@ -318,7 +318,6 @@ class Query(object):
                 currenttables.append(join.left)
                 currenttables.append(join.right)
         FindJoinedTables().traverse(clause)
-            
         
         mapper = start
         alias = self._aliases
@@ -349,6 +348,11 @@ class Query(object):
                         clause = clause.join(alias.alias, alias.primaryjoin, isouter=outerjoin)
                     else:
                         clause = clause.join(prop.select_table, prop.get_join(mapper), isouter=outerjoin)
+            elif not create_aliases and prop.secondary is not None and prop.secondary not in currenttables:
+                # TODO: this check is not strong enough for different paths to the same endpoint which
+                # does not use secondary tables
+                raise exceptions.InvalidRequestError("Can't join to property '%s'; a path to this table along a different secondary table already exists.  Use the `alias=True` argument to `join()`." % prop.key)
+                
             mapper = prop.mapper
             
         if create_aliases:
index be72eb1e467d716fa4779f6181d2bccfa096f442..a40af7d6d0a1712f61fceead02241c25160b04d5 100644 (file)
@@ -672,6 +672,17 @@ def bindparam(key, value=None, type_=None, shortname=None, unique=False):
     else:
         return _BindParamClause(key, value, type_=type_, shortname=shortname, unique=unique)
 
+def outparam(key, type_=None):
+    """create an 'OUT' parameter for usage in functions (stored procedures), for databases 
+    whith support them.
+    
+    The ``outparam`` can be used like a regular function parameter.  The "output" value will
+    be available from the [sqlalchemy.engine#ResultProxy] object via its ``out_parameters``
+    attribute, which returns a dictionary containing the values.
+    """
+    
+    return _BindParamClause(key, type_=type_, unique=False, isoutparam=True)
+    
 def text(text, bind=None, *args, **kwargs):
     """Create literal text to be inserted into a query.
 
@@ -746,7 +757,7 @@ def _is_literal(element):
 
 def _literal_as_text(element):
     if isinstance(element, Operators):
-        return element.clause_element()
+        return element.expression_element()
     elif _is_literal(element):
         return _TextClause(unicode(element))
     else:
@@ -762,7 +773,7 @@ def _literal_as_column(element):
     
 def _literal_as_binds(element, name='literal', type_=None):
     if isinstance(element, Operators):
-        return element.clause_element()
+        return element.expression_element()
     elif _is_literal(element):
         if element is None:
             return null()
@@ -1414,7 +1425,7 @@ class _CompareMixin(ColumnOperators):
 
     def _check_literal(self, other):
         if isinstance(other, Operators):
-            return other.clause_element()
+            return other.expression_element()
         elif _is_literal(other):
             return self._bind_param(other)
         else:
@@ -1422,7 +1433,6 @@ class _CompareMixin(ColumnOperators):
     
     def clause_element(self):
         """Allow ``_CompareMixins`` to return the underlying ``ClauseElement``, for non-``ClauseElement`` ``_CompareMixins``."""
-
         return self
 
     def expression_element(self):
@@ -1830,7 +1840,7 @@ class _BindParamClause(ClauseElement, _CompareMixin):
 
     __visit_name__ = 'bindparam'
     
-    def __init__(self, key, value, shortname=None, type_=None, unique=False):
+    def __init__(self, key, value, shortname=None, type_=None, unique=False, isoutparam=False):
         """Construct a _BindParamClause.
 
         key
@@ -1863,12 +1873,17 @@ class _BindParamClause(ClauseElement, _CompareMixin):
           modified if another ``_BindParamClause`` of the same
           name already has been located within the containing 
           ``ClauseElement``.
+          
+        isoutparam
+          if True, the parameter should be treated like a stored procedure "OUT"
+          parameter.
         """
 
         self.key = key or "{ANON %d param}" % id(self)
         self.value = value
         self.shortname = shortname or key
         self.unique = unique
+        self.isoutparam = isoutparam
         type_ = sqltypes.to_instance(type_)
         if isinstance(type_, sqltypes.NullType) and type(value) in _BindParamClause.type_map:
             self.type = sqltypes.to_instance(_BindParamClause.type_map[type(value)])
index 8943a6122fa2753712248f3a6c496008d489cf0e..3783e1fa0c2aa47390c3d9b2f6be4833970e12e5 100644 (file)
@@ -452,6 +452,49 @@ class JoinTest(QueryTest):
         except exceptions.InvalidRequestError, e:
             assert str(e) == "Ambiguous join for entity 'Mapper|Order|orders'; specify id=<someid> to query.join()/query.add_entity()"
 
+class MultiplePathTest(ORMTest):
+    def define_tables(self, metadata):
+        global t1, t2, t1t2_1, t1t2_2
+        t1 = Table('t1', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('data', String(30))
+            )
+        t2 = Table('t2', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('data', String(30))
+            )
+
+        t1t2_1 = Table('t1t2_1', metadata,
+            Column('t1id', Integer, ForeignKey('t1.id')),
+            Column('t2id', Integer, ForeignKey('t2.id'))
+            )
+
+        t1t2_2 = Table('t1t2_2', metadata,
+            Column('t1id', Integer, ForeignKey('t1.id')),
+            Column('t2id', Integer, ForeignKey('t2.id'))
+            )
+
+    def test_basic(self):
+        class T1(object):pass
+        class T2(object):pass
+
+        mapper(T1, t1, properties={
+            't2s_1':relation(T2, secondary=t1t2_1),
+            't2s_2':relation(T2, secondary=t1t2_2),
+        })
+        mapper(T2, t2)
+
+        try:
+            create_session().query(T1).join('t2s_1').filter(t2.c.id==5).reset_joinpoint().join('t2s_2')
+            assert False
+        except exceptions.InvalidRequestError, e:
+            assert str(e) == "Can't join to property 't2s_2'; a path to this table along a different secondary table already exists.  Use the `alias=True` argument to `join()`."
+
+        create_session().query(T1).join('t2s_1', aliased=True).filter(t2.c.id==5).reset_joinpoint().join('t2s_2').all()
+        create_session().query(T1).join('t2s_1').filter(t2.c.id==5).reset_joinpoint().join('t2s_2', aliased=True).all()
+        
+        
+
 class SynonymTest(QueryTest):
     keep_mappers = True
     keep_data = True