switch for forcibly disabling explicit joins, and partial work for merging...

switch for forcibly disabling explicit joins, and partial work for merging related-key join-chains based on aliases

git-svn-id: https://svn.erp5.org/repos/public/erp5/sandbox/catalog_join@42594 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 5d17b17c
...@@ -60,7 +60,8 @@ class ColumnMap(object): ...@@ -60,7 +60,8 @@ class ColumnMap(object):
def __init__(self, def __init__(self,
catalog_table_name=None, catalog_table_name=None,
table_override_map=None, table_override_map=None,
left_join_list=None): left_join_list=None,
implicit_join=False):
self.catalog_table_name = catalog_table_name self.catalog_table_name = catalog_table_name
# Key: group # Key: group
# Value: set of column names # Value: set of column names
...@@ -104,6 +105,10 @@ class ColumnMap(object): ...@@ -104,6 +105,10 @@ class ColumnMap(object):
# We need to keep track of the original definition to do inner joins on it # We need to keep track of the original definition to do inner joins on it
self._inner_table_definition = self.table_definition self._inner_table_definition = self.table_definition
self.left_join_list = left_join_list self.left_join_list = left_join_list
self.implicit_join = implicit_join
assert not (self.implicit_join and self.left_join_list), (
"Cannot do left_joins while forcing implicit join"
)
@profiler_decorator @profiler_decorator
def registerColumn(self, raw_column, group=DEFAULT_GROUP_ID, simple_query=None): def registerColumn(self, raw_column, group=DEFAULT_GROUP_ID, simple_query=None):
...@@ -478,15 +483,19 @@ class ColumnMap(object): ...@@ -478,15 +483,19 @@ class ColumnMap(object):
def _isBackwardCompatibilityRequired(self): def _isBackwardCompatibilityRequired(self):
return bool( return bool(
# if they explicitly ask for implicit
self.implicit_join or
# if they don't pass a catalog alias, we cannot do explicit joins
not self._setMinimalTableDefinition() or
# If one or more RelatedKey methods weren't converted, we'll get # If one or more RelatedKey methods weren't converted, we'll get
# queries for an implicit inner join, so we have to do all joins # queries for an implicit inner join, so we have to do all joins
# as implicit. # as implicit.
self.join_query_list self.join_query_list or
# for now, work in BW compat mode if a table_override # for now, work in BW compat mode if a table_override
# is passed. It only works for simple subselect # is passed. It only works for simple subselect
# definitions anyway, and it's being used primarily # definitions anyway, and it's being used primarily
# for writing left-joins manually. # for writing left-joins manually.
or self.table_override_map) self.table_override_map)
def getTableAliasDict(self): def getTableAliasDict(self):
if self._isBackwardCompatibilityRequired(): if self._isBackwardCompatibilityRequired():
...@@ -620,19 +629,21 @@ class ColumnMap(object): ...@@ -620,19 +629,21 @@ class ColumnMap(object):
try: try:
catalog_table_alias = self.getCatalogTableAlias() catalog_table_alias = self.getCatalogTableAlias()
except KeyError: except KeyError:
LOG('ColumnMap', WARNING,
'_setMinimalTableDefinition called but the main catalog has not '
'yet received an alias!')
return False return False
inner_def.replace(self.makeTableAliasDefinition(self.catalog_table_name, inner_def.replace(self.makeTableAliasDefinition(self.catalog_table_name,
catalog_table_alias)) catalog_table_alias))
return True return True
def getTableDefinition(self): def getTableDefinition(self):
if not self._setMinimalTableDefinition():
raise RuntimeError("ColumnMap.build() must be called first!")
if self._isBackwardCompatibilityRequired(): if self._isBackwardCompatibilityRequired():
# BBB: One of the RelatedKeys registered an implicit join, do # BBB: One of the RelatedKeys registered an implicit join, do
# not return a table definition, self.getTableAliasDict() should # not return a table definition, self.getTableAliasDict() should
# be used instead # be used instead
return None return None
self.table_definition.checkTableAliases()
return self.table_definition return self.table_definition
def addRelatedKeyJoin(self, column, right_side, condition): def addRelatedKeyJoin(self, column, right_side, condition):
......
...@@ -64,7 +64,8 @@ class EntireQuery(object): ...@@ -64,7 +64,8 @@ class EntireQuery(object):
catalog_table_name=None, catalog_table_name=None,
extra_column_list=(), extra_column_list=(),
from_expression=None, from_expression=None,
order_by_override_list=None): order_by_override_list=None,
implicit_join=False):
self.query = query self.query = query
self.order_by_list = list(order_by_list) self.order_by_list = list(order_by_list)
self.order_by_override_set = frozenset(order_by_override_list) self.order_by_override_set = frozenset(order_by_override_list)
...@@ -75,6 +76,7 @@ class EntireQuery(object): ...@@ -75,6 +76,7 @@ class EntireQuery(object):
self.catalog_table_name = catalog_table_name self.catalog_table_name = catalog_table_name
self.extra_column_list = list(extra_column_list) self.extra_column_list = list(extra_column_list)
self.from_expression = from_expression self.from_expression = from_expression
self.implicit_join = implicit_join
def asSearchTextExpression(self, sql_catalog): def asSearchTextExpression(self, sql_catalog):
return self.query.asSearchTextExpression(sql_catalog) return self.query.asSearchTextExpression(sql_catalog)
...@@ -89,6 +91,7 @@ class EntireQuery(object): ...@@ -89,6 +91,7 @@ class EntireQuery(object):
column_map = ColumnMap(catalog_table_name=self.catalog_table_name, column_map = ColumnMap(catalog_table_name=self.catalog_table_name,
table_override_map=self.from_expression, table_override_map=self.from_expression,
left_join_list=self.left_join_list, left_join_list=self.left_join_list,
implicit_join=self.implicit_join,
) )
self.column_map = column_map self.column_map = column_map
for extra_column in self.extra_column_list: for extra_column in self.extra_column_list:
......
...@@ -2298,6 +2298,8 @@ class Catalog(Folder, ...@@ -2298,6 +2298,8 @@ class Catalog(Folder,
select_dict = dict([(x, None) for x in select_dict]) select_dict = dict([(x, None) for x in select_dict])
# Handle left_join_list # Handle left_join_list
left_join_list = kw.pop('left_join_list', ()) left_join_list = kw.pop('left_join_list', ())
# Handle implicit_join
implicit_join = kw.pop('implicit_join', False)
# Handle order_by_list # Handle order_by_list
order_by_list = kw.pop('order_by_list', None) order_by_list = kw.pop('order_by_list', None)
sort_on = kw.pop('sort_on', None) sort_on = kw.pop('sort_on', None)
...@@ -2336,6 +2338,7 @@ class Catalog(Folder, ...@@ -2336,6 +2338,7 @@ class Catalog(Folder,
group_by_list=group_by_list, group_by_list=group_by_list,
select_dict=select_dict, select_dict=select_dict,
left_join_list=left_join_list, left_join_list=left_join_list,
implicit_join=implicit_join,
limit=limit, limit=limit,
catalog_table_name=query_table, catalog_table_name=query_table,
extra_column_list=extra_column_list, extra_column_list=extra_column_list,
......
...@@ -181,8 +181,6 @@ class SQLExpression(object): ...@@ -181,8 +181,6 @@ class SQLExpression(object):
sql_expression.query, sql_expression.query,
', '.join('%r (%r)' % (x, x.query) for x in self.sql_expression_list)) ', '.join('%r (%r)' % (x, x.query) for x in self.sql_expression_list))
raise ValueError, message raise ValueError, message
if result is not None:
result.checkTableAliases()
return result return result
@profiler_decorator @profiler_decorator
......
...@@ -78,6 +78,14 @@ class TableDefinition(object): ...@@ -78,6 +78,14 @@ class TableDefinition(object):
def _extendJoinConditionQueryList(self, query_list): def _extendJoinConditionQueryList(self, query_list):
raise NotImplementedError('should be implemented by subclasses') raise NotImplementedError('should be implemented by subclasses')
def getSuperSet(self, other):
"""Checks if this TableDefinition is a subset of the other table
definition or vice-versa.
Returns whichever is the superset of the other or None
"""
raise NotImplementedError('should be implemented by subclasses')
class PlaceHolderTableDefinition(TableDefinition): class PlaceHolderTableDefinition(TableDefinition):
"""Table Definition that simply holds an inner table definition and """Table Definition that simply holds an inner table definition and
delegates to it the rendering. delegates to it the rendering.
...@@ -105,8 +113,14 @@ class PlaceHolderTableDefinition(TableDefinition): ...@@ -105,8 +113,14 @@ class PlaceHolderTableDefinition(TableDefinition):
return self.table_definition.render() return self.table_definition.render()
def _extendJoinConditionQueryList(self, query_list): def _extendJoinConditionQueryList(self, query_list):
# XXX _extendJoinConditionQueryList
#assert self.table_definition is not None, "table definition wasn't set"
if self.table_definition is not None:
return self.table_definition._extendJoinConditionQueryList(query_list)
def getSuperSet(self, other):
assert self.table_definition is not None, "table definition wasn't set" assert self.table_definition is not None, "table definition wasn't set"
return self.table_definition._extendJoinConditionQueryList(query_list) return self.table_definition.getSuperSet(other)
class TableAlias(TableDefinition): class TableAlias(TableDefinition):
"""Definition of a table alias as a FROM expression""" """Definition of a table alias as a FROM expression"""
...@@ -139,6 +153,22 @@ class TableAlias(TableDefinition): ...@@ -139,6 +153,22 @@ class TableAlias(TableDefinition):
def _extendJoinConditionQueryList(self, query_list): def _extendJoinConditionQueryList(self, query_list):
pass pass
def __eq__(self, other):
return (isinstance(other, TableAlias) and
self.table == other.table and
self.alias == other.alias)
def getSuperSet(self, other):
"""A TableAlias is a subset of another table Alias if either:
- the other is an equivalent TableAlias
- the other is an InnerJoin where the left-side is an equivalent TableAlias
"""
if isinstance(other, TableAlias) and self == other:
# we're just like the other guy, we could return self or other
return self
# delegate the rest of the job to InnerJoin
return other.getSuperSet(self)
JOIN_FORMAT = """ JOIN_FORMAT = """
( (
%(left)s %(left)s
...@@ -149,17 +179,18 @@ JOIN_FORMAT = """ ...@@ -149,17 +179,18 @@ JOIN_FORMAT = """
) )
""".strip() """.strip()
class InnerJoin(TableDefinition): class Join(TableDefinition):
"""Definition of an inner-join as a FROM expression"""
JOIN_TYPE = "INNER JOIN" JOIN_TYPE = None
def __init__(self, left_tabledef, right_tabledef, condition): def __init__(self, left_tabledef, right_tabledef, condition):
assert self.JOIN_TYPE, ('Join must be subclassed and self.JOIN_TYPE '
'must be defined.')
assert isinstance(left_tabledef, (TableDefinition, None.__class__)) assert isinstance(left_tabledef, (TableDefinition, None.__class__))
assert isinstance(right_tabledef, (TableDefinition, None.__class__)) assert isinstance(right_tabledef, (TableDefinition, None.__class__))
self.left_tabledef = left_tabledef self.left_tabledef = left_tabledef
self.right_tabledef = right_tabledef self.right_tabledef = right_tabledef
# perhaps expect condition to be a SQLExpression? # perhaps assert condition is an SQLExpression?
self.condition = condition self.condition = condition
def render(self): def render(self):
...@@ -187,6 +218,37 @@ class InnerJoin(TableDefinition): ...@@ -187,6 +218,37 @@ class InnerJoin(TableDefinition):
self.right_tabledef._extendJoinConditionQueryList(query_list) self.right_tabledef._extendJoinConditionQueryList(query_list)
query_list.append(SQLQuery(self.condition)) query_list.append(SQLQuery(self.condition))
def getSuperSet(self, other):
return None
class InnerJoin(Join):
"""Definition of an inner-join as a FROM expression"""
JOIN_TYPE = "INNER JOIN"
def getSuperSet(self, other):
"""This InnerJoin is a superset of another TableDefinition if either:
- other is a TableAlias (or None) equal to our
left_side. I.e. "other" is at the end of it's inner-join chain.
- other is an InnerJoin, and it's left-side is equal to our left-side (both TableAliases or None), and it's righ
"""
if self.left_tabledef == other:
# other and left-side are both None or TableAliases
return self
if (isinstance(other, InnerJoin) and
self.left_tabledef == other.left_tabledef):
# our left-sides match. If one of our right sides is a superset of the
# other, then one of is is the superset
sub_superset = self.right_tabledef.getSuperSet(other.right_tabledef)
if sub_superset is self.right_tabledef:
return self
elif sub_superset is other.right_tabledef:
return other
return None
return None
class LeftJoin(InnerJoin): class LeftJoin(InnerJoin):
"""Definition of a left-join as a FROM expression""" """Definition of a left-join as a FROM expression"""
...@@ -210,9 +272,6 @@ class LegacyTableDefinition(TableDefinition): ...@@ -210,9 +272,6 @@ class LegacyTableDefinition(TableDefinition):
self.from_expression = from_expression self.from_expression = from_expression
self.table_alias_map = table_alias_map self.table_alias_map = table_alias_map
def checkTableAliases(self, current_aliases=None):
pass
def render(self): def render(self):
from_expression_dict = self.from_expression from_expression_dict = self.from_expression
table_alias_map = self.table_alias_map table_alias_map = self.table_alias_map
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment