Commit 4b65b915 authored by Yusuke Muraoka's avatar Yusuke Muraoka

A bug, which is reported by complex query test, is fixed.

That was category relation related.

Lazy generation of category relation is replaced by using
IN with sub query.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@30783 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent cfc75a55
...@@ -82,6 +82,7 @@ class ColumnMap(object): ...@@ -82,6 +82,7 @@ class ColumnMap(object):
# Entries: column name # Entries: column name
self.column_ignore_set = set() self.column_ignore_set = set()
self.join_table_set = set() self.join_table_set = set()
self.ignore_table_join_set = set()
self.straight_join_table_list = [] self.straight_join_table_list = []
self.left_join_table_list = [] self.left_join_table_list = []
self.join_query_list = [] self.join_query_list = []
...@@ -499,6 +500,19 @@ class ColumnMap(object): ...@@ -499,6 +500,19 @@ class ColumnMap(object):
self.resolveColumn('uid', catalog_table) self.resolveColumn('uid', catalog_table)
self.join_table_set.add((group, table_name)) self.join_table_set.add((group, table_name))
def getIgnoreingTableAliasList(self):
return [self.getTableAlias(table_name, group=group)
for (group, table_name) in self.ignore_table_join_set]
@profiler_decorator
def ignoreTableJoin(self, table_name, group=DEFAULT_GROUP_ID):
"""
Add a table, which is already or will be joined in anywhere,
to be ignored to join.
This values will be referred by EntireQuery.
"""
self.ignore_table_join_set.add((group, table_name))
def getJoinTableAliasList(self): def getJoinTableAliasList(self):
return [self.getTableAlias(table_name, group=group) return [self.getTableAlias(table_name, group=group)
for (group, table_name) in self.join_table_set] for (group, table_name) in self.join_table_set]
......
...@@ -174,7 +174,8 @@ class EntireQuery(object): ...@@ -174,7 +174,8 @@ class EntireQuery(object):
select_dict=self.final_select_dict, select_dict=self.final_select_dict,
limit=self.limit, limit=self.limit,
where_expression_operator='and', where_expression_operator='and',
sql_expression_list=self.sql_expression_list) sql_expression_list=self.sql_expression_list,
ignoreing_table_alias_list=column_map.getIgnoreingTableAliasList())
verifyClass(IEntireQuery, EntireQuery) verifyClass(IEntireQuery, EntireQuery)
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# MURAOKA Yusuke <yusuke@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Query import Query
from Products.ZSQLCatalog.SQLExpression import SQLExpression
from SQLQuery import SQLQuery
from Products.ZSQLCatalog.interfaces.query import IQuery
from zope.interface.verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
class SubCatalogQuery(Query):
"""
A Query do out child query of the Query to sub query and
relate to that in SQL.
"""
method_id = 'z_SubCatalogQuery'
@profiler_decorator
def __init__(self, query):
self.query = query
@profiler_decorator
def _asSearchTextExpression(self, sql_catalog, column=None):
if self.query:
return self.query._asSearchTextExpression(sql_catalog, column=column)
else:
return (False, '')
@profiler_decorator
def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
sql_expression = self.query.asSQLExpression(sql_catalog,
column_map,
only_group_columns)
method = getattr(sql_catalog, self.method_id, None)
# XXX: this if clause is exist for backward compatibility of testSQLCatalog.
if method:
where_expression = method(src__=1, **sql_expression.asSQLExpressionDict())
else:
where_expression = None
return SQLExpression(self,
where_expression=where_expression,
sql_expression_list=(sql_expression,))
@profiler_decorator
def registerColumnMap(self, sql_catalog, column_map):
if self.query:
self.query.registerColumnMap(sql_catalog, column_map)
@profiler_decorator
def setTableAliasList(self, table_alias_list):
if self.query:
self.query.setTableAliasList(table_alias_list)
def __repr__(self):
return '<%s with %r>' % (self.__class__.__name__, self.query)
...@@ -2253,6 +2253,7 @@ class Catalog(Folder, ...@@ -2253,6 +2253,7 @@ class Catalog(Folder,
kw['where_expression'] = query['where_expression'] kw['where_expression'] = query['where_expression']
kw['sort_on'] = query['order_by_expression'] kw['sort_on'] = query['order_by_expression']
kw['from_table_list'] = query['from_table_list'] kw['from_table_list'] = query['from_table_list']
kw['valid_from_table_list'] = query['valid_from_table_list']
kw['from_expression'] = query['from_expression'] kw['from_expression'] = query['from_expression']
kw['limit_expression'] = query['limit_expression'] kw['limit_expression'] = query['limit_expression']
kw['select_expression'] = query['select_expression'] kw['select_expression'] = query['select_expression']
......
...@@ -72,7 +72,8 @@ class SQLExpression(object): ...@@ -72,7 +72,8 @@ class SQLExpression(object):
select_dict=None, select_dict=None,
limit=None, limit=None,
from_expression=None, from_expression=None,
can_merge_select_dict=False): can_merge_select_dict=False,
ignoreing_table_alias_list=()):
if DEBUG: if DEBUG:
self.query = query self.query = query
self.table_alias_dict = defaultDict(table_alias_dict) self.table_alias_dict = defaultDict(table_alias_dict)
...@@ -108,6 +109,7 @@ class SQLExpression(object): ...@@ -108,6 +109,7 @@ class SQLExpression(object):
if from_expression is not None: if from_expression is not None:
LOG('SQLExpression', 0, 'Providing a from_expression is deprecated.') LOG('SQLExpression', 0, 'Providing a from_expression is deprecated.')
self.from_expression = from_expression self.from_expression = from_expression
self.ignoreing_table_alias_list = ignoreing_table_alias_list
@profiler_decorator @profiler_decorator
def getTableAliasDict(self): def getTableAliasDict(self):
...@@ -348,9 +350,15 @@ class SQLExpression(object): ...@@ -348,9 +350,15 @@ class SQLExpression(object):
def asSQLExpressionDict(self): def asSQLExpressionDict(self):
table_alias_dict = self.getTableAliasDict() table_alias_dict = self.getTableAliasDict()
from_table_list = [] from_table_list = []
valid_from_table_list = []
# method caching
append = from_table_list.append append = from_table_list.append
append_valid = valid_from_table_list.append
for alias, table in table_alias_dict.iteritems(): for alias, table in table_alias_dict.iteritems():
append((SQL_TABLE_FORMAT % (alias, ), SQL_TABLE_FORMAT % (table, ))) formatted_table_alias = (SQL_TABLE_FORMAT % (alias, ), SQL_TABLE_FORMAT % (table, ))
append(formatted_table_alias)
if alias not in self.ignoreing_table_alias_list:
append_valid(formatted_table_alias)
from_expression_dict = self.getFromExpression() from_expression_dict = self.getFromExpression()
if from_expression_dict is not None: if from_expression_dict is not None:
from_expression = SQL_LIST_SEPARATOR.join( from_expression = SQL_LIST_SEPARATOR.join(
...@@ -362,6 +370,7 @@ class SQLExpression(object): ...@@ -362,6 +370,7 @@ class SQLExpression(object):
'where_expression': self.getWhereExpression(), 'where_expression': self.getWhereExpression(),
'order_by_expression': self.getOrderByExpression(), 'order_by_expression': self.getOrderByExpression(),
'from_table_list': from_table_list, 'from_table_list': from_table_list,
'valid_from_table_list': valid_from_table_list,
'from_expression': from_expression, 'from_expression': from_expression,
'limit_expression': self.getLimitExpression(), 'limit_expression': self.getLimitExpression(),
'select_expression': self.getSelectExpression(), 'select_expression': self.getSelectExpression(),
......
...@@ -32,14 +32,13 @@ from SearchKey import SearchKey ...@@ -32,14 +32,13 @@ from SearchKey import SearchKey
from Products.ZSQLCatalog.Query.Query import Query from Products.ZSQLCatalog.Query.Query import Query
from Products.ZSQLCatalog.Query.RelatedQuery import RelatedQuery from Products.ZSQLCatalog.Query.RelatedQuery import RelatedQuery
from Products.ZSQLCatalog.Query.SQLQuery import SQLQuery from Products.ZSQLCatalog.Query.SQLQuery import SQLQuery
from Products.ZSQLCatalog.Query.SubCatalogQuery import SubCatalogQuery
from Products.ZSQLCatalog.SQLExpression import SQLExpression from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.interfaces.search_key import IRelatedKey from Products.ZSQLCatalog.interfaces.search_key import IRelatedKey
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
from zope.interface import implements from zope.interface import implements
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
BACKWARD_COMPATIBILITY = True
class RelatedKey(SearchKey): class RelatedKey(SearchKey):
""" """
This SearchKey handles searched on virtual columns of RelatedKey type. This SearchKey handles searched on virtual columns of RelatedKey type.
...@@ -108,8 +107,8 @@ class RelatedKey(SearchKey): ...@@ -108,8 +107,8 @@ class RelatedKey(SearchKey):
if isinstance(search_value, Query): if isinstance(search_value, Query):
search_value.setGroup(self.getColumn()) search_value.setGroup(self.getColumn())
join_condition = search_value join_condition = search_value
return RelatedQuery(search_key=self, return SubCatalogQuery(RelatedQuery(search_key=self,
join_condition=join_condition) join_condition=join_condition))
@profiler_decorator @profiler_decorator
def registerColumnMap(self, column_map, table_alias_list=None): def registerColumnMap(self, column_map, table_alias_list=None):
...@@ -126,6 +125,7 @@ class RelatedKey(SearchKey): ...@@ -126,6 +125,7 @@ class RelatedKey(SearchKey):
table_name = self.table_list[table_position] table_name = self.table_list[table_position]
local_group = column_map.registerRelatedKeyColumn(related_column, table_position, group) local_group = column_map.registerRelatedKeyColumn(related_column, table_position, group)
column_map.registerTable(table_name, group=local_group) column_map.registerTable(table_name, group=local_group)
column_map.ignoreTableJoin(table_name, group=local_group)
if table_alias_list is not None: if table_alias_list is not None:
# Pre-resolve all tables with given aliases # Pre-resolve all tables with given aliases
given_name, given_alias = table_alias_list[table_position] given_name, given_alias = table_alias_list[table_position]
...@@ -133,6 +133,7 @@ class RelatedKey(SearchKey): ...@@ -133,6 +133,7 @@ class RelatedKey(SearchKey):
column_map.resolveTable(table_name, given_alias, group=local_group) column_map.resolveTable(table_name, given_alias, group=local_group)
table_name = self.table_list[-1] table_name = self.table_list[-1]
column_map.registerTable(table_name, group=group) column_map.registerTable(table_name, group=group)
column_map.ignoreTableJoin(table_name, group=group)
if table_alias_list is not None: if table_alias_list is not None:
given_name, given_alias = table_alias_list[-1] given_name, given_alias = table_alias_list[-1]
assert table_name == given_name assert table_name == given_name
...@@ -182,36 +183,9 @@ class RelatedKey(SearchKey): ...@@ -182,36 +183,9 @@ class RelatedKey(SearchKey):
query_table=column_map.getCatalogTableAlias(), query_table=column_map.getCatalogTableAlias(),
src__=1, src__=1,
**table_alias_dict) **table_alias_dict)
# Important: return SQLExpression(self,
# Former catalog separated join condition from related query. where_expression=rendered_related_key,
# Example: table_alias_dict=dict(table_alias_list))
# ComplexQuery(Query(title="foo"),
# Query(subordination_title="bar")
# , operator='OR')
# Former catalog rendering (truncated where-expression):
# AND ((catalog.title LIKE '%foo%') OR
# (related_catalog_1.title LIKE '%bar%'))
# AND (related_catalog_1.uid = related_category_0.category_uid AND
# related_category_0.base_category_uid = 873 AND
# related_category_0.uid = catalog.uid)
# As you can see, the part of the query joining the tables is *out* of the
# OR expression, and therefor applies to the entire query.
# This was done on purpose, because doing otherwise gives very poor
# performances (on a simple data set, similar query can take *minutes* to
# execute - as of MySQL 5.x).
# Doing the same way as the former catalog is required for backward
# compatibility, until a decent alternative is found (like spliting the
# "OR" expression into ensemblist operations at query level).
# Note that doing this has a side effect on result list, as objects
# lacking a relation will never appear in the result.
if BACKWARD_COMPATIBILITY:
# XXX: Calling a private-ish method on column_map.
# This should never happen. It should be removed as soon as an
# alternative exists.
column_map._addJoinQuery(SQLQuery(rendered_related_key))
return None
else:
return SQLExpression(self, where_expression=rendered_related_key)
verifyClass(IRelatedKey, RelatedKey) verifyClass(IRelatedKey, RelatedKey)
...@@ -34,6 +34,7 @@ from Products.ZSQLCatalog.SQLCatalog import ComplexQuery ...@@ -34,6 +34,7 @@ from Products.ZSQLCatalog.SQLCatalog import ComplexQuery
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
from Products.ZSQLCatalog.Query.EntireQuery import EntireQuery from Products.ZSQLCatalog.Query.EntireQuery import EntireQuery
from Products.ZSQLCatalog.Query.RelatedQuery import RelatedQuery from Products.ZSQLCatalog.Query.RelatedQuery import RelatedQuery
from Products.ZSQLCatalog.Query.SubCatalogQuery import SubCatalogQuery
from DateTime import DateTime from DateTime import DateTime
class ReferenceQuery: class ReferenceQuery:
...@@ -119,6 +120,8 @@ class ReferenceQuery: ...@@ -119,6 +120,8 @@ class ReferenceQuery:
self.args[0] == other.query self.args[0] == other.query
elif isinstance(other, RelatedQuery): elif isinstance(other, RelatedQuery):
return self == other.join_condition return self == other.join_condition
elif isinstance(other, SubCatalogQuery):
return self == other.query
elif isinstance(other, Query): elif isinstance(other, Query):
return self == other.wrapped_query return self == other.wrapped_query
else: else:
...@@ -138,8 +141,9 @@ class RelatedReferenceQuery: ...@@ -138,8 +141,9 @@ class RelatedReferenceQuery:
self.subquery = reference_subquery self.subquery = reference_subquery
def __eq__(self, other): def __eq__(self, other):
return isinstance(other, RelatedQuery) and \ return isinstance(other, SubCatalogQuery) and \
self.subquery == other.join_condition isinstance(other.query, RelatedQuery) and \
self.subquery == other.query.join_condition
def __repr__(self): def __repr__(self):
return '<%s %r>' % (self.__class__.__name__, self.subquery) return '<%s %r>' % (self.__class__.__name__, self.subquery)
......
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