FullTextKey.py 4.3 KB
Newer Older
Ivan Tyagov's avatar
Ivan Tyagov committed
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
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
#                     Ivan Tyagov <ivan@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.
#
##############################################################################

29
from SearchKey import SearchKey
30
import re
Ivan Tyagov's avatar
Ivan Tyagov committed
31 32 33 34

SEARCH_MODE_MAPPING = {'in_boolean_mode': 'IN BOOLEAN MODE',
                       'with_query_expansion': 'WITH QUERY EXPANSION'}

35
class FullTextKey(SearchKey):
Ivan Tyagov's avatar
Ivan Tyagov committed
36
  """ FullTextKey key is an ERP5 portal_catalog search key which is used to render
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37
      SQL expression that will try match all possible values using
Ivan Tyagov's avatar
Ivan Tyagov committed
38
      MySQL's fulltext search support.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
39
      See syntax see MySQL's FullText search reference:
Ivan Tyagov's avatar
Ivan Tyagov committed
40 41 42
      http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
  """

Jean-Paul Smets's avatar
Jean-Paul Smets committed
43
  tokens =  ('PLUS', 'MINUS', 'WORD', 'GREATERTHAN', 'LESSTHAN', 'LEFTPARENTHES',
Ivan Tyagov's avatar
Ivan Tyagov committed
44
             'RIGHTPARENTHES', 'TILDE', 'ASTERISK', 'DOUBLEQUOTE',)
45

Ivan Tyagov's avatar
Ivan Tyagov committed
46 47
  # SQL expressions patterns
  relevance = '%s_relevance'
Jérome Perrin's avatar
Jérome Perrin committed
48 49
  where_match_against = "MATCH %s AGAINST (%s %s)"
  select_match_against_as = "MATCH %s AGAINST (%s %s) AS %s"
50

Ivan Tyagov's avatar
Ivan Tyagov committed
51 52 53
  t_PLUS = r'(\+)'
  t_MINUS = r'(\-)'
  t_GREATERTHAN = r'(\>)'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
54 55
  t_LESSTHAN = r'(\<)'
  t_LEFTPARENTHES = r'(\()'
Ivan Tyagov's avatar
Ivan Tyagov committed
56
  t_RIGHTPARENTHES = r'(\))'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
57
  t_TILDE = r'(\~)'
Ivan Tyagov's avatar
Ivan Tyagov committed
58
  t_ASTERISK = r'(\*)'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59
  t_DOUBLEQUOTE = r'(\")'
60

Ivan Tyagov's avatar
Ivan Tyagov committed
61
  def t_WORD(self, t):
62
    r'[^\+\-<>\(\)\~\*\"\s]$|[^\+\-<>\(\)\~\*\"\s]+[^\*\"\s\)]'
63
    #r'[^\+\-<>\(\)\~\*\"\s]\S*'
Ivan Tyagov's avatar
Ivan Tyagov committed
64 65 66 67
    #r'[\x7F-\xFF\w\d][\x7F-\xFF\w\d]*'
    # WORD may contain arbitrary letters and numbers without white space
    return t

Jean-Paul Smets's avatar
Jean-Paul Smets committed
68
  def buildSQLExpression(self, key, value,
Ivan Tyagov's avatar
Ivan Tyagov committed
69 70 71 72 73 74
                         format=None, mode=None, range_value=None, stat__=None):
    """ Analize token list and generate SQL expressions."""
    tokens = self.tokenize(value)
    # based on type tokens we may switch to different search mode
    mode = SEARCH_MODE_MAPPING.get(mode, '')
    if mode == '':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
75 76
      # determine it based on list of tokens  i.e if we have only words
      # leave as its but if we have '-' or '+' use boolean mode
Ivan Tyagov's avatar
Ivan Tyagov committed
77 78 79 80
      for token in tokens:
        if token.type != 'WORD':
          mode = SEARCH_MODE_MAPPING['in_boolean_mode']
          break
81 82 83
      if mode == '' and len(tokens) > 1:
        value = ' '.join(['+%s' %  x.value for x in tokens])
        mode = SEARCH_MODE_MAPPING['in_boolean_mode']
Ivan Tyagov's avatar
Ivan Tyagov committed
84 85 86 87 88 89 90 91 92
    # split (if possible) to column.key
    if key.find('.') != -1:
      table, column = key.split('.')
      relevance_key1 = self.relevance %key.replace('.', '_')
      relevance_key2 = self.relevance %column
    else:
      relevance_key1 = self.relevance %key
      relevance_key2 = None
    select_expression_list = []
Jérome Perrin's avatar
Jérome Perrin committed
93 94
    where_expression = self.where_match_against % (key,
                            self.quoteSQLString(value, ''), mode)
Ivan Tyagov's avatar
Ivan Tyagov committed
95 96
    if not stat__:
      # stat__ is an internal implementation artifact to prevent adding
Jean-Paul Smets's avatar
Jean-Paul Smets committed
97
      # select_expression for countFolder
Jérome Perrin's avatar
Jérome Perrin committed
98 99 100 101 102
      select_expression_list = [self.select_match_against_as % (key,
                    self.quoteSQLString(value, ''), mode, relevance_key1),]
      if relevance_key2 is not None:
        select_expression_list.append(self.select_match_against_as % (
          key, self.quoteSQLString(value, ''), mode, relevance_key2))
Ivan Tyagov's avatar
Ivan Tyagov committed
103
    return where_expression, select_expression_list