Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
francois
erp5
Commits
9cdf7a81
Commit
9cdf7a81
authored
Dec 14, 2011
by
Leonardo Rochael Almeida
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'catalog_join'
parents
07cbd374
d2a9088a
Changes
11
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
904 additions
and
115 deletions
+904
-115
product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_related_grand_parent.xml
...rtal_catalog/erp5_mysql_innodb/z_related_grand_parent.xml
+7
-6
product/ERP5Catalog/tests/testERP5Catalog.py
product/ERP5Catalog/tests/testERP5Catalog.py
+194
-13
product/ERP5Type/Core/Predicate.py
product/ERP5Type/Core/Predicate.py
+3
-0
product/ZSQLCatalog/ColumnMap.py
product/ZSQLCatalog/ColumnMap.py
+217
-22
product/ZSQLCatalog/Query/EntireQuery.py
product/ZSQLCatalog/Query/EntireQuery.py
+65
-24
product/ZSQLCatalog/SQLCatalog.py
product/ZSQLCatalog/SQLCatalog.py
+10
-0
product/ZSQLCatalog/SQLExpression.py
product/ZSQLCatalog/SQLExpression.py
+16
-11
product/ZSQLCatalog/SearchKey/RelatedKey.py
product/ZSQLCatalog/SearchKey/RelatedKey.py
+107
-23
product/ZSQLCatalog/TableDefinition.py
product/ZSQLCatalog/TableDefinition.py
+283
-0
product/ZSQLCatalog/interfaces/column_map.py
product/ZSQLCatalog/interfaces/column_map.py
+0
-15
product/ZSQLCatalog/tests/testSQLCatalog.py
product/ZSQLCatalog/tests/testSQLCatalog.py
+2
-1
No files found.
product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_related_grand_parent.xml
View file @
9cdf7a81
...
@@ -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>
...
...
product/ERP5Catalog/tests/testERP5Catalog.py
View file @
9cdf7a81
This diff is collapsed.
Click to expand it.
product/ERP5Type/Core/Predicate.py
View file @
9cdf7a81
...
@@ -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"
...
...
product/ZSQLCatalog/ColumnMap.py
View file @
9cdf7a81
This diff is collapsed.
Click to expand it.
product/ZSQLCatalog/Query/EntireQuery.py
View file @
9cdf7a81
...
@@ -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
,
...
...
product/ZSQLCatalog/SQLCatalog.py
View file @
9cdf7a81
...
@@ -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
...
...
product/ZSQLCatalog/SQLExpression.py
View file @
9cdf7a81
...
@@ -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
(),
...
...
product/ZSQLCatalog/SearchKey/RelatedKey.py
View file @
9cdf7a81
...
@@ -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
=
"
\
n
AND -- 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 searche
d
on virtual columns of RelatedKey type.
This SearchKey handles searche
s
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
\
n
Forcing 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
\
n
Adding 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
)
product/ZSQLCatalog/TableDefinition.py
0 → 100644
View file @
9cdf7a81
This diff is collapsed.
Click to expand it.
product/ZSQLCatalog/interfaces/column_map.py
View file @
9cdf7a81
...
@@ -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.
"""
product/ZSQLCatalog/tests/testSQLCatalog.py
View file @
9cdf7a81
...
@@ -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
# Dot
t
ed 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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment