column_map.py 10.7 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
##############################################################################
#
# Copyright (c) 2002-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.
#
##############################################################################

30
from zope.interface import Interface
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

class IColumnMap(Interface):
  """
    The role of the column mapper is to make possible to have a "flat"
    overview of all columns used in a query, to choose how they will be
    mapped to catalog tables, and how those tables will be aliased in the SQL
    rendering of that query.

    Typical usage:
    - Instanciate (with or without a catalog table)
    - Register all columns the query will use
    - Build the column map
    - Fetch SQL representation of each registered column.
    - Fetch table alias mapping
    - Fetch the list of table aliases which require a join with catalog
    - Fetch the list of queries implied by columns. This happens when there
      are virtual columns registered which were not expanded in the query
      already. In that case, column map generate queries required to reach
      the real column behind the virtual one.

    Note that, although it's not enforced, it is meaningless to:
    - call build more than once
    - register columns after build happened
    - fetch SQL representation before build happened (this will most probably
      lead to KeyErrors anyway)

    Groups.
      All references to a given table withing a given group will cause all
      columns referencing that table to be resolved to the same table alias
      in resulting query.
      Ex:
        Note: instead of resolving columns by hand, it is also possible to
        call "build", but this way it makes .
        Registration:
          registerColumn('bar')
          registerColumn('baz')
          resolveColumn('bar', 'foo')
          resolveColumn('baz', 'foo')
        Result:
          asSQLColumn('bar') -> 'foo_alias.bar'
          asSQLColumn('baz') -> 'foo_alias.baz'
      Complementary, any reference to a given table from one group to
      another will cause columns to be resolved to distinct table aliases
      in resulting query.
      Ex:
        Registration:
          registerColumn('bar')
          registerColumn('baz', group='hoge')
          resolveColumn('bar', 'foo')
          resolveColumn('baz', 'foo', group='hoge')
        Result:
          asSQLColumn('bar')               -> 'foo_alias.bar'
          asSQLColumn('baz', group='hoge') -> 'other_foo_alias.baz'
  """

  def __init__(catalog_table_name=None):
    """
      Create a column map.

      Initialises internal data structures and set the table name to use as
      catalog table.
    """

  def registerColumn(column, group=None, simple_query=None):
    """
      Register a column in given group on this column map.

      column (string)
        Contains the name of the column to register.

        This name might contain a dot, in which case a table name is extracted
        from it and that column is resolved to that table within its group.
        Note that this practice is discouraged, but not deprecated.
      group (string)
        Group id of given column.
      simple_query (SimpleQuery)
        The SimpleQuery doing a comparison on given column. This can be
        accessed from vote script at ColumnMap build time.
    """

  def ignoreColumn(column):
    """
      Act as if given column was valid and its mapping succeeded.
      ie: supresses all warnings relative to that column, and all mapping
      attemps.
      It is supposed to be used when given value is an SQL alias for a
      separately registered column.

      column (string)
        The value to ignore.
    """

  def registerRelatedKey(related_column, column):
    """
      Register a "virtual column".
      This method is to be called when registering any virtual column.
      A virtual column makes use internaly of multiple catalog columns.
      A related key is an example of a virtual column.

      This method internaly generates a new group id that caller must pass to
      registerRelatedKeyColumn when registering those internal (and real)
      columns. This is because the same virtual column can be used more than
      once in a query without interferences from other uses, and hence must be
      put in separate groups, without having to know that there are other uses
      for the same virtual column.

      virtual_column
        Name of the virtual column to register.
      column
        Name of the "last" real column behind the virtual column. Comparisons
        done on the virtual column will happen on that real column instead.
        For example, it's "title" for "source_title" related key.
    """

  def registerCatalog():
    """
      Register catalog table as being used in default group.

      This is for backward compatibility with text-only related keys, since
      they often hardcode catalog table name.
    """

  def registerRelatedKeyColumn(related_column, position, group):
    """
      Register given column as part of already registered virtual column.

      related_column (string)
        Name of the virtual column registered column is a part of.
      position (int)
        Unique id of this column in the list of all columns used by the
        virtual column it is a part of (it might use the same column name
        while expecting uses to be mapped to different aliases or evne
        different tables).
        Typically, this is table position in the parameter list the related
        key expects.
      group (string)
        Group id as returned by registerRelatedKey of given virtual column.
    """

  def getRelatedKeyGroup(position, group):
    """
      For given virtual key position and group, return a group.

      This is here so that all group generation code is inside ColumnMap
      class.
    """

  def registerTable(table_name, alias=None, group=None):
    """
      Register given table name as being used in given group.

      This method should not be called outside of this class except for
      backward compatibility purposes.

      It is implicitely done most of the time, and should only be used only
      when there is no control over chosen table alias or when table
      registration cannot be done by a Query.
      There are 2 cases where it cas required:
      - pre-mapped columns which won't be actually used, but which force table
        alias choice (see EntireQuery).
      - related keys, where the Related Query cannot register columns it uses
        by itself (since its content is not based on Queries but on raw SQL
        text).
    """

  def build(sql_catalog):
    """
      Resolve all unresolved registered columns (ie, not mapped to tables).
      Resolve all used tables to unique aliases.

      Chosen table aliases are based on table name and virtual column name the
      column comes from, if applicable. This is done to make final SQL more
      reader-friendly.
    """

  def asSQLColumn(column, group=None):
    """
      Return an SQL rendering of given column, with the table alias it has
      been mapped to.

      column (string)
        Column name. Can be the name of a virtual column.

        This name might contain a dot, in which case a table name is extracted
        from it and that column is resolved to that table within its group.
        Note that this practice is discouraged, but not deprecated.
        Note also that it does not apply to virtual columns (they cannot
        contain a dot).
      group (string)
        Group id of given column.
    """

  def getCatalogTableAlias(group=None):
    """
      Return the alias of catalog table name given at instantiation.
    """

  def getTableAliasDict():
    """
      Return a copy of table mapping.

      returned value (dict)
        key (string)
          Table alias.
        value (string)
          Table name.
    """

  def resolveColumn(column, table_name, group=None):
    """
      Map given column to given table within given group.

      column (string)
        Name of the column to map to a table. This cannot be a virtual column.
      table_name (string)
        Name of the table column must be mapped to. This is not an alias.
        It must be already known to be used within given group.
      group (string)
        Name of the group column and table are in.
    """

  def resolveTable(table_name, alias, group=None):
    """
      Resolve given table of given group as given alias.

      table_name (string)
        Table to alias.
      alias (string)
        Table alias. It must be unique at query scope to work as intended.
      gorup (string)
        Group the table belongs to.
    """

  def getTableAlias(table_name, group=None):
    """
      Get table alias in given group.

      table_name (string)
        Table name.
      group (string)
        Group id of given table.
    """

  def iterJoinQueryList():
    """
      Get an iterator over queries internally generated when resolving column
      map. Those queries are generated when a virtual column was registered as
      a real one. Queries required to map virtual columns to real ones are
      put in that list by build.
    """

  def getJoinTableAliasList():
    """
      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.
    """