EntireQuery.py 7.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
##############################################################################
#
# 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>
#                    Vincent Pelletier <vincent@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 Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.ColumnMap import ColumnMap
from zLOG import LOG
34
from Products.ZSQLCatalog.interfaces.entire_query import IEntireQuery
35 36
from zope.interface.verify import verifyClass
from zope.interface import implements
37 38
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator

39 40 41 42 43 44
def defaultDict(value):
  if value is None:
    return {}
  assert isinstance(value, dict)
  return value

45 46 47 48 49 50 51
class EntireQuery(object):
  """
    This is not a Query subclass, since it does not define a
    registerColumnMap method, and instead does the ColumnMap handling
    internaly.
  """

52
  implements(IEntireQuery)
53 54 55 56

  column_map = None

  @profiler_decorator
57
  def __init__(self, query, order_by_list=(), group_by_list=(),
58
               select_dict=None, limit=None, catalog_table_name=None,
59
               extra_column_list=(), from_expression=None,
60 61
               order_by_override_list=None):
    self.query = query
62 63 64
    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)
65 66 67
    self.select_dict = defaultDict(select_dict)
    self.limit = limit
    self.catalog_table_name = catalog_table_name
68
    self.extra_column_list = list(extra_column_list)
69 70 71
    self.from_expression = from_expression

  def asSearchTextExpression(self, sql_catalog):
72
    return self.query.asSearchTextExpression(sql_catalog)
73 74 75 76 77

  @profiler_decorator
  def asSQLExpression(self, sql_catalog, only_group_columns):
    column_map = self.column_map
    if column_map is None:
78 79
      # XXX: should we provide a way to register column map as a separate 
      # method or do it here ?
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
      # Column Map was not built yet, do it.
      self.column_map = column_map = ColumnMap(catalog_table_name=self.catalog_table_name)
      for extra_column in self.extra_column_list:
        table, column = extra_column.replace('`', '').split('.')
        if table != self.catalog_table_name:
          raise ValueError, 'Extra columns must be catalog columns. %r does not follow this rule (catalog=%r, extra_column_list=%r)' % (extra_column, self.catalog_table_name, self.extra_column_list)
        column_map.registerColumn(extra_column)
      for column in self.group_by_list:
        column_map.registerColumn(column)
      for alias, column in self.select_dict.iteritems():
        if column is None:
          column = alias
        else:
          column_map.ignoreColumn(alias)
        column_map.registerColumn(column)
      for override in self.order_by_override_set:
        column_map.ignoreColumn(override)
      for order_by in self.order_by_list:
        assert isinstance(order_by, (tuple, list))
        assert len(order_by)
        column_map.registerColumn(order_by[0])
      self.query.registerColumnMap(sql_catalog, column_map)
      column_map.build(sql_catalog)
      # Replace given group_by_list entries by their mapped representations.
      new_column_list = []
      append = new_column_list.append
      for column in self.group_by_list:
        try:
          append(column_map.asSQLColumn(column))
        except KeyError:
          LOG('EntireQuery', 100, 'Group-by column %r could not be mapped, but is passed through. This use is strongly discouraged.' % (column, ))
          append(column)
      self.group_by_list = new_column_list
      # Build a dictionnary from select_dict aliasing their mapped representations
      self.final_select_dict = select_dict = {}
      for alias, raw_column in self.select_dict.iteritems():
        if raw_column is None:
          column = alias
        else:
          column = raw_column
        try:
          rendered = column_map.asSQLColumn(column)
        except KeyError:
          LOG('EntireQuery', 100, 'Select column %r could not be mapped, but is passed through. This use is strongly discouraged.' % (column, ))
          rendered = column
        select_dict[alias] = rendered
      # Replace given order_by_list entries by their mapped representations.
      new_order_by_list = []
      append = new_order_by_list.append
      for order_by in self.order_by_list:
        column = order_by[0]
        if column in self.order_by_override_set:
          LOG('EntireQuery', 100, 'Order-by column %r is forcibly accepted. This use is strongly discouraged.' % (column, ))
          rendered = column
        else:
          try:
            rendered = column_map.asSQLColumn(column)
          except KeyError:
            LOG('SQLCatalog', 100, 'Order by %r ignored: it could not be mapped to a known column.' % (order_by, ))
            rendered = None
        if rendered is not None:
          if len(order_by) > 1:
            if len(order_by) > 2 and order_by[2] not in (None, ''):
              rendered = 'CAST(%s AS %s)' % (rendered, order_by[2])
            rendered = '%s %s' % (rendered, order_by[1])
          append(rendered)
      self.order_by_list = new_order_by_list
      # generate SQLExpression from query
      sql_expression_list = [self.query.asSQLExpression(sql_catalog, column_map, only_group_columns)]
      # generate join expression based on column_map.getJoinTableAliasList
      append = sql_expression_list.append
      for join_query in column_map.iterJoinQueryList():
        append(join_query.asSQLExpression(sql_catalog, column_map, only_group_columns))
      join_table_list = column_map.getJoinTableAliasList()
      if len(join_table_list):
        # XXX: Is there any special rule to observe when joining tables ?
        # Maybe we could check which column is a primary key instead of
        # hardcoding "uid".
        where_pattern = '`%s`.`uid` = `%%s`.`uid`' % \
          (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
        )))
      self.sql_expression_list = sql_expression_list
    return SQLExpression(
      self,
      table_alias_dict=column_map.getTableAliasDict(),
      from_expression=self.from_expression,
      order_by_list=self.order_by_list,
      group_by_list=self.group_by_list,
      select_dict=self.final_select_dict,
      limit=self.limit,
      where_expression_operator='and',
177
      sql_expression_list=self.sql_expression_list)
178 179 180

verifyClass(IEntireQuery, EntireQuery)