From: Mike Bayer Date: Thu, 26 Jul 2007 18:37:53 +0000 (+0000) Subject: - fixed clause_element/expression_element change from preivous checkin X-Git-Tag: rel_0_4_6~12 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e4c99a2c2a7e40e8e31fc5a6d37598da41e1ecb9;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - fixed clause_element/expression_element change from preivous checkin - starting to add "out" params for oracle - implemented partial check for "rejoin on conflicting paths" for [ticket:687] --- diff --git a/CHANGES b/CHANGES index eee6730b53..01c245038b 100644 --- a/CHANGES +++ b/CHANGES @@ -217,7 +217,16 @@ 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 @@ -236,9 +245,6 @@ - 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 diff --git a/lib/sqlalchemy/databases/oracle.py b/lib/sqlalchemy/databases/oracle.py index ad60eabc13..38a7b50de8 100644 --- a/lib/sqlalchemy/databases/oracle.py +++ b/lib/sqlalchemy/databases/oracle.py @@ -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 diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 8028f0fc81..47ff260853 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -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) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 30a6088908..aeb8a23fa1 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -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) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 7b3f27a614..284653b5c5 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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: diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index be72eb1e46..a40af7d6d0 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -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)]) diff --git a/test/orm/query.py b/test/orm/query.py index 8943a6122f..3783e1fa0c 100644 --- a/test/orm/query.py +++ b/test/orm/query.py @@ -452,6 +452,49 @@ class JoinTest(QueryTest): except exceptions.InvalidRequestError, e: assert str(e) == "Ambiguous join for entity 'Mapper|Order|orders'; specify id= 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