Commit 9cdf7a81 authored by Leonardo Rochael Almeida's avatar Leonardo Rochael Almeida

Merge branch 'catalog_join'

parents 07cbd374 d2a9088a
...@@ -14,8 +14,10 @@ ...@@ -14,8 +14,10 @@
</item> </item>
<item> <item>
<key> <string>arguments_src</string> </key> <key> <string>arguments_src</string> </key>
<value> <string>table_0\n <value> <string>table_0\r\n
table_1</string> </value> table_1\r\n
RELATED_QUERY_SEPARATOR=" AND "\r\n
query_table="catalog"</string> </value>
</item> </item>
<item> <item>
<key> <string>cache_time_</string> </key> <key> <string>cache_time_</string> </key>
...@@ -55,10 +57,9 @@ table_1</string> </value> ...@@ -55,10 +57,9 @@ table_1</string> </value>
<key> <string>src</string> </key> <key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
<dtml-var table_0>.uid = catalog.parent_uid\n <dtml-var table_1>.uid = <dtml-var table_0>.parent_uid\n
AND <dtml-var table_1>.uid = <dtml-var table_0>.parent_uid\n <dtml-var RELATED_QUERY_SEPARATOR>\n
\n <dtml-var table_0>.uid = <dtml-var query_table>.parent_uid
]]></string> </value> ]]></string> </value>
</item> </item>
......
...@@ -329,7 +329,10 @@ class Predicate(XMLObject): ...@@ -329,7 +329,10 @@ class Predicate(XMLObject):
catalog_kw['where_expression'] = SQLQuery(sql_text) catalog_kw['where_expression'] = SQLQuery(sql_text)
else: else:
catalog_kw['where_expression'] = '' catalog_kw['where_expression'] = ''
# force implicit join
catalog_kw['implicit_join'] = True
sql_query = portal_catalog.buildSQLQuery(**catalog_kw) sql_query = portal_catalog.buildSQLQuery(**catalog_kw)
# XXX from_table_list is None most of the time after the explicit_join work
for alias, table in sql_query['from_table_list']: for alias, table in sql_query['from_table_list']:
if from_table_dict.has_key(alias): if from_table_dict.has_key(alias):
raise KeyError, "The same table is used twice for an identity criterion and for a membership criterion" raise KeyError, "The same table is used twice for an identity criterion and for a membership criterion"
......
This diff is collapsed.
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
# #
############################################################################## ##############################################################################
import warnings
from Products.ZSQLCatalog.SQLExpression import SQLExpression from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.ColumnMap import ColumnMap from Products.ZSQLCatalog.ColumnMap import ColumnMap
from zLOG import LOG from zLOG import LOG
...@@ -35,6 +36,7 @@ from Products.ZSQLCatalog.interfaces.entire_query import IEntireQuery ...@@ -35,6 +36,7 @@ from Products.ZSQLCatalog.interfaces.entire_query import IEntireQuery
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
from Products.ZSQLCatalog.TableDefinition import LegacyTableDefinition
def defaultDict(value): def defaultDict(value):
if value is None: if value is None:
...@@ -54,19 +56,28 @@ class EntireQuery(object): ...@@ -54,19 +56,28 @@ class EntireQuery(object):
column_map = None column_map = None
@profiler_decorator @profiler_decorator
def __init__(self, query, order_by_list=(), group_by_list=(), def __init__(self, query,
select_dict=None, limit=None, catalog_table_name=None, order_by_list=(),
extra_column_list=(), from_expression=None, group_by_list=(),
order_by_override_list=None): select_dict=None,
left_join_list=(),
limit=None,
catalog_table_name=None,
extra_column_list=(),
from_expression=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)
self.group_by_list = list(group_by_list) self.group_by_list = list(group_by_list)
self.select_dict = defaultDict(select_dict) self.select_dict = defaultDict(select_dict)
self.left_join_list = left_join_list
self.limit = limit self.limit = limit
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)
...@@ -78,7 +89,12 @@ class EntireQuery(object): ...@@ -78,7 +89,12 @@ class EntireQuery(object):
# XXX: should we provide a way to register column map as a separate # XXX: should we provide a way to register column map as a separate
# method or do it here ? # method or do it here ?
# Column Map was not built yet, do it. # Column Map was not built yet, do it.
self.column_map = 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,
left_join_list=self.left_join_list,
implicit_join=self.implicit_join,
)
self.column_map = column_map
for extra_column in self.extra_column_list: for extra_column in self.extra_column_list:
table, column = extra_column.replace('`', '').split('.') table, column = extra_column.replace('`', '').split('.')
if table != self.catalog_table_name: if table != self.catalog_table_name:
...@@ -145,30 +161,55 @@ class EntireQuery(object): ...@@ -145,30 +161,55 @@ class EntireQuery(object):
None, ) * (3 - len(order_by))) None, ) * (3 - len(order_by)))
self.order_by_list = new_order_by_list self.order_by_list = new_order_by_list
# generate SQLExpression from query # generate SQLExpression from query
sql_expression_list = [self.query.asSQLExpression(sql_catalog, column_map, only_group_columns)] sql_expression_list = [self.query.asSQLExpression(sql_catalog,
# generate join expression based on column_map.getJoinTableAliasList column_map,
only_group_columns)]
append = sql_expression_list.append append = sql_expression_list.append
for join_query in column_map.iterJoinQueryList(): for join_query in column_map.iterJoinQueryList():
append(join_query.asSQLExpression(sql_catalog, column_map, only_group_columns)) append(join_query.asSQLExpression(sql_catalog,
join_table_list = column_map.getJoinTableAliasList() column_map,
if len(join_table_list): only_group_columns))
# XXX: Is there any special rule to observe when joining tables ? # generate join expression based on column_map.getJoinTableAliasList
# Maybe we could check which column is a primary key instead of # XXX: This is now done by ColumnMap to its table_definition,
# hardcoding "uid". # during build()
where_pattern = '`%s`.`uid` = `%%s`.`uid`' % \ #
(column_map.getCatalogTableAlias(), ) # join_table_list = column_map.getJoinTableAliasList()
# XXX: It would cleaner from completeness point of view to use column # if len(join_table_list):
# mapper to render column, but makes code much more complex to just do # # XXX: Is there any special rule to observe when joining tables ?
# a simple text rendering. If there is any reason why we should have # # Maybe we could check which column is a primary key instead of
# those column in the mapper, then we should use the clean way. # # hardcoding "uid".
append(SQLExpression(self, where_expression=' AND '.join( # where_pattern = '`%s`.`uid` = `%%s`.`uid`' % \
where_pattern % (x, ) for x in join_table_list # (column_map.getCatalogTableAlias(), )
))) # # XXX: It would cleaner from completeness point of view to use column
# # mapper to render column, but makes code much more complex to just do
# # a simple text rendering. If there is any reason why we should have
# # those column in the mapper, then we should use the clean way.
# append(SQLExpression(self, where_expression=' AND '.join(
# where_pattern % (x, ) for x in join_table_list
# )))
# BBB self.from_expression forces use of implicit inner join
table_alias_dict = column_map.getTableAliasDict()
if self.from_expression:
warnings.warn("Providing a 'from_expression' is deprecated.",
DeprecationWarning)
# XXX: perhaps move this code to ColumnMap?
legacy_from_expression = self.from_expression
from_expression = LegacyTableDefinition(legacy_from_expression,
table_alias_dict)
table_alias_dict = None
else:
from_expression = column_map.getTableDefinition()
assert ((from_expression is None) !=
(table_alias_dict is None)), ("Got both a from_expression "
"and a table_alias_dict")
self.sql_expression_list = sql_expression_list self.sql_expression_list = sql_expression_list
# TODO: wrap the table_alias_dict above into a TableDefinition as well,
# even without a legacy_table_definition.
return SQLExpression( return SQLExpression(
self, self,
table_alias_dict=column_map.getTableAliasDict(), table_alias_dict=table_alias_dict,
from_expression=self.from_expression, from_expression=from_expression,
order_by_list=self.order_by_list, order_by_list=self.order_by_list,
group_by_list=self.group_by_list, group_by_list=self.group_by_list,
select_dict=self.final_select_dict, select_dict=self.final_select_dict,
......
...@@ -2291,6 +2291,13 @@ class Catalog(Folder, ...@@ -2291,6 +2291,13 @@ class Catalog(Folder,
select_dict = None select_dict = None
elif isinstance(select_dict, (list, tuple)): elif isinstance(select_dict, (list, tuple)):
select_dict = dict([(x, None) for x in select_dict]) select_dict = dict([(x, None) for x in select_dict])
# Handle left_join_list
left_join_list = kw.pop('left_join_list', ())
# Handle implicit_join. It's True by default, as there's a lot of code
# in BT5s and elsewhere that calls buildSQLQuery() expecting implicit
# join. self._queryResults() defaults it to False for those using
# catalog.searchResults(...) or catalog(...) directly.
implicit_join = kw.pop('implicit_join', True)
# 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)
...@@ -2328,6 +2335,8 @@ class Catalog(Folder, ...@@ -2328,6 +2335,8 @@ class Catalog(Folder,
order_by_override_list=order_by_override_list, order_by_override_list=order_by_override_list,
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,
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,
...@@ -2413,6 +2422,7 @@ class Catalog(Folder, ...@@ -2413,6 +2422,7 @@ class Catalog(Folder,
""" Returns a list of brains from a set of constraints on variables """ """ Returns a list of brains from a set of constraints on variables """
if build_sql_query_method is None: if build_sql_query_method is None:
build_sql_query_method = self.buildSQLQuery build_sql_query_method = self.buildSQLQuery
kw.setdefault('implicit_join', False)
query = build_sql_query_method(REQUEST=REQUEST, **kw) query = build_sql_query_method(REQUEST=REQUEST, **kw)
# XXX: decide if this should be made normal # XXX: decide if this should be made normal
ENFORCE_SEPARATION = True ENFORCE_SEPARATION = True
......
...@@ -166,7 +166,7 @@ class SQLExpression(object): ...@@ -166,7 +166,7 @@ class SQLExpression(object):
@profiler_decorator @profiler_decorator
def getFromExpression(self): def getFromExpression(self):
""" """
Returns a string. Returns a TableDefinition stored in one of the from_expressions or None
If there are nested SQLExpression, it checks that they either don't If there are nested SQLExpression, it checks that they either don't
define any from_expression or the exact same from_expression. Otherwise, define any from_expression or the exact same from_expression. Otherwise,
...@@ -175,7 +175,7 @@ class SQLExpression(object): ...@@ -175,7 +175,7 @@ class SQLExpression(object):
result = self.from_expression result = self.from_expression
for sql_expression in self.sql_expression_list: for sql_expression in self.sql_expression_list:
from_expression = sql_expression.getFromExpression() from_expression = sql_expression.getFromExpression()
if None not in (result, from_expression): if from_expression not in (result, None):
message = 'I don\'t know how to merge from_expressions' message = 'I don\'t know how to merge from_expressions'
if DEBUG: if DEBUG:
message = message + '. I was created by %r, and I am working on %r (%r) out of [%s]' % ( message = message + '. I was created by %r, and I am working on %r (%r) out of [%s]' % (
...@@ -385,20 +385,25 @@ class SQLExpression(object): ...@@ -385,20 +385,25 @@ class SQLExpression(object):
SQL_SELECT_ALIAS_FORMAT % (column, alias) SQL_SELECT_ALIAS_FORMAT % (column, alias)
for alias, column in self.getSelectDict().iteritems()) for alias, column in self.getSelectDict().iteritems())
@profiler_decorator def getFromTableList(self):
def asSQLExpressionDict(self):
table_alias_dict = self.getTableAliasDict() table_alias_dict = self.getTableAliasDict()
if not table_alias_dict:
return None
from_table_list = [] from_table_list = []
append = from_table_list.append append = 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, ))) append((SQL_TABLE_FORMAT % (alias, ), SQL_TABLE_FORMAT % (table, )))
from_expression_dict = self.getFromExpression() return from_table_list
if from_expression_dict is not None:
from_expression = SQL_LIST_SEPARATOR.join( @profiler_decorator
from_expression_dict.get(alias, '`%s` AS `%s`' % (table, alias)) def asSQLExpressionDict(self):
for alias, table in table_alias_dict.iteritems()) from_expression = self.getFromExpression()
else: from_table_list = self.getFromTableList()
from_expression = None assert None in (from_expression,
from_table_list), ("Cannot return both a from_expression "
"and a from_table_list")
if from_expression is not None:
from_expression = from_expression.render()
return { return {
'where_expression': self.getWhereExpression(), 'where_expression': self.getWhereExpression(),
'order_by_expression': self.getOrderByExpression(), 'order_by_expression': self.getOrderByExpression(),
......
...@@ -37,12 +37,29 @@ from Products.ZSQLCatalog.interfaces.search_key import IRelatedKey ...@@ -37,12 +37,29 @@ 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
from Products.ZSQLCatalog.TableDefinition import TableAlias, InnerJoin, LeftJoin
from logging import getLogger
log = getLogger(__name__)
BACKWARD_COMPATIBILITY = True BACKWARD_COMPATIBILITY = True
RELATED_QUERY_SEPARATOR = "\nAND -- related query separator\n"
RELATED_KEY_MISMATCH_MESSAGE = "\
A rendered related key must contain the same number of querying \
conditions as the tables it relates, properly separated by \
RELATED_QUERY_SEPARATOR. \n\
Offending related key: %r, for column %r, table_alias_list: %r, \
rendered_related_key: \n%s"
RELATED_KEY_ALIASED_MESSAGE = "\
Support for explicit joins of aliased related keys is not yet implemented. \
Offending related key: %r, for column %r, table_alias_list: %r"
class RelatedKey(SearchKey): class RelatedKey(SearchKey):
""" """
This SearchKey handles searched on virtual columns of RelatedKey type. This SearchKey handles searches on virtual columns of RelatedKey type.
It generates joins required by the virtual column to reach the actual It generates joins required by the virtual column to reach the actual
column to compare, plus a regular query on that column if needed. column to compare, plus a regular query on that column if needed.
""" """
...@@ -115,13 +132,23 @@ class RelatedKey(SearchKey): ...@@ -115,13 +132,23 @@ class RelatedKey(SearchKey):
def registerColumnMap(self, column_map, table_alias_list=None): def registerColumnMap(self, column_map, table_alias_list=None):
related_column = self.getColumn() related_column = self.getColumn()
group = column_map.registerRelatedKey(related_column, self.real_column) group = column_map.registerRelatedKey(related_column, self.real_column)
# Each table except last one must be registered to their own group, so that # Each table except last one must be registered to their own
# the same table can be used multiple time (and aliased multiple times) # group, so that the same table can be used multiple times (and
# in the same related key. The last one must be register to related key # aliased multiple times) in the same related key. The last one
# "main" group (ie, the value of the "group" variable) to be the same as # must be registered to the related key "main" group (ie, the
# the ta ble used in join_condition. # value of the "group" variable) to be the same as the table used
# in join_condition.
if table_alias_list is not None: if table_alias_list is not None:
assert len(self.table_list) == len(table_alias_list) assert len(self.table_list) == len(table_alias_list)
# XXX-Leo: remove the rest of this 'if' branch after making sure
# that ColumnMap.addRelatedKeyJoin() can handle collapsing
# chains of inner-joins that are subsets of one another based on
# having the same aliases:
msg = RELATED_KEY_ALIASED_MESSAGE % (self.related_key_id,
self.column,
table_alias_list,)
log.warning(msg + "\n\nForcing implicit join...")
column_map.implicit_join = True
for table_position in xrange(len(self.table_list) - 1): for table_position in xrange(len(self.table_list) - 1):
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)
...@@ -145,6 +172,23 @@ class RelatedKey(SearchKey): ...@@ -145,6 +172,23 @@ class RelatedKey(SearchKey):
column_map.registerCatalog() column_map.registerCatalog()
return group return group
def stitchJoinDefinition(self, table_alias_list, join_query_list, column_map):
alias, table = table_alias_list[-1]
right = column_map.makeTableAliasDefinition(table, alias)
if not join_query_list:
# nothing to do, just return the table alias
assert len(table_alias_list) == 1
return right
else:
# create an InnerJoin of the last element of the alias list with
# a chain of InnerJoins of the rest of the list conditioned on
# the the last element of the join_query_list
left = self.stitchJoinDefinition(table_alias_list[:-1],
join_query_list[:-1],
column_map)
condition = join_query_list[-1]
return InnerJoin(left, right, condition)
@profiler_decorator @profiler_decorator
def buildSQLExpression(self, sql_catalog, column_map, only_group_columns, group): def buildSQLExpression(self, sql_catalog, column_map, only_group_columns, group):
""" """
...@@ -170,20 +214,26 @@ class RelatedKey(SearchKey): ...@@ -170,20 +214,26 @@ class RelatedKey(SearchKey):
table_alias_list = [(getTableAlias(related_table, group=getRelatedKeyGroup(index, group)), related_table) table_alias_list = [(getTableAlias(related_table, group=getRelatedKeyGroup(index, group)), related_table)
for (index, related_table) in enumerate(related_table_list)] for (index, related_table) in enumerate(related_table_list)]
# table alias for destination table # table alias for destination table
table_alias_list.append((getTableAlias(destination_table, group=group), destination_table)) table_alias_list.append((getTableAlias(destination_table, group=group),
destination_table))
# map aliases to use in ZSQLMethod. # map aliases to use in ZSQLMethod.
table_alias_dict = dict(('table_%s' % (index, ), table_alias[0]) table_alias_dict = dict(('table_%s' % (index, ), table_alias)
for (index, table_alias) in enumerate(table_alias_list)) for (index, (table_alias, table_name))
in enumerate(table_alias_list))
assert len(table_alias_list) == len(table_alias_dict) assert len(table_alias_list) == len(table_alias_dict)
query_table=column_map.getCatalogTableAlias()
rendered_related_key = related_key( rendered_related_key = related_key(
query_table=column_map.getCatalogTableAlias(), query_table=query_table,
RELATED_QUERY_SEPARATOR=RELATED_QUERY_SEPARATOR,
src__=1, src__=1,
**table_alias_dict) **table_alias_dict)
join_condition_list = rendered_related_key.split(RELATED_QUERY_SEPARATOR)
# Important: # Important:
# Former catalog separated join condition from related query. # Previously the catalog separated join condition from the related query.
# Example: # Example:
# ComplexQuery(Query(title="foo"), # ComplexQuery(Query(title="foo"),
# Query(subordination_title="bar") # Query(subordination_title="bar")
...@@ -199,19 +249,53 @@ class RelatedKey(SearchKey): ...@@ -199,19 +249,53 @@ class RelatedKey(SearchKey):
# This was done on purpose, because doing otherwise gives very poor # This was done on purpose, because doing otherwise gives very poor
# performances (on a simple data set, similar query can take *minutes* to # performances (on a simple data set, similar query can take *minutes* to
# execute - as of MySQL 5.x). # 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 # Because of this, we never return an SQLExpression here, as it
# "OR" expression into ensemblist operations at query level). # would mix join definition with column condition in the body of
# Note that doing this has a side effect on result list, as objects # the WHERE clause. Instead we explicitly define a Join to the
# lacking a relation will never appear in the result. # catalog. The ColumnMap defines whether this is an Inner Join or
# a Left Outer Join. Notice that if an Inner Join is decided,
# objects lacking a relationship will never appear in the result.
if len(join_condition_list) == len(table_alias_list):
# Good! we got a compatible method that splits the join
# conditions according to the related tables.
#
# Add a join on this related key, based on the chain of
# inner-joins of the related key tables.
query_table_join_condition = join_condition_list.pop()
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)
else:
# Method did not render the related key condition with the
# appropriate separators so we could split it
# XXX: Can we try to parse rendered_related_key to select which
# conditions go with each table? Maybe we could still use
# explicit joins this way...
msg = RELATED_KEY_MISMATCH_MESSAGE % (self.related_key_id,
self.column,
table_alias_list,
rendered_related_key)
if BACKWARD_COMPATIBILITY: if BACKWARD_COMPATIBILITY:
# XXX: Calling a private-ish method on column_map. # BBB: remove this branch of the condition, and the above
# This should never happen. It should be removed as soon as an # constant, when all zsql_methods have been adapted to return
# alternative exists. # the join queries properly separated by the
column_map._addJoinQuery(SQLQuery(rendered_related_key)) # RELATED_QUERY_SEPARATOR.
return None
# The rendered related key doesn't have the separators for each
# joined table, so we revert to doing implicit inner joins:
log.warning(msg + "\n\nAdding an Implicit Join Condition...")
column_map._addJoinQueryForColumn(self.column,
SQLQuery(rendered_related_key))
else: else:
return SQLExpression(self, where_expression=rendered_related_key) raise RuntimeError(msg)
return None
verifyClass(IRelatedKey, RelatedKey) verifyClass(IRelatedKey, RelatedKey)
This diff is collapsed.
...@@ -284,18 +284,3 @@ class IColumnMap(Interface): ...@@ -284,18 +284,3 @@ class IColumnMap(Interface):
Return a copy of the table alias list for tables requiring a join with Return a copy of the table alias list for tables requiring a join with
catalog table. 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.
"""
...@@ -202,6 +202,7 @@ class DummyCatalog(SQLCatalog): ...@@ -202,6 +202,7 @@ class DummyCatalog(SQLCatalog):
assert 'query_table' in kw assert 'query_table' in kw
assert 'table_0' in kw assert 'table_0' in kw
assert 'table_1' in kw assert 'table_1' in kw
assert 'AND' in kw.pop('RELATED_QUERY_SEPARATOR')
assert len(kw) == 4 assert len(kw) == 4
return '%(table_0)s.uid = %(query_table)s.uid AND %(table_0)s.other_uid = %(table_1)s' % kw return '%(table_0)s.uid = %(query_table)s.uid AND %(table_0)s.other_uid = %(table_1)s' % kw
...@@ -629,7 +630,7 @@ class TestSQLCatalog(unittest.TestCase): ...@@ -629,7 +630,7 @@ class TestSQLCatalog(unittest.TestCase):
select_dict = sql_expression.getSelectDict() select_dict = sql_expression.getSelectDict()
self.assertTrue('ambiguous_mapping' in select_dict, select_dict) self.assertTrue('ambiguous_mapping' in select_dict, select_dict)
self.assertTrue('bar' in select_dict['ambiguous_mapping'], select_dict['ambiguous_mapping']) self.assertTrue('bar' in select_dict['ambiguous_mapping'], select_dict['ambiguous_mapping'])
# Doted alias: table name must get stripped. This is required to have an # Dotted alias: table name must get stripped. This is required to have an
# upgrade path from old ZSQLCatalog versions where pre-mapped columns were # upgrade path from old ZSQLCatalog versions where pre-mapped columns were
# used in their select_expression. This must only happen in the # used in their select_expression. This must only happen in the
# "{column: None}" form, as otherwise it's the user explicitely asking for # "{column: None}" form, as otherwise it's the user explicitely asking for
......
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