diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-03-05 23:08:52 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-03-05 23:08:52 +0000 |
commit | 9154e1eff1da41c41b31e2a24a6128ff457c7716 (patch) | |
tree | 743bb691fc6e06e5b3d43c29a96568f1ac81a66e /lib/sqlalchemy | |
parent | 7693a680edf9776d3037283f3d63e3aeeb06649e (diff) | |
download | sqlalchemy-9154e1eff1da41c41b31e2a24a6128ff457c7716.tar.gz |
- added concept of 'require_embedded' to corresponding_column.
requires that the target column be present in a sub-element of the
target selectable.
- embedded logic above more appropriate for ClauseAdapter functionality
since its trying to "pull up" clauses that represent columns within
a larger union up to the level of the union itself.
- the "direction" test against the "foreign_keys" collection apparently
works for an exact "column 'x' is present in the collection", no proxy
relationships needed. fixes the case of relating a selectable/alias
to one of its underlying tables, probably fixes other scenarios
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/ansisql.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/sql.py | 36 | ||||
-rw-r--r-- | lib/sqlalchemy/sql_util.py | 6 |
4 files changed, 36 insertions, 15 deletions
diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py index 19cde3862..5d5c42208 100644 --- a/lib/sqlalchemy/ansisql.py +++ b/lib/sqlalchemy/ansisql.py @@ -391,9 +391,9 @@ class ANSICompiler(sql.Compiled): c.accept_visitor(self) inner_columns[self.get_str(c)] = c continue - try: + if hasattr(c, '_selectable'): s = c._selectable() - except AttributeError: + else: c.accept_visitor(self) inner_columns[self.get_str(c)] = c continue diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 98b386edd..95f6c1b3b 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -302,8 +302,9 @@ class PropertyLoader(StrategizedProperty): else: self.direction = sync.ONETOMANY else: - onetomany = len([c for c in self.foreign_keys if self.mapper.unjoined_table.corresponding_column(c, False) is not None]) - manytoone = len([c for c in self.foreign_keys if self.parent.unjoined_table.corresponding_column(c, False) is not None]) + onetomany = len([c for c in self.foreign_keys if self.mapper.unjoined_table.c.contains_column(c)]) + manytoone = len([c for c in self.foreign_keys if self.parent.unjoined_table.c.contains_column(c)]) + if not onetomany and not manytoone: raise exceptions.ArgumentError("Cant determine relation direction for relationship '%s' - foreign key columns are not present in neither the parent nor the child's mapped tables" %(str(self))) elif onetomany and manytoone: diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index da1afe799..073277d57 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -506,6 +506,21 @@ class ClauseVisitor(object): def visit_label(self, label):pass def visit_typeclause(self, typeclause):pass +class VisitColumnMixin(object): + """a mixin that adds Column traversal to a ClauseVisitor""" + def visit_table(self, table): + for c in table.c: + c.accept_visitor(self) + def visit_select(self, select): + for c in select.c: + c.accept_visitor(self) + def visit_compound_select(self, select): + for c in select.c: + c.accept_visitor(self) + def visit_alias(self, alias): + for c in alias.c: + c.accept_visitor(self) + class Executor(object): """Represent a *thing that can produce Compiled objects and execute them*.""" @@ -1041,20 +1056,25 @@ class FromClause(Selectable): self._oid_column = self._locate_oid_column() return self._oid_column - def corresponding_column(self, column, raiseerr=True, keys_ok=False, require_exact=False): + def _get_all_embedded_columns(self): + ret = [] + class FindCols(VisitColumnMixin, ClauseVisitor): + def visit_column(self, col): + ret.append(col) + self.accept_visitor(FindCols()) + return ret + + def corresponding_column(self, column, raiseerr=True, keys_ok=False, require_embedded=False): """Given a ``ColumnElement``, return the ``ColumnElement`` object from this ``Selectable`` which corresponds to that original ``Column`` via a proxy relationship. """ - if require_exact: - if self.columns.get(column.name) is column: - return column + if require_embedded and column not in util.Set(self._get_all_embedded_columns()): + if not raiseerr: + return None else: - if not raiseerr: - return None - else: - raise exceptions.InvalidRequestError("Column instance '%s' is not directly present in table '%s'" % (str(column), str(column.table))) + raise exceptions.InvalidRequestError("Column instance '%s' is not directly present within selectable '%s'" % (str(column), column.table)) for c in column.orig_set: try: return self.original_columns[c] diff --git a/lib/sqlalchemy/sql_util.py b/lib/sqlalchemy/sql_util.py index 3eb4b6d06..70fc85702 100644 --- a/lib/sqlalchemy/sql_util.py +++ b/lib/sqlalchemy/sql_util.py @@ -178,7 +178,7 @@ class Aliasizer(AbstractClauseProcessor): class ClauseAdapter(AbstractClauseProcessor): """Given a clause (like as in a WHERE criterion), locate columns - which *correspond* to a given selectable, and changes those + which are embedded within a given selectable, and changes those columns to be that of the selectable. E.g.:: @@ -219,10 +219,10 @@ class ClauseAdapter(AbstractClauseProcessor): if self.exclude is not None: if col in self.exclude: return None - newcol = self.selectable.corresponding_column(col, raiseerr=False, keys_ok=False) + newcol = self.selectable.corresponding_column(col, raiseerr=False, require_embedded=True, keys_ok=False) if newcol is None and self.equivalents is not None and col in self.equivalents: for equiv in self.equivalents[col]: - newcol = self.selectable.corresponding_column(equiv, raiseerr=False, keys_ok=False) + newcol = self.selectable.corresponding_column(equiv, raiseerr=False, require_embedded=True, keys_ok=False) if newcol: return newcol return newcol |