diff --git a/product/ZSQLCatalog/ColumnMap.py b/product/ZSQLCatalog/ColumnMap.py index c9c25a1fd29588977e03a416e169a17a39e05ef6..e1f7fcb569cc4d4486b2592dbc62918cd02067c1 100644 --- a/product/ZSQLCatalog/ColumnMap.py +++ b/product/ZSQLCatalog/ColumnMap.py @@ -36,7 +36,8 @@ from Products.ZSQLCatalog.interfaces.column_map import IColumnMap from Products.ZSQLCatalog.SQLCatalog import profiler_decorator from Products.ZSQLCatalog.TableDefinition import (PlaceHolderTableDefinition, TableAlias, - InnerJoin) + InnerJoin, + LeftJoin) DEFAULT_GROUP_ID = None @@ -55,7 +56,10 @@ class ColumnMap(object): implements(IColumnMap) @profiler_decorator - def __init__(self, catalog_table_name=None, table_override_map=None): + def __init__(self, + catalog_table_name=None, + table_override_map=None, + left_join_list=None): self.catalog_table_name = catalog_table_name # Key: group # Value: set of column names @@ -89,18 +93,19 @@ class ColumnMap(object): # Entries: column name self.column_ignore_set = set() self.join_table_set = set() - self.straight_join_table_list = [] - self.left_join_table_list = [] self.join_query_list = [] self.table_override_map = table_override_map or {} self.table_definition = PlaceHolderTableDefinition() # We need to keep track of the original definition to do inner joins on it self._inner_table_definition = self.table_definition + self.left_join_list = left_join_list @profiler_decorator def registerColumn(self, raw_column, group=DEFAULT_GROUP_ID, simple_query=None): assert ' as ' not in raw_column.lower() # Sanitize input: extract column from raw column (might contain COUNT, ...). + # XXX This is not enough to parse something like: + # GROUP_CONCAT(DISTINCT foo ORDER BY bar) if '(' in raw_column: function, column = raw_column.split('(') column = column.strip() @@ -529,14 +534,6 @@ class ColumnMap(object): return [self.getTableAlias(table_name, group=group) for (group, table_name) in self.join_table_set] - def getStraightJoinTableList(self): - # XXX this function is unused and should be removed - return self.straight_join_table_list[:] - - def getLeftJoinTableList(self): - # XXX this function is unused and should be removed - return self.left_join_table_list[:] - def _getTableOverride(self, table_name): # self.table_override_map is a dictionary mapping table names to # strings containing aliases of arbitrary table definitions @@ -545,9 +542,12 @@ class ColumnMap(object): table_override_w_alias = self.table_override_map.get(table_name) if table_override_w_alias is None: return table_name - # XXX move the table aliasing cleanup to EntireQuery class, so we - # don't need SQL syntax knowledge in ColumnMap. Normalise the AS - # sql keyword to remove the last aliasing in the string if present. E.g.: + # XXX move the cleanup of table alias overrides to EntireQuery + # class or ZSQLCatalog, so we don't need SQL syntax knowledge in + # ColumnMap. + # + # Normalise the AS sql keyword to remove the last + # aliasing in the string if present. E.g.: # # '(SELECT sub_catalog.* # FROM catalog AS sub_catalog @@ -596,17 +596,23 @@ class ColumnMap(object): return self.table_definition return None - def addJoin(self, join_definition, condition): - """ Replaces the current table_definition with a new one, assuming - it is a join definition, and replacing it's left side with the - previous table definition. - - Effectively, this method wraps the current table definition within - the received join_definition. + def addRelatedKeyJoin(self, related_key_id, right_side, condition): + """ Wraps the current table_definition in the left-side of a new join. + Use an InnerJoin or a LeftJoin depending on whether the related_key_id is + in the left_join_list or not. """ + # XXX: to fix TestERP5Catalog.test_56_CreateUidDuringClearCatalog, + # Create here a list of joins and try to merge each new entry into + # one of the pre-existing entries by comparing their right-sides. + # XXX 2: This is the place were we could do ordering of inner and left + # joins so as to get better performance. For instance, a quick win is to + # add all inner-joins first, and all left-joins later. We could also decide + # on the order of left-joins based on the order of self.left_join_list or + # even a catalog property/configuration. assert self._setMinimalTableDefinition() - assert join_definition.left_tabledef is None, join_definition.left_tabledef - join_definition.left_tabledef = self.table_definition + Join = (related_key_id in self.left_join_list) and LeftJoin or InnerJoin + join_definition = Join(self.table_definition, right_side, + condition=condition) self.table_definition = join_definition # def getFinalTableDefinition(self): diff --git a/product/ZSQLCatalog/Query/EntireQuery.py b/product/ZSQLCatalog/Query/EntireQuery.py index dc4e6f3062ec5ac42410f655d0f5fb516bc570a4..21822b54eda40b6a6b7db769a995ec6307855169 100644 --- a/product/ZSQLCatalog/Query/EntireQuery.py +++ b/product/ZSQLCatalog/Query/EntireQuery.py @@ -54,15 +54,22 @@ class EntireQuery(object): column_map = None @profiler_decorator - def __init__(self, query, order_by_list=(), group_by_list=(), - select_dict=None, limit=None, catalog_table_name=None, - extra_column_list=(), from_expression=None, + def __init__(self, query, + order_by_list=(), + group_by_list=(), + select_dict=None, + left_join_list=(), + limit=None, + catalog_table_name=None, + extra_column_list=(), + from_expression=None, order_by_override_list=None): self.query = query self.order_by_list = list(order_by_list) self.order_by_override_set = frozenset(order_by_override_list) self.group_by_list = list(group_by_list) self.select_dict = defaultDict(select_dict) + self.left_join_list = left_join_list self.limit = limit self.catalog_table_name = catalog_table_name self.extra_column_list = list(extra_column_list) @@ -79,7 +86,9 @@ class EntireQuery(object): # method or do it here ? # Column Map was not built yet, do it. 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, + ) self.column_map = column_map for extra_column in self.extra_column_list: table, column = extra_column.replace('`', '').split('.') diff --git a/product/ZSQLCatalog/SQLCatalog.py b/product/ZSQLCatalog/SQLCatalog.py index 7c9099e1eb36861a0e7837ca05d5c3f36669f587..8a5713f5ad7d984658dc7a7f469e774851518991 100644 --- a/product/ZSQLCatalog/SQLCatalog.py +++ b/product/ZSQLCatalog/SQLCatalog.py @@ -2296,6 +2296,8 @@ class Catalog(Folder, select_dict = None elif isinstance(select_dict, (list, tuple)): select_dict = dict([(x, None) for x in select_dict]) + # Handle left_join_list + left_join_list = kw.pop('left_join_list', ()) # Handle order_by_list order_by_list = kw.pop('order_by_list', None) sort_on = kw.pop('sort_on', None) @@ -2333,6 +2335,7 @@ class Catalog(Folder, order_by_override_list=order_by_override_list, group_by_list=group_by_list, select_dict=select_dict, + left_join_list=left_join_list, limit=limit, catalog_table_name=query_table, extra_column_list=extra_column_list, diff --git a/product/ZSQLCatalog/SearchKey/RelatedKey.py b/product/ZSQLCatalog/SearchKey/RelatedKey.py index 98ac0f85abac1ddffe9d3a6823f2733a35a06577..9dafc5109f32967bd8db81713f9ddc789ad3fd74 100644 --- a/product/ZSQLCatalog/SearchKey/RelatedKey.py +++ b/product/ZSQLCatalog/SearchKey/RelatedKey.py @@ -215,14 +215,15 @@ class RelatedKey(SearchKey): # add a left join on this related key, based on the inner-join of the # related key tables. query_table_join_condition = join_condition_list.pop() - right = self.stitchJoinDefinition(table_alias_list, - join_condition_list, - column_map) - table_def = LeftJoin(None, - right, - condition=query_table_join_condition) - column_map.addJoin(table_def, condition=query_table_join_condition) - return None # XXX decide what to do with the comment below + right_side = self.stitchJoinDefinition(table_alias_list, + join_condition_list, + column_map) + column_map.addRelatedKeyJoin(self.column, + right_side=right_side, + condition=query_table_join_condition) + return None + # XXX decide what to do with the comment below and the rest of the code. + # possibly we need to move all the code above into .registerColumnMap() # Important: # Former catalog separated join condition from related query. # Example: diff --git a/product/ZSQLCatalog/interfaces/column_map.py b/product/ZSQLCatalog/interfaces/column_map.py index 0c26bc5ac630948b57368c838969c1ebe74d9cc8..c17b807ff5845d189c8d5dca452edfbac14984b7 100644 --- a/product/ZSQLCatalog/interfaces/column_map.py +++ b/product/ZSQLCatalog/interfaces/column_map.py @@ -284,18 +284,3 @@ class IColumnMap(Interface): Return a copy of the table alias list for tables requiring a join with catalog table. """ - - def getStraightJoinTableList(): - """ - Returns the list of tables used this search and which - need to be joined with the main table using explicit - indices. - """ - - def getLeftJoinTableList(): - """ - Returns the list of tables used this search and which - need to be LEFT joined with the main table using explicit - indices. - """ -