On 8 Feb 2006, at 20:52, Michael Bayer wrote:
wow reduce() ! the one python function i havent tried yet.
:-) You'll have to slap me if I get too lisp-y.
how come we need to test for an Alias wrapping a Select? why not
just put
an "is_correlated()" method on FromClause as a non implemented
method, and
each subclass of FromClause implements a version which either descends
into its child selectable, or its list of from objects, and determines
that there is a correlation occuring...that way MySQL compilation
just has
one simple test and the SQL object graph uses the is_correlated method
recursively/polymorphically to give you the right answer.
Yes, you're right, that would be better. I thought an Alias
containing a Select
was probably the only way it could currently happen, but now I think
about it
I suppose it's quite likely that there are or will be other ways
(e.g. a union of two select statements) that it could happen.
also is this correlated FROM issue on all versions of MySQL or just
some ?
on myISAM tables or InnoDB tables as well ?
All versions so far. There are suggested workarounds in the manual,
but nothing that looks terribly straightforward to do automatically.
So the patch becomes something like this:
Index: lib/sqlalchemy/sql.py
===================================================================
--- lib/sqlalchemy/sql.py (revision 926)
+++ lib/sqlalchemy/sql.py (working copy)
@@ -543,6 +543,8 @@
def _get_from_objects(self):
# this could also be [self], at the moment it doesnt matter
to the Select object
return []
+ def is_correlated (self):
+ raise NotImplementedError(repr(self))
def default_order_by(self):
if not self.engine.default_ordering:
return None
@@ -598,7 +600,7 @@
raise NotImplementedError(repr(self))
def _proxy_column(self, column):
return column._make_proxy(self)
-
+
class BindParamClause(ClauseElement, CompareMixin):
"""represents a bind parameter. public constructor is the
bindparam() function."""
def __init__(self, key, value, shortname=None, type=None):
@@ -865,6 +867,9 @@
statement"""
return True
+ def is_correlated(self):
+ return self.left.is_correlated() or self.right.is_correlated()
+
def hash_key(self):
return "Join(%s, %s, %s, %s)" % (repr(self.left.hash_key
()), repr(self.right.hash_key()), repr(self.onclause.hash_key()), repr
(self.isouter))
@@ -885,6 +890,8 @@
self.join = join
def _exportable_columns(self):
return []
+ def is_correlated(self):
+ return self.join.is_correlated()
def _process_from_dict(self, data, asfrom):
for f in self.onclause._get_from_objects():
@@ -919,6 +926,9 @@
def _exportable_columns(self):
return self.selectable.columns
+ def is_correlated(self):
+ return self.selectable.is_correlated()
+
def hash_key(self):
return "Alias(%s, %s)" % (self.selectable.hash_key(), repr
(self.name))
@@ -1078,6 +1088,9 @@
foreign_keys = property(lambda self:self.table.foreign_keys)
original_columns = property(_orig_columns)
+ def is_correlated(self):
+ return False
+
def _exportable_columns(self):
raise NotImplementedError()
@@ -1165,6 +1178,9 @@
[util.hash_key(s) for s in self.selects] +
["%s=%s" % (k, repr(getattr(self, k))) for k in
['use_labels', 'keyword']],
",")
+ def is_correlated(self):
+ return reduce(lambda x, sel: x or sel.is_correlated(),
+ self.selects, False)
def _exportable_columns(self):
return self.selects[0].columns
def _proxy_column(self, column):
@@ -1311,6 +1327,12 @@
fromclause.accept_visitor(self._correlator)
fromclause._process_from_dict(self._froms, True)
+ def is_correlated(self):
+ return (self._correlated is not None
+ and reduce(lambda x, f: (x or
self._correlated.has_key(f.id)
+ or f.is_correlated()),
+ self._froms.values(), False))
+
def _get_froms(self):
return [f for f in self._froms.values() if self._correlated
is None or not self._correlated.has_key(f.id)]
froms = property(lambda s: s._get_froms())
Index: lib/sqlalchemy/databases/mysql.py
===================================================================
--- lib/sqlalchemy/databases/mysql.py (revision 926)
+++ lib/sqlalchemy/databases/mysql.py (working copy)
@@ -244,7 +244,13 @@
text += " \n LIMIT 18446744073709551615"
text += " OFFSET " + str(select.offset)
return text
+
+ def visit_select(self, select):
+ if (select.is_correlated()):
+ raise "MySQL does not support correlated subqueries in
the FROM clause. If it was not your intention to perform a
correlated subquery, you may need to use a table alias to avoid this
problem."
+ super(MySQLCompiler, self).visit_select(select)
+
class MySQLSchemaGenerator(ansisql.ANSISchemaGenerator):
def get_column_specification(self, column, override_pk=False,
first_pk=False):
colspec = column.name + " " + column.type.get_col_spec()
My understanding of exactly how all of sql.py fits together is still
not 100%, so it wouldn't surprise me if I've missed something. Also,
perhaps we need some new unit tests that exercise this code to make
sure that it works (I might get some time to do something tomorrow,
we'll see).
Kind regards,
Alastair.
--
http://www.alastairs-place.net
-------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc. Do you grep through log files
for problems? Stop! Download the new AJAX search engine that makes
searching your log files as easy as surfing the web. DOWNLOAD SPLUNK!
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=103432&bid=230486&dat=121642
_______________________________________________
Sqlalchemy-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users