diff --git a/product/ZSQLCatalog/Query/ComplexQuery.py b/product/ZSQLCatalog/Query/ComplexQuery.py
new file mode 100644
index 0000000000000000000000000000000000000000..c553cff59ea77a6e4c5117d49397f227a127c4c1
--- /dev/null
+++ b/product/ZSQLCatalog/Query/ComplexQuery.py
@@ -0,0 +1,93 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Products.PythonScripts.Utility import allow_class
+from Query import QueryMixin
+
+class ComplexQuery(QueryMixin):
+  """
+  Used in order to concatenate many queries
+  """
+  
+  def __init__(self, *args, **kw):
+    self.query_list = args
+    self.operator = kw.pop('operator', 'AND')
+    # XXX: What is that used for ?! It's utterly dangerous.
+    #self.__dict__.update(kw)
+
+  def getQueryList(self):
+    return self.query_list
+
+  def getRelatedTableMapDict(self):
+    result = {}
+    for query in self.getQueryList():
+      if not(isinstance(query, basestring)):
+        result.update(query.getRelatedTableMapDict())
+    return result
+
+  def asSQLExpression(self, key_alias_dict=None,
+                            ignore_empty_string=1,
+                            keyword_search_keys=None,
+                            datetime_search_keys=None,
+                            full_text_search_keys=None,
+                            stat__=0):
+    """
+    Build the sql string
+    """
+    sql_expression_list = []
+    select_expression_list = []
+    for query in self.getQueryList():
+      if isinstance(query, basestring):
+        sql_expression_list.append(query)
+      else:
+        query_result = query.asSQLExpression(key_alias_dict=key_alias_dict,
+                               ignore_empty_string=ignore_empty_string,
+                               keyword_search_keys=keyword_search_keys,
+                               datetime_search_keys=datetime_search_keys,
+                               full_text_search_keys=full_text_search_keys,
+                               stat__=stat__)
+        sql_expression_list.append(query_result['where_expression'])
+        select_expression_list.extend(query_result['select_expression_list'])
+    operator = self.getOperator()
+    result = {'where_expression':('(%s)' %  \
+                         (' %s ' % operator).join(['(%s)' % x for x in sql_expression_list])),
+              'select_expression_list':select_expression_list}
+    return result
+
+  def getSQLKeyList(self):
+    """
+    Returns the list of keys used by this
+    instance
+    """
+    key_list=[]
+    for query in self.getQueryList():
+      if not(isinstance(query, basestring)):
+        key_list.extend(query.getSQLKeyList())
+    return key_list
+
+allow_class(ComplexQuery)
diff --git a/product/ZSQLCatalog/Query/Query.py b/product/ZSQLCatalog/Query/Query.py
new file mode 100644
index 0000000000000000000000000000000000000000..016bd154c8d2a22b3d6607784594572aea74c6cd
--- /dev/null
+++ b/product/ZSQLCatalog/Query/Query.py
@@ -0,0 +1,140 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+class QueryMixin:
+  """
+    Mixing class which implements methods which are
+    common to all kinds of Queries
+  """
+  operator = None
+  format = None
+  type = None
+  
+  def __call__(self, **kw):
+    return self.asSQLExpression(**kw)  
+  
+  def getOperator(self):
+    return self.operator
+
+  def getFormat(self):
+    return self.format
+
+  def getType(self):
+    return self.type
+
+  def getRange(self):
+    return self.range
+
+  def getTableAliasList(self):
+    return self.table_alias_list
+
+  def getSearchMode(self):
+    """Search mode used for Full Text search
+    """
+    return self.search_mode
+    
+  def getSearchKey(self):
+    """Search mode used for Full Text search
+    """
+    return self.search_key
+  
+  def getKey(self):
+    return self.key
+
+  def getValue(self):
+    return self.value 
+    
+  def getOperator(self):
+    return self.operator.upper().strip()
+
+  def asSearchTextExpression(self):
+    raise NotImplementedError
+    
+  def asSQLExpression(self, key_alias_dict=None,
+                      keyword_search_keys=None,
+                      datetime_search_keys=None,
+                      full_text_search_keys=None,
+                      ignore_empty_string=1, stat__=0):
+    """
+      Return a dictionnary containing the keys and value types:
+        'where_expression': string
+        'select_expression_list': string
+    """
+    raise NotImplementedError
+
+  def getSQLKeyList(self):
+    """
+      Return a list of keys used by this query and its subqueries.
+    """
+    raise NotImplementedError
+  
+  def getRelatedTableMapDict(self):
+    """
+      Return for each key used by this query (plus ones used by its
+      subqueries) the table alias mapping.
+    """
+    raise NotImplementedError
+
+  def _quoteSQLString(self, value):
+    """Return a quoted string of the value.
+       XXX: Left for backwards compatability!
+    """
+    format = self.getFormat()
+    type = self.getType()
+    if format is not None and type is not None:
+      if type == 'date':
+        if hasattr(value, 'strftime'):
+          value = value.strftime(format)
+        if isinstance(value, basestring):
+          value = "STR_TO_DATE('%s','%s')" % (value, format)
+      if type == 'float':
+        # Make sure there is no space in float values
+        value = value.replace(' ','')
+        value = "'%s'" % value
+    else:
+      if getattr(value, 'ISO', None) is not None:
+        value = "'%s'" % value.toZone('UTC').ISO()
+      else:
+        value = "'%s'" % sql_quote(str(value))
+    return value
+
+  def _quoteSQLKey(self, key):
+    """Return a quoted string of the value.
+       XXX: Left for backwards compatability!
+    """
+    format = self.getFormat()
+    type = self.getType()
+    if format is not None and type is not None:
+      if type == 'date':
+        key = "STR_TO_DATE(DATE_FORMAT(%s,'%s'),'%s')" % (key, format, format)
+      if type == 'float':
+        float_format = format.replace(' ','')
+        if float_format.find('.') >= 0:
+          precision = len(float_format.split('.')[1])
+          key = "TRUNCATE(%s,%s)" % (key, precision)
+    return key    
diff --git a/product/ZSQLCatalog/Query/SimpleQuery.py b/product/ZSQLCatalog/Query/SimpleQuery.py
new file mode 100644
index 0000000000000000000000000000000000000000..d50326659025e794101f65255d5244cb45479282
--- /dev/null
+++ b/product/ZSQLCatalog/Query/SimpleQuery.py
@@ -0,0 +1,295 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Products.PythonScripts.Utility import allow_class
+from DateTime import DateTime
+from Query import QueryMixin
+from pprint import pprint
+
+# valid search modes for queries
+FULL_TEXT_SEARCH_MODE = 'FullText'
+EXACT_MATCH_SEARCH_MODE = 'ExactMatch'
+KEYWORD_SEARCH_MODE = 'Keyword'
+DATETIME_SEARCH_MODE = 'DateTime'
+
+def isSimpleType(value):
+  return isinstance(value, basestring) or \
+         isinstance(value, int) or \
+         isinstance(value, long) or \
+         isinstance(value, float)
+
+# XXX Bad name JPS - NotQuery or NegativeQuery is better NegationQuery
+class NegatedQuery(QueryMixin):
+  """
+    Do a boolean negation of given query.
+  """
+
+  def __init__(self, query):
+    self._query = query
+
+  def asSQLExpression(self, *args, **kw):
+    sql_expression_dict = self._query.asSQLExpression(*args, **kw)
+    sql_expression_dict['where_expression'] = '(NOT (%s))' % \
+      (sql_expression_dict['where_expression'], )
+    return sql_expression_dict
+
+  def getSQLKeyList(self, *args, **kw):
+    return self._query.getSQLKeyList(*args, **kw)
+
+  def getRelatedTableMapDict(self, *args, **kw):
+    return self._query.getRelatedTableMapDict(*args, **kw)
+
+allow_class(NegatedQuery)
+
+class SimpleQuery(QueryMixin):
+  """
+  This allow to define constraints on a sql column
+
+  format - type date : %d/%m/%Y
+           type float : 1 234.12
+  """
+  
+  def __init__(self, format=None, operator=None, range=None, key=None,
+                     search_mode=None, table_alias_list=None, type=None, **kw):
+    self.format = format
+    if operator is None:
+      operator = 'OR'
+    self.operator = operator
+    self.range = range
+    self.search_mode = search_mode
+    self.table_alias_list = table_alias_list
+    key_list = kw.keys()
+    if len(key_list) != 1:
+      raise KeyError, 'Query must have only one key'
+    self.key = key_list[0]
+    self.value = kw[self.key]
+    self.type = type
+    self.search_key = key
+
+  def getRelatedTableMapDict(self):
+    result = {}
+    table_alias_list = self.getTableAliasList()
+    if table_alias_list is not None:
+      result[self.getKey()] = table_alias_list
+    return result  
+    
+  def getSQLKeyList(self):
+    """
+    Returns the list of keys used by this
+    instance
+    """
+    return [self.getKey()]
+    
+  def asSearchTextExpression(self):
+    # This will be the standard way to represent
+    # complex values in listbox. Some fixed
+    # point must be garanteed
+    value = self.getValue()
+    if isSimpleType(value) or isinstance(value, DateTime):
+      return str(value)
+    elif isinstance(value, (list, tuple)):
+      value = map(lambda x:str(x), value)
+      return (' %s ' % self.operator).join(value)
+        
+  def _getSearchKeyClassByType(self, type, search_key_class = None):
+    """ Return search key class based on type of value. """
+    name_search_key_map = {'keyword': KeyWordKey,
+                           'default': DefaultKey,
+                           'fulltext': FullTextKey,
+                           'date': DateTimeKey,
+                           'float': FloatKey,
+                           'int': DefaultKey,}
+    return name_search_key_map.get(type, search_key_class)
+    
+  def _getSearchKeyClassByValue(self, value, search_key_class = None):
+    """ Return search key class based on type of value. """
+    if isinstance(value, basestring):
+      if value.find('%')!=-1:
+        # it's likely a KeyWordKey
+        search_key_class = KeyWordKey
+      else:
+        search_key_class = DefaultKey
+    elif isinstance(value, DateTime):
+      search_key_class = DateTimeKey      
+    elif isinstance(value, (int, long,)):
+      search_key_class = DefaultKey
+    elif isinstance(value, float):
+      search_key_class = FloatKey
+    return search_key_class
+
+  def _asSQLExpression(self, search_key_class, key, value, format=None, mode=None, range_value=None, stat__=None):
+    """ Generate SQL expressions based on respective search_key passed. """
+    lexer = getSearchKeyInstance(search_key_class)
+    where_expression, select_expression_list = \
+             lexer.buildSQLExpression(key, value, format, mode, range_value, stat__)
+    sql_expressions = {'where_expression': where_expression,
+                       'select_expression_list': select_expression_list,}
+    return sql_expressions
+    
+  def asSQLExpression(self, key_alias_dict = None, keyword_search_keys = [],
+                      datetime_search_keys = [], full_text_search_keys = [],
+                      ignore_empty_string = 1, stat__ = 0):
+    """
+    Build the sql expressions string
+    """
+    search_key_class = None
+    value = self.getValue()
+    key = self.getKey()
+    operator = self.getOperator()
+    type = self.getType()
+    format = self.getFormat()
+    search_mode = self.getSearchMode()
+    range_value = self.getRange()
+    search_key = self.getSearchKey()
+
+    # key can have an alias definition which we should acquire
+    if key_alias_dict is not None:
+      key = key_alias_dict.get(key, None)
+
+    search_key_class = None
+    where_expression_list = []
+    select_expression_list = []    
+    sql_expressions = {'where_expression': '1', 
+                       'select_expression_list': []}
+    
+    # some use cases where we can just return SQL without grammar staff
+    if key is None or (ignore_empty_string and \
+                       isinstance(value, basestring) and \
+                       value.strip() == ''):
+      # do not further generate sql expressions because
+      # we ignore empty strings by default
+      return sql_expressions
+    elif ignore_empty_string==0 and isinstance(value, basestring) and value.strip() == '':
+      # explicitly requested not to ignore empty strings
+      sql_expressions = {'where_expression': "%s = ''" %key, 
+                         'select_expression_list': []}
+      return sql_expressions
+    else:
+      # search for 'NULL' values 
+      if value is None:
+        sql_expressions = {'where_expression':  "%s is NULL" % (key),
+                           'select_expression_list': [],}
+        return sql_expressions                 
+      # get search class based on explicitly passed key type
+      if search_key_class is None:
+        search_key_class = self._getSearchKeyClassByType(type)   
+            
+      # we have a list of values and respective operator defined
+      if isinstance(value, (tuple, list)):
+        if range_value is None:
+          # use operators to build sql expressions
+          if operator in ('IN',):
+            # values in list are not treated as searchable strings but 
+            # they should be SQL quoted at least
+            if len(value) > 1:
+              if search_key_class is None:
+                # no explicitly defined, try to find by value
+                search_key_class = self._getSearchKeyClassByValue(value[0]) 
+              search_key_instance = getSearchKeyInstance(search_key_class)
+              escaped_value_list = [search_key_instance.quoteSQLString(x, format) for x in value]
+              escaped_value_string = ', '.join(escaped_value_list)
+              where_expression_list.append("%s IN (%s)" % (key, escaped_value_string))
+            elif len(value) == 1:
+              if search_key_class is None:
+                # no explicitly defined, try to find by value            
+                search_key_class = self._getSearchKeyClassByValue(value[0])
+              search_key_instance = getSearchKeyInstance(search_key_class)               
+              where_expression_list.append("%s = %s" 
+                                           %(key, search_key_instance.quoteSQLString(value[0], format)))
+            else:
+              # empty list
+              where_expression_list.append("0")          
+          elif operator in ('OR', 'AND',):
+            # each of the list elements can be treated as a Key, so 
+            # leave SQL generation to Key itself
+            if len(value) > 1:
+              sql_logical_sub_expressions = []
+              if search_key_class is None:
+                # no explicitly defined, try to find by value
+                search_key_class = self._getSearchKeyClassByValue(value[0]) 
+              for item in value:
+                list_item_sql_expressions = self._asSQLExpression(search_key_class, key, \
+                                                                   item, format, search_mode, range_value, stat__)
+                sql_logical_sub_expressions.append('%s' %list_item_sql_expressions['where_expression'])
+              # join list items (now sql logical expressions) using respective operator
+              where_expression = (' %s ' %operator).join(sql_logical_sub_expressions)
+              where_expression_list.append("(%s)" % (where_expression))
+            elif len(value) == 1:
+              if search_key_class is None:
+                # no explicitly defined, try to find by value            
+                search_key_class = self._getSearchKeyClassByValue(value[0])            
+              item_sql_expressions = self._asSQLExpression(search_key_class, key, \
+                                                            value[0], format, search_mode, range_value, stat__)
+              where_expression_list.append(item_sql_expressions['where_expression'])
+          # join where expressions list
+          where_expression = ' '.join(where_expression_list)
+          sql_expressions = {'where_expression': where_expression,
+                             'select_expression_list': [],}
+          return sql_expressions
+        else:
+          # we can have range specified
+          if search_key_class is None:
+            # try to guess by type of first_element in list
+            search_key_class = self._getSearchKeyClassByValue(value[0])
+      
+      # try to get search key type by the key definitions passed
+      if search_key_class is None:
+        if search_key == EXACT_MATCH_SEARCH_MODE:
+          search_key_class =  RawKey
+        elif search_key == KEYWORD_SEARCH_MODE or \
+            (key in keyword_search_keys):
+          search_key_class =  KeyWordKey
+        elif search_key == DATETIME_SEARCH_MODE or \
+          (key in datetime_search_keys):
+          search_key_class =  DateTimeKey
+        elif search_key == FULL_TEXT_SEARCH_MODE or \
+          (key in full_text_search_keys):
+          search_key_class =  FullTextKey
+      
+      # get search class based on value of value
+      if search_key_class is None:
+        search_key_class = self._getSearchKeyClassByValue(value)
+      
+      # last fallback case
+      if search_key_class is None:
+        search_key_class = DefaultKey
+        
+      # use respective search key.       
+      sql_expressions = self._asSQLExpression(search_key_class, key, 
+                                                value, format, search_mode, range_value, stat__)
+      return sql_expressions
+
+allow_class(SimpleQuery)
+
+from Products.ZSQLCatalog.SearchKey.DefaultKey import DefaultKey
+from Products.ZSQLCatalog.SearchKey.RawKey import RawKey
+from Products.ZSQLCatalog.SearchKey.KeyWordKey import KeyWordKey
+from Products.ZSQLCatalog.SearchKey.DateTimeKey import DateTimeKey
+from Products.ZSQLCatalog.SearchKey.FullTextKey import FullTextKey
+from Products.ZSQLCatalog.SearchKey.FloatKey import FloatKey
+from Products.ZSQLCatalog.SQLCatalog import getSearchKeyInstance
diff --git a/product/ZSQLCatalog/Query/__init__.py b/product/ZSQLCatalog/Query/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/product/ZSQLCatalog/Query/__init__.py
@@ -0,0 +1 @@
+
diff --git a/product/ZSQLCatalog/SQLCatalog.py b/product/ZSQLCatalog/SQLCatalog.py
index d3d6ecc3e63c009d0b17707dfde8bee73612874d..959ac4ca3b62eaf8a14371dff39f5b7c996fa01a 100644
--- a/product/ZSQLCatalog/SQLCatalog.py
+++ b/product/ZSQLCatalog/SQLCatalog.py
@@ -75,7 +75,7 @@ except ImportError:
       return self.function(*opts, **kw)
   enableReadOnlyTransactionCache = doNothing
   disableReadOnlyTransactionCache = doNothing
-
+ 
 UID_BUFFER_SIZE = 300
 OBJECT_LIST_SIZE = 300
 MAX_PATH_LEN = 255
@@ -114,13 +114,6 @@ def manage_addSQLCatalog(self, id, title,
   if REQUEST is not None:
     return self.manage_main(self, REQUEST,update_menu=1)
 
-def isSimpleType(value):
-  return isinstance(value, basestring) or \
-         isinstance(value, int) or \
-         isinstance(value, long) or \
-         isinstance(value, float)
-
-
 class UidBuffer(TM):
   """Uid Buffer class caches a list of reserved uids in a transaction-safe way."""
 
@@ -188,414 +181,6 @@ class UidBuffer(TM):
     tid = get_ident()
     self.temporary_buffer.setdefault(tid, []).extend(iterable)
 
-
-# valid search modes for queries
-FULL_TEXT_SEARCH_MODE = 'FullText'
-EXACT_MATCH_SEARCH_MODE = 'ExactMatch'
-KEYWORD_SEARCH_MODE = 'Keyword'
-DATETIME_SEARCH_MODE = 'DateTime'
-
-
-class QueryMixin:
-  """
-    Mixing class which implements methods which are
-    common to all kinds of Queries
-  """
-
-  operator = None
-  format = None
-  type = None
-
-  def getOperator(self):
-    return self.operator
-
-  def getFormat(self):
-    return self.format
-
-  def getType(self):
-    return self.type
-
-  def getLogicalOperator(self):
-    return self.logical_operator
-
-  def _quoteSQLString(self, value):
-    """Return a quoted string of the value.
-    """
-    format = self.getFormat()
-    type = self.getType()
-    if format is not None and type is not None:
-      if type == 'date':
-        if hasattr(value, 'strftime'):
-          value = value.strftime(format)
-        if isinstance(value, basestring):
-          value = "STR_TO_DATE('%s','%s')" % (value, format)
-      if type == 'float':
-        # Make sure there is no space in float values
-        value = value.replace(' ','')
-        value = "'%s'" % value
-    else:
-      if getattr(value, 'ISO', None) is not None:
-        value = "'%s'" % value.toZone('UTC').ISO()
-      else:
-        value = "'%s'" % sql_quote(str(value))
-    return value
-
-  def _quoteSQLKey(self, key):
-    """Return a quoted string of the value.
-    """
-    format = self.getFormat()
-    type = self.getType()
-    if format is not None and type is not None:
-      if type == 'date':
-        key = "STR_TO_DATE(DATE_FORMAT(%s,'%s'),'%s')" % (key, format, format)
-      if type == 'float':
-        float_format = format.replace(' ','')
-        if float_format.find('.') >= 0:
-          precision = len(float_format.split('.')[1])
-          key = "TRUNCATE(%s,%s)" % (key, precision)
-    return key
-
-  def asSQLExpression(self, key_alias_dict=None,
-                      keyword_search_keys=None,
-                      datetime_search_keys=None,
-                      full_text_search_keys=None,
-                      ignore_empty_string=1, stat__=0):
-    """
-      Return a dictionnary containing the keys and value types:
-        'where_expression': string
-        'select_expression_list': string
-    """
-    raise NotImplementedError
-
-  def getSQLKeyList(self):
-    """
-      Return a list of keys used by this query and its subqueries.
-    """
-    raise NotImplementedError
-  
-  def getRelatedTableMapDict(self):
-    """
-      Return for each key used by this query (plus ones used by its
-      subqueries) the table alias mapping.
-    """
-    raise NotImplementedError
-
-class NegatedQuery(QueryMixin): # XXX Bad name JPS - NotQuery or NegativeQuery is better NegationQuery
-  """
-    Do a boolean negation of given query.
-  """
-
-  def __init__(self, query):
-    self._query = query
-
-  def asSQLExpression(self, *args, **kw):
-    sql_expression_dict = self._query.asSQLExpression(*args, **kw)
-    sql_expression_dict['where_expression'] = '(NOT (%s))' % \
-      (sql_expression_dict['where_expression'], )
-    return sql_expression_dict
-
-  def getSQLKeyList(self, *args, **kw):
-    return self._query.getSQLKeyList(*args, **kw)
-
-  def getRelatedTableMapDict(self, *args, **kw):
-    return self._query.getRelatedTableMapDict(*args, **kw)
-
-  # asSearchTextExpression is still not implemented
-
-allow_class(NegatedQuery)
-
-class Query(QueryMixin):
-  """
-  This allow to define constraints on a sql column
-
-  format - type date : %d/%m/%Y
-           type float : 1 234.12
-  """
-  def __init__(self, format=None, operator=None, range=None, key=None,
-                     search_mode=None, table_alias_list=None, type=None, **kw):
-    self.format = format
-    if operator is None:
-      operator = 'OR'
-    self.operator = operator
-    self.range = range
-    self.search_mode = search_mode
-    self.table_alias_list = table_alias_list
-    key_list = kw.keys()
-    if len(key_list) != 1:
-      raise KeyError, 'Query must have only one key'
-    self.key = key_list[0]
-    self.value = kw[self.key]
-    self.type = type
-    self.search_key = key
-
-  def __call__(self, **kw):
-    return self.asSQLExpression(**kw)
-
-  def getRange(self):
-    return self.range
-
-  def getTableAliasList(self):
-    return self.table_alias_list
-
-  def getRelatedTableMapDict(self):
-    result = {}
-    table_alias_list = self.getTableAliasList()
-    if table_alias_list is not None:
-      result[self.getKey()] = table_alias_list
-    return result
-
-  def getSearchMode(self):
-    """Search mode used for Full Text search
-    """
-    return self.search_mode
-
-  def asSearchTextExpression(self):
-    # This will be the standard way to represent
-    # complex values in listbox. Some fixed
-    # point must be garanteed
-    value = self.value
-    if isSimpleType(value) or isinstance(value, DateTime):
-      return str(value)
-    elif isinstance(value, (list, tuple)):
-      value = map(lambda x:str(x), value)
-      return (' %s ' % self.operator).join(value)
-
-  def asSQLExpression(self, key_alias_dict=None,
-                            keyword_search_keys=None,
-                            datetime_search_keys=None,
-                            full_text_search_keys=None,
-                            ignore_empty_string=1, stat__=0):
-    """
-    Build the sql string
-    """
-    sql_expression = ''
-    value = self.getValue()
-    key = self.getKey()
-    search_key = self.search_key
-    ignore_key = 0
-    if key_alias_dict is not None:
-      # Try to find the alias
-      if key not in key_alias_dict:
-        ignore_key=1
-      else:
-        key = key_alias_dict.get(key)
-        if key is None:
-          ignore_key=1
-    where_expression = []
-    select_expression = []
-    # Default case: variable equality
-    range_value = self.getRange()
-    format = self.getFormat()
-    if ignore_key:
-      pass    
-    elif range_value is not None:
-      if isinstance(value, (list, tuple)):
-        if format is None:
-          query_min = min(value)
-          query_max = max(value)
-        else:
-          query_min = value[0]
-          query_max = value[1]
-      else:
-        query_min=query_max=value
-      query_min = self._quoteSQLString(query_min)
-      query_max = self._quoteSQLString(query_max)
-      if range_value == 'min' :
-        where_expression.append("%s >= %s" % (key, query_min))
-      elif range_value == 'max' :
-        where_expression.append("%s < %s" % (key, query_max))
-      elif range_value == 'minmax' :
-        where_expression.append("%s >= %s and %s < %s" % (key, query_min, key, query_max))
-      elif range_value == 'minngt' :
-        where_expression.append("%s >= %s and %s <= %s" % (key, query_min, key, query_max))
-      elif range_value == 'ngt' :
-        where_expression.append("%s <= %s" % (key, query_max))
-      elif range_value == 'nlt' :
-        where_expression.append("%s > %s" % (key, query_max))
-    elif isSimpleType(value) or isinstance(value, DateTime) \
-        or (isinstance(value, (list, tuple)) and self.operator.upper() != 'IN'):
-      # Convert into lists any value which contain 'OR'
-      # Refer to _listGlobalActions DCWorkflow patch for example of use
-      if isinstance(value, basestring) \
-                and search_key != EXACT_MATCH_SEARCH_MODE:
-        value = value.split(' OR ')
-        value = map(lambda x:x.strip(), value)
-      value_list = value
-      if isSimpleType(value) or isinstance(value, DateTime):
-        value_list = [value]
-      # For security.
-      for value in value_list:
-        comparison_operator = None
-        if (value != '' or not ignore_empty_string) \
-                        and isinstance(value, basestring):
-          if '%' in value and search_key != EXACT_MATCH_SEARCH_MODE:
-            comparison_operator = 'LIKE'
-          elif search_key == DATETIME_SEARCH_MODE  or (
-               datetime_search_keys is not None and key in datetime_search_keys):
-            if len(value) >= 1 and value[0:2] in ('<=','!=','>='):
-              comparison_operator = value[0:2]
-              value = value[2:]
-            elif len(value) >= 1 and value[0] in ('=','>','<'):
-              comparison_operator = value[0]
-              value = value[1:]
-            if comparison_operator is None:
-              comparison_operator = '='
-            # this seems like a DateTime bug!
-            # 2002/02/01 ==>(UTC) 2002-01-31 22:00:00
-            # 2002-02-01 ==>(UTC) 2002-02-01 00:00:00 (!)
-            value = value.replace('-', '/') 
-            value = DateTime(value).toZone('UTC')
-          elif len(value) >= 1 and value[0:2] in ('<=','!=','>='):
-            comparison_operator = value[0:2]
-            value = value[2:]
-          elif len(value) >= 1 and value[0] in ('=','>','<'):
-            comparison_operator = value[0]
-            value = value[1:]
-          elif search_key == KEYWORD_SEARCH_MODE or (
-                   key in keyword_search_keys and
-                    search_key != EXACT_MATCH_SEARCH_MODE):
-            # We must add % in the request to simulate the catalog
-            comparison_operator = 'LIKE'
-            value = '%%%s%%' % value
-          elif search_key == FULL_TEXT_SEARCH_MODE or (
-                  key in full_text_search_keys
-                  and search_key != EXACT_MATCH_SEARCH_MODE):
-            # We must add % in the request to simulate the catalog
-            # we first check if there is a special search_mode for this key
-            # incl. table name, or for all keys of that name,
-            # or there is a search_mode supplied for all fulltext keys
-            # or we fall back to natural mode
-            search_mode=self.getSearchMode()
-            if search_mode is None:
-              search_mode = 'natural'
-            search_mode=search_mode.lower()
-            mode = full_text_search_modes.get(search_mode,'')
-            where_expression.append(
-                        "MATCH %s AGAINST ('%s' %s)" % (key, value, mode))
-            if not stat__:
-              # we return relevance as Table_Key_relevance
-              select_expression.append(
-                     "MATCH %s AGAINST ('%s' %s) AS %s_relevance" 
-                     % (key, value, mode,key.replace('.','_')))
-              # and for simplicity as Key_relevance
-              if '.' in key:
-                select_expression.append(
-                     "MATCH %s AGAINST ('%s' %s) AS %s_relevance" % 
-                     (key, value, mode,key.split('.')[1]))
-          else:
-            comparison_operator = '='
-        elif not isinstance(value, basestring):
-          comparison_operator = '='
-        if comparison_operator is not None:
-          key = self._quoteSQLKey(key)
-          value = self._quoteSQLString(value)
-          where_expression.append("%s %s %s" % 
-                                  (key, comparison_operator, value))
-
-    elif value is None:
-      where_expression.append("%s is NULL" % (key))
-    elif isinstance(value, (tuple, list)) and self.operator.upper() == 'IN':
-      if len(value) > 1:
-        escaped_value_list = [self._quoteSQLString(x) for x in value]
-        escaped_value_string = ', '.join(escaped_value_list)
-        where_expression.append("%s IN (%s)" % (key, escaped_value_string))
-      elif len(value) == 1:
-        where_expression.append("%s = %s" % (key, self._quoteSQLString(value[0])))
-      else:
-        where_expression.append('0') # "foo IN ()" is invalid SQL syntax, so use a "false" value.
-    else:
-      where_expression.append("%s = %s" % 
-           (self._quoteSQLKey(key), self._quoteSQLString(value)))
-
-    if len(where_expression)>0:
-      if len(where_expression)==1:
-        where_expression = where_expression[0]
-      else:
-        where_expression = '(%s)' % (' %s ' % self.getOperator()).join(where_expression)
-    else:
-      where_expression = '1' # It is better to have a valid default
-    return {'where_expression':where_expression,
-            'select_expression_list':select_expression}
-
-  def getKey(self):
-    return self.key
-
-  def getValue(self):
-    return self.value
-
-  def getSQLKeyList(self):
-    """
-    Returns the list of keys used by this
-    instance
-    """
-    return [self.getKey()]
-
-allow_class(Query)
-
-class ComplexQuery(QueryMixin):
-  """
-  Used in order to concatenate many queries
-  """
-  def __init__(self, *args, **kw):
-    self.query_list = args
-    self.operator = kw.pop('operator', 'AND')
-    # XXX: What is that used for ?! It's utterly dangerous.
-    self.__dict__.update(kw)
-
-  def __call__(self, **kw):
-    return self.asSQLExpression(**kw)
-
-  def getQueryList(self):
-    return self.query_list
-
-  def getRelatedTableMapDict(self):
-    result = {}
-    for query in self.getQueryList():
-      if not(isinstance(query, basestring)):
-        result.update(query.getRelatedTableMapDict())
-    return result
-
-  def asSQLExpression(self, key_alias_dict=None,
-                            ignore_empty_string=1,
-                            keyword_search_keys=None,
-                            datetime_search_keys=None,
-                            full_text_search_keys=None,
-                            stat__=0):
-    """
-    Build the sql string
-    """
-    sql_expression_list = []
-    select_expression_list = []
-    for query in self.getQueryList():
-      if isinstance(query, basestring):
-        sql_expression_list.append(query)
-      else:
-        query_result = query.asSQLExpression( key_alias_dict=key_alias_dict,
-                               ignore_empty_string=ignore_empty_string,
-                               keyword_search_keys=keyword_search_keys,
-                               full_text_search_keys=full_text_search_keys,
-                               stat__=stat__)
-        sql_expression_list.append(query_result['where_expression'])
-        select_expression_list.extend(query_result['select_expression_list'])
-    operator = self.getOperator()
-    result = {'where_expression':('(%s)' %  \
-                         (' %s ' % operator).join(['(%s)' % x for x in sql_expression_list])),
-              'select_expression_list':select_expression_list}
-    return result
-
-  def getSQLKeyList(self):
-    """
-    Returns the list of keys used by this
-    instance
-    """
-    key_list=[]
-    for query in self.getQueryList():
-      if not(isinstance(query, basestring)):
-        key_list.extend(query.getSQLKeyList())
-    return key_list
-
-allow_class(ComplexQuery)
-
 class Catalog(Folder,
               Persistent,
               Acquisition.Implicit,
@@ -2191,7 +1776,12 @@ class Catalog(Folder,
     for t in self.sql_catalog_scriptable_keys:
       t = t.split('|')
       key = t[0].strip()
-      method_id = t[1].strip()
+      if len(t)>1:
+        # method defined that will generate a ComplexQuery
+        method_id = t[1].strip()
+      else:
+        # no method define, let ScriptableKey generate a ComplexQuery
+        method_id = None
       scriptable_key_dict[key] = method_id
 
     # Build the list of Queries and ComplexQueries
@@ -2210,9 +1800,14 @@ class Catalog(Folder,
         if isinstance(value, (Query, ComplexQuery)):
           current_query = value
         elif scriptable_key_dict.has_key(key):
-          # Turn this key into a query by invoking a script
-          method = getattr(self, scriptable_key_dict[key])
-          current_query = method(value) # May return None
+          if scriptable_key_dict[key] is not None:
+            # Turn this key into a query by invoking a script          
+            method = getattr(self, scriptable_key_dict[key])
+            current_query = method(value) # May return None            
+          else:
+            # let default implementation of ScriptableKey generate ComplexQuery
+            search_key_instance = getSearchKeyInstance(ScriptableKey)
+            current_query = search_key_instance.buildQuery('', value)
           if hasattr(current_query, 'order_by'): query_group_by_list = current_query.order_by
         else:
           if isinstance(value, dict):
@@ -2240,7 +1835,7 @@ class Catalog(Folder,
         sort_key_dict[sort_key] = 1
 
     related_tuples = self.getSQLCatalogRelatedKeyList(key_list=key_list)
-
+    
     # Define related maps
     # each tuple from `related_tuples` has the form (key,
     # 'table1,table2,table3/column/where_expression')
@@ -2523,7 +2118,7 @@ class Catalog(Folder,
     #LOG('queryResults',0,'kw: %s' % str(kw))
     #LOG('queryResults',0,'from_table_list: %s' % str(query['from_table_list']))
     return sql_method(src__=src__, **kw)
-
+      
   def searchResults(self, REQUEST=None, used=None, **kw):
     """ Returns a list of brains from a set of constraints on variables """
     # The used argument is deprecated and is ignored
@@ -2558,7 +2153,7 @@ class Catalog(Folder,
     """
     method = getattr(self, self.sql_read_recorded_object_list)
     return method(catalog=catalog)
-
+   
   # Filtering
   def manage_editFilter(self, REQUEST=None, RESPONSE=None, URL1=None):
     """
@@ -2745,3 +2340,37 @@ class Catalog(Folder,
 Globals.default__class_init__(Catalog)
 
 class CatalogError(Exception): pass
+
+# hook search keys and Query implementation 
+def getSearchKeyInstance(search_key_class):
+  """ Return instance of respective search_key class.
+      We should have them initialized only once."""
+  global SEARCH_KEY_INSTANCE_POOL
+  lexer = SEARCH_KEY_INSTANCE_POOL[search_key_class]
+  return lexer 
+  
+from Query.Query import QueryMixin
+from Query.SimpleQuery import NegatedQuery, SimpleQuery
+from Query.ComplexQuery import ComplexQuery
+
+# for of backwards compatability  
+QueryMixin = QueryMixin
+Query = SimpleQuery
+NegatedQuery = NegatedQuery 
+ComplexQuery = ComplexQuery
+ 
+from Products.ZSQLCatalog.SearchKey.DefaultKey import DefaultKey
+from Products.ZSQLCatalog.SearchKey.RawKey import RawKey
+from Products.ZSQLCatalog.SearchKey.KeyWordKey import KeyWordKey
+from Products.ZSQLCatalog.SearchKey.DateTimeKey import DateTimeKey
+from Products.ZSQLCatalog.SearchKey.FullTextKey import FullTextKey
+from Products.ZSQLCatalog.SearchKey.FloatKey import FloatKey
+from Products.ZSQLCatalog.SearchKey.ScriptableKey import ScriptableKey, KeyMappingKey
+
+# pool of global preinitialized search keys instances
+SEARCH_KEY_INSTANCE_POOL = {}
+for search_key_class in (DefaultKey, RawKey, KeyWordKey, DateTimeKey, 
+                         FullTextKey, FloatKey, ScriptableKey, KeyMappingKey):
+  search_key_instance = search_key_class()
+  search_key_instance.build()
+  SEARCH_KEY_INSTANCE_POOL[search_key_class] = search_key_instance
diff --git a/product/ZSQLCatalog/SearchKey/DateTimeKey.py b/product/ZSQLCatalog/SearchKey/DateTimeKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..09347e786c9aee349fe0a299c96cfaafa7beadb6
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/DateTimeKey.py
@@ -0,0 +1,196 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery as Query
+from Products.ZSQLCatalog.Query.ComplexQuery import ComplexQuery
+from Products.ZSQLCatalog.SQLCatalog import getSearchKeyInstance
+from DateTime import DateTime
+from Key import BaseKey
+from pprint import pprint
+
+ 
+class DateTimeKey(BaseKey):
+  """ DateTimeKey key is an ERP5 portal_catalog search key which is used to render
+      SQL expression that will try to match values in DateTime MySQL columns.
+      It supports following special operator ['=', '%', '>' , '>=', '<', '<='] in
+      addition to main logical operators like ['OR', 'or', 'AND', 'and'].
+      
+      Note: because all ERP5 datetime values are indexed in MySQL in 'UTC' 
+      the respective passed date will be first converted to 'UTC' before inserted into
+      respective SQL query!
+      
+      Examples (GMT+02, Bulgaria/Sofia for 'delivery.start_date'):
+      
+        * '15/01/2008' --> "delivery.start_date = '2008-01-14 22:00'"
+        
+        * '>=15/01/2008' --> "delivery.start_date >= '2008-01-14 22:00'"      
+        
+        * '>=15/01/2008 or <=20/01/2008' 
+          --> "delivery.start_date >= '2008-01-14 22:00' or delivery.start_date<='2008-01-19 22:00'"
+        
+        * '>=15/01/2008 10:00 GMT+02 OR <=20/01/2008 05:12 Universal'
+          -->
+          "delivery.start_date >= '2008-01-15 08:00 Universal' 
+            OR 
+          delivery.start_date <= '2008-01-20 05:12 Universal'
+          "
+  """
+  
+  tokens =  ('DATE', 'OR', 'AND', 'NOT', 'EQUAL',
+             'GREATERTHAN', 'GREATERTHANEQUAL',
+             'LESSTHAN', 'LESSTHANEQUAL')
+             
+  sub_operators =  ('GREATERTHAN', 'GREATERTHANEQUAL', 
+                    'LESSTHAN', 'LESSTHANEQUAL', 'NOT', 'EQUAL',)
+ 
+  def t_OR(self, t):
+    r'(\s+OR\s+|\s+or\s+)'
+    # operator has leading and trailing ONLY one white space character
+    t.value = 'OR'
+    return t
+
+  def t_AND(self, t):
+    r'(\s+AND\s+|\s+and\s+)'
+    # operator has leading and trailing ONLY one white space character
+    t.value = 'AND'
+    return t 
+  
+  def t_NOT(self, t):
+    r'(\s+NOT\s+|\s+not\s+|!=)'
+    # operator has leading and trailing ONLY one white space character
+    t.value = t.value.upper().strip()
+    return t   
+
+  t_GREATERTHANEQUAL = r'>='  
+  t_LESSTHANEQUAL = r'<='  
+  t_GREATERTHAN = r'>'
+  t_LESSTHAN = r'<'
+  t_EQUAL = r'='    
+  t_DATE = r'\d{1,4}[(/|\.|\-) /.]\d{1,4}[(/|\.|\-) /.]\d{1,4}((\s.)*\d{0,2}:\d{0,2}(:\d{0,2})?)?(\sUniversal|\sGMT\+\d\d)?|\d\d\d\d%?'
+          
+  def quoteSQLString(self, value, format):
+    """ Return a quoted string of the value. 
+        Make sure to convert it to UTC first."""
+    if getattr(value, 'ISO', None) is not None:
+      value = "'%s'" % value.toZone('UTC').ISO()
+    else:
+      value = "'%s'" %DateTime(value).toZone('UTC').ISO()
+    return value
+       
+  def buildQueryForTokenList(self, tokens, key, value, format):
+    """ Build a ComplexQuery for a token list """
+    query_list = []
+    for group_tokens in self.groupByLogicalOperator(tokens, 'AND'):
+      token_values = [x.value for x in group_tokens]
+      sub_operator, sub_tokens = self.getOperatorForTokenList(group_tokens)
+      date_value = sub_tokens[0].value
+      days_offset = 0
+      # some format require special handling
+      if format != '%Y':
+        # full format (Year/Month/Day)
+        if sub_operator in ('=',):
+          # 2007/01/01 00:00 <= date < 2007/01/02
+          days_offset = 1
+      elif format == '%Y':
+        # incomplete format only Year because DateTime can not handle
+        # extend format and value by assumption that start of year is ment
+        # add days ofset accordingly
+        format = '%%%s/%%m/%%d' %format
+        date_value = '%s/01/01' %date_value
+        days_offset_map = {'=' : 366, '>' : 366, 
+                           '>=' : 366, '<': -366, '<=':-366}
+        days_offset = days_offset_map[sub_operator]
+  
+      # convert to UTC in given format
+      is_valid_date = 1
+      try:
+        if format != '%m/%d/%Y':
+          # treat ambigious dates as "days before month before year"
+          date_value = DateTime(date_value, datefmt="international").toZone('UTC')
+        else:
+          # US style "month before day before year"
+          date_value = DateTime(date_value).toZone('UTC')
+      except:
+        is_valid_date = 0
+      
+      query_kw = None       
+      if is_valid_date:
+        if sub_operator == '=':
+          # transform to range 'key >= date  AND  date < key'
+          query_kw = {key: (date_value, date_value + days_offset,),
+                      'range': 'minmax'} 
+        else:
+          query_kw = {key: date_value + days_offset,
+                      'range': sub_operator}   
+        query_kw['type'] = 'date'              
+      else:
+        # not a valid date, try to get an year range
+        is_year = 1
+        date_value = date_value.replace('%', '')
+        try: date_value = int(date_value)
+        except: is_year = 0
+        if is_year:
+          date_value = '%s/01/01' % date_value
+          date_value = DateTime(date_value).toZone('UTC')
+          query_kw = {key: (date_value, date_value + 366,),
+                      'type': 'date',
+                      'range': 'minmax'} 
+                      
+      # append only if it was possible to generate query
+      if query_kw is not None:
+        query_list.append(Query(**query_kw))                    
+        
+    # join query list in one really big ComplexQuery
+    if len(query_list):
+      complex_query = ComplexQuery(*query_list, 
+                                   **{'operator': 'AND'})
+      return complex_query   
+       
+##  def buildSQLExpressionFromSearchString(self, key, value, format, mode, range_value, stat__):
+##    """ Tokenize/analyze passed string value and generate SQL query expressions. """
+##    where_expression = ''
+##    key = self.quoteSQLKey(key, format)
+##    tokens = self.tokenize(value)
+##    operators_mapping_list = self.groupByOperator(tokens)
+##    # new one
+##    for item in operators_mapping_list:
+##      row_tokens_values = []
+##      tokens = item['tokens']
+##      operator = item['operator']
+##      operator_value = None
+##      if operator is not None:
+##        # operator is standalone expression
+##        operator_value = operator.value
+##        where_expressions.append('%s' %operator_value)
+##      if len(tokens):
+##        # no it's not a stand alone expression, 
+##        # determine it from list of tokens
+##        operator_value, sub_tokens = self.getOperatorForTokenList(tokens)
+##        row_tokens_values = [self.quoteSQLString(x.value, format) for x in sub_tokens]
+##        where_expression = "%s %s %s" %(key, operator_value, ' '.join(row_tokens_values))
+##    return where_expression, []
diff --git a/product/ZSQLCatalog/SearchKey/DefaultKey.py b/product/ZSQLCatalog/SearchKey/DefaultKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c1d2bbc6e68d0e10923ab69786c709eb667e326
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/DefaultKey.py
@@ -0,0 +1,147 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+ 
+from Key import BaseKey
+from pprint import pprint
+
+class DefaultKey(BaseKey):
+  """ DefaultKey key is an ERP5 portal_catalog search key which is used to render
+      SQL expression that will try to exactly one value.
+      It supports following special operator ['=', '%', '>' , '>=', '<', '<='] in
+      addition to main logical operators like ['OR', 'or', 'AND', 'and'].
+      
+      Examples for title column: 
+        * 'foo or bar'  --> "title = 'foo' OR title = 'bar'"
+        * 'foo or =bar'  --> "title = 'foo' OR title = 'bar'"
+        * '%foo% or bar' --> "title = '%foo%' OR title = 'bar'"
+        * 'Organisation Module' -->  "title = 'Organisation Module'"
+        * '"Organisation Module"' --> "title = 'Organisation Module'"
+        * '="Organisation Module"' --> "title = 'Organisation Module'"
+  """
+  
+  # default type of sub Queries to be generated out fo a search string
+  default_key_type = 'default'
+  
+  tokens =  ('OR', 'AND', 'NOT', 'WORDSET', 'WORD',
+             'GREATERTHAN', 'GREATERTHANEQUAL', 
+             'LESSTHAN', 'LESSTHANEQUAL')
+             
+  sub_operators = ('GREATERTHAN', 'GREATERTHANEQUAL', 
+                    'LESSTHAN', 'LESSTHANEQUAL', 'NOT')
+  
+
+  # Note: Order of placing rules (t_WORD for example) is very important
+  def t_OR(self, t):
+    r'(\s+OR\s+|\s+or\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'OR'
+    return t
+
+  def t_AND(self, t):
+    r'(\s+AND\s+|\s+and\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'AND'
+    return t  
+  
+  def t_NOT(self, t):
+    r'(\s+NOT\s+|\s+not\s+|!=)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = '!=' 
+    return t     
+    
+  t_GREATERTHANEQUAL = r'>='  
+  t_LESSTHANEQUAL = r'<='  
+  t_GREATERTHAN = r'>'
+  t_LESSTHAN = r'<'     
+
+  def t_WORD(self, t):
+    r'[\x7F-\xFF\w\d\/~!@#$%^&*()_+\n][\x7F-\xFF\w\d\/~!@#$%^&*()_+\n]*'
+    #r'[\x7F-\xFF\w\d\/%][\x7F-\xFF\w\d\/%]*'
+    # WORD may contain arbitrary letters and numbers without white space
+    # WORD may contain '%' but not at the beginning or end (otherwise it's KEYWORD)
+    value = t.value.strip()
+    t.value = "%s" %value
+    return t     
+  
+  def t_WORDSET(self, t):
+    r'"[\x7F-\xFF\w\d\s\/~!@#$%^&*()_+][\x7F-\xFF\w\d\s\/~!@#$%^&*()_+]*"'
+    #r'"[\x7F-\xFF\w\d\s/%][\x7F-\xFF\w\d\s/%]*"'
+    # WORDSET is a combination of WORDs separated by white space
+    # and starting/ending with "
+    value = t.value.replace('"', '').strip()
+    t.value = "%s" %value
+    return t 
+        
+  def quoteSQLString(self, value, format):
+    """ Return a quoted string of the value. """
+    if isinstance(value, (int, long,)):
+      return str(value)
+    return "'%s'" %value
+
+     
+##  def buildSQLExpressionFromSearchString(self, key, value, format, mode, range_value, stat__):
+##    """ Tokenize/analyze passed string value and generate SQL query expressions. """
+##    where_expressions = []
+##    select_expressions = []
+##    tokens = self.tokenize(value)
+##    operators_mapping_list = self.groupByOperator(tokens)
+##    
+##    # find if any logical operator exists
+##    tokens_values = []
+##    logical_operator_found = 0
+##    for token in tokens:
+##      if token.type not in ('WORDSET', 'WORD',):
+##        logical_operator_found = 1
+##        break
+##      tokens_values.append(token.value.replace("'", ""))
+##      
+##    # build expressions
+##    if not logical_operator_found:
+##      # no logical operator found so we assume that we search for a combination of words
+##      where_expressions.append("%s = '%s'" %(key, ' '.join(tokens_values)))
+##    else:
+##      # in the search string we have explicitly defined an operator
+##      for item in operators_mapping_list:
+##        row_tokens_values = []
+##        tokens = item['tokens']
+##        operator = item['operator']
+##        operator_value = None
+##        if operator is not None:
+##          # operator is standalone expression
+##          operator_value = operator.value
+##          where_expressions.append('%s' %operator_value)
+##        if len(tokens):
+##          # no it's not a stand alone expression, 
+##          # determine it from list of tokens
+##          operator_value, sub_tokens = self.getOperatorForTokenList(tokens)
+##          row_tokens_values = [x.value for x in sub_tokens]
+##          where_expressions.append("%s %s '%s'" %(key, operator_value, ' '.join(row_tokens_values)))
+##    return where_expressions, select_expressions      
diff --git a/product/ZSQLCatalog/SearchKey/FloatKey.py b/product/ZSQLCatalog/SearchKey/FloatKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e7744dd456215b5d53060812801957d8914b3f2
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/FloatKey.py
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Key import BaseKey
+    
+class FloatKey(BaseKey):
+  """ FloatKey key is an ERP5 portal_catalog search key which is used to render
+      float like SQL expression.
+  """
+  # default type of sub Queries to be generated out fo a search string
+  default_key_type = 'float'
+  
+  tokens =  ('OR', 'AND', 'NOT', 'FLOAT',
+             'GREATERTHAN', 'GREATERTHANEQUAL', 
+             'LESSTHAN', 'LESSTHANEQUAL')
+             
+  sub_operators = ('GREATERTHAN', 'GREATERTHANEQUAL', 
+                    'LESSTHAN', 'LESSTHANEQUAL', 'NOT')
+  
+
+  # Note: Order of placing rules (t_WORD for example) is very important
+  def t_OR(self, t):
+    r'(\s+OR\s+|\s+or\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'OR'
+    return t
+
+  def t_AND(self, t):
+    r'(\s+AND\s+|\s+and\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'AND'
+    return t  
+  
+  def t_NOT(self, t):
+    r'(\s+NOT\s+|\s+not\s+|!=)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = '!=' 
+    return t     
+    
+  t_GREATERTHANEQUAL = r'>='  
+  t_LESSTHANEQUAL = r'<='  
+  t_GREATERTHAN = r'>'
+  t_LESSTHAN = r'<'  
+
+  def t_FLOAT(self, t):
+    r'[\d.][\d.]*'
+    # FLOAT is a float number
+    value = t.value.replace('"', '').strip()
+    t.value = "%s" %value
+    return t   
+  
+  def quoteSQLString(self, value, format):
+    """ Return a quoted string of the value. """
+    # Make sure there is no space in float values
+    return "'%s'" %str(value).replace(' ', '')    
+
+  def quoteSQLKey(self, key, format):
+    """ Return a quoted string of the value. """
+    if format is not None:
+      float_format = format.replace(' ', '')
+      if float_format.find('.') >= 0:
+        precision = len(float_format.split('.')[1])
+        key = "TRUNCATE(%s,%s)" % (key, precision)    
+    return key    
diff --git a/product/ZSQLCatalog/SearchKey/FullTextKey.py b/product/ZSQLCatalog/SearchKey/FullTextKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c1caed08469cc47e209d3ceeff4436682e98f11
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/FullTextKey.py
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Key import BaseKey
+
+SEARCH_MODE_MAPPING = {'in_boolean_mode': 'IN BOOLEAN MODE',
+                       'with_query_expansion': 'WITH QUERY EXPANSION'}
+
+class FullTextKey(BaseKey):
+  """ FullTextKey key is an ERP5 portal_catalog search key which is used to render
+      SQL expression that will try match all possible values using 
+      MySQL's fulltext search support.
+      See syntax see MySQL's FullText search reference: 
+      http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
+  """
+
+  tokens =  ('PLUS', 'MINUS', 'WORD', 'GREATERTHAN', 'LESSTHAN', 'LEFTPARENTHES', 
+             'RIGHTPARENTHES', 'TILDE', 'ASTERISK', 'DOUBLEQUOTE',)
+             
+  # SQL expressions patterns
+  relevance = '%s_relevance'
+  where_match_against = "MATCH %s AGAINST ('%s' %s)"
+  select_match_against_as = "MATCH %s AGAINST ('%s' %s) AS %s"
+  
+  t_PLUS = r'(\+)'
+  t_MINUS = r'(\-)'
+  t_GREATERTHAN = r'(\>)'
+  t_LESSTHAN = r'(\<)'  
+  t_LEFTPARENTHES = r'(\()'    
+  t_RIGHTPARENTHES = r'(\))'
+  t_TILDE = r'(\~)'   
+  t_ASTERISK = r'(\*)'
+  t_DOUBLEQUOTE = r'(\")'      
+  
+  def t_WORD(self, t):
+    r'[\x7F-\xFF\w\d\/!@#$%^&_][\x7F-\xFF\w\d\/!@#$%^&_]*'
+    #r'[\x7F-\xFF\w\d][\x7F-\xFF\w\d]*'
+    # WORD may contain arbitrary letters and numbers without white space
+    word_value = t.value
+    t.value = "'%s'" %word_value
+    return t
+
+  def buildSQLExpression(self, key, value, 
+                         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 == '':
+      # 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 
+      for token in tokens:
+        if token.type != 'WORD':
+          mode = SEARCH_MODE_MAPPING['in_boolean_mode']
+          break
+    # 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 = []
+    where_expression = self.where_match_against %(key, value, mode)
+    if not stat__:
+      # stat__ is an internal implementation artifact to prevent adding
+      # select_expression for countFolder    
+      select_expression_list = [self.select_match_against_as %(key, value, mode, relevance_key1),]
+      if  relevance_key2 is not None:
+        select_expression_list.append(self.select_match_against_as %(key, value, mode, relevance_key2))
+    return where_expression, select_expression_list
diff --git a/product/ZSQLCatalog/SearchKey/Key.py b/product/ZSQLCatalog/SearchKey/Key.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a0f661f9db88aad912e02976c1d20a35197556b
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/Key.py
@@ -0,0 +1,245 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+    
+from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery as Query
+from Products.ZSQLCatalog.Query.ComplexQuery import ComplexQuery
+from Products.ZSQLCatalog.SQLCatalog import getSearchKeyInstance
+
+import ply.yacc as yacc
+import ply.lex as lex
+         
+class BaseKey:
+  """ BaseKey is a base class that implements a parser of 
+      search grammar used in ERP5. It also implements all generic 
+      search key class methods."""
+  
+  # main logical operators
+  operators = ('OR', 'AND',)
+  default_operator = '='
+  
+  # in ERP5 search grammer white space is extremely important
+  # so we can not ignore it.
+  #t_ignore  = ' \t' 
+  
+  # no need to rack down line numbers
+  #def t_newline(self, t):
+  #  r'\n+'
+  #  #t.lexer.lineno += len(t.value)
+  
+  def t_error(self, t):
+    #print "Illegal character '%s'" % t.value[0]
+    t.lexer.skip(1)
+    
+  def p_error(self, p):
+    pass
+ 
+  def build(self, **kwargs):
+    """ This method will initialize respective search key class with 
+        tokens' definitions. """
+    self.lexer = lex.lex(object = self, **kwargs)
+  
+  def tokenize(self, data):
+    """ Return list of tokens according to respective 
+        search key tokens' definitions. """
+    result = []
+    self.lexer.input(data)
+    while 1:
+      tok = self.lexer.token()
+      if not tok: 
+        break
+      result.append(tok) 
+    return result
+
+  # Grouping of tokens
+  def getOperatorForTokenList(self, tokens):
+    """ Generic implementation that will return respective 
+        operator for a token list. The first found occurence wins."""
+    token = tokens[0]        
+    token_type = token.type
+    if token_type in self.sub_operators:
+      return token.value, tokens[1:]
+    else:
+      return self.default_operator, tokens    
+        
+  def groupByLogicalOperator(self, tokens, logical_operator ='OR'):
+    """ Split tokens list into one or many OR concatanated tokens list 
+    """
+    sub_tokens_or_groups = []
+    tmp_token_list = []
+    for token in tokens:
+      if token.type != logical_operator:
+        tmp_token_list.append(token)
+      else:
+        sub_tokens_or_groups.append(tmp_token_list)
+        tmp_token_list = []
+    # append remainig last tokens
+    sub_tokens_or_groups.append(tmp_token_list)
+    return sub_tokens_or_groups    
+  
+  # SQL quoting (each search key should override them it if needed)
+  def quoteSQLKey(self, key, format):
+    """ Return a quoted string of the value. """
+    return key  
+    
+  def quoteSQLString(self, value, format):
+    """ Return a quoted string of the value. """
+    return "'%s'" %value    
+  
+  # SQL generation
+  def buildSQLExpression(self, key, value, 
+                         format = None, mode = None, range_value = None, stat__=0):
+    """ Generic implementation. Leave details to respective key. """
+    if range_value is not None:
+      # if range_value we handle directly (i.e no parsing of search string)
+      where_expressions, select_expressions = \
+         self.buildSQLExpressionFromRange(key, value, 
+                                          format, mode, range_value, stat__) 
+    else:
+      # search string parsing is needed
+      where_expressions, select_expressions = \
+        self.buildSQLExpressionFromSearchString(key, str(value), 
+                                                format, mode, range_value, stat__) 
+    return where_expressions, select_expressions    
+    
+  def buildSQLExpressionFromSearchString(self, key, value, format, mode, range_value, stat__):
+    complex_query = self.buildQuery(key, value, format, mode, range_value, stat__)
+    if complex_query is None:
+      # Query could not be generated from search string
+      sql_expression = {'where_expression': '1', 
+                        'select_expression_list': []}
+    else:
+      sql_expression = complex_query(keyword_search_keys = [],
+                                     datetime_search_keys = [], 
+                                     full_text_search_keys = [])
+    return sql_expression['where_expression'], sql_expression['select_expression_list']
+     
+  def buildQuery(self, key, value, format, mode, range_value, stat__):
+    """ Build Query """
+    query_list = []   
+    # tokenize searchs string into tokens for Search Key
+    tokens = self.tokenize(value)
+    
+    # split tokens list into one or more 'OR' tokens lists
+    tokens_or_groups = self.groupByLogicalOperator(tokens, 'OR')
+    
+    # remove empty tokens lists
+    tokens_or_groups = filter(lambda x: len(x), tokens_or_groups)
+    
+    # get a ComplexQuery for a sub token list    
+    for tokens_or_group in tokens_or_groups:
+      query = self.buildQueryForTokenList(tokens_or_group, key, value, format)
+      if query is not None:
+        # query could be generated for token list
+        query_list.append(query)
+    
+    if len(query_list):
+      # join query list in one really big ComplexQuery
+      return ComplexQuery(*query_list, 
+                          **{'operator':'OR'}) 
+
+  def buildQueryForTokenList(self, tokens, key, value, format):
+    """ Build a ComplexQuery for a token list """
+    query_list = []
+    logical_groups = self.groupByLogicalOperator(tokens, 'AND')
+    for group_tokens in logical_groups:
+      token_values = [x.value for x in group_tokens]
+      sub_operator, sub_tokens = self.getOperatorForTokenList(group_tokens)
+      sub_tokens_values = [x.value for x in sub_tokens]
+      query_kw = {key: ' '.join(sub_tokens_values),
+                  'type': self.default_key_type,
+                  'format': format,
+                  'range': sub_operator}
+      query_list.append(Query(**query_kw))
+
+    # join query list in one really big ComplexQuery
+    complex_query = ComplexQuery(*query_list, 
+                                 **{'operator': 'AND'})
+    return complex_query                       
+                   
+  def buildSQLExpressionFromRange(self, key, value, format, mode, range_value, stat__):
+    """ This method will generate SQL expressions
+       from explicitly passed list of values and 
+       range_value in ('min', 'max', ..)"""
+    key = self.quoteSQLKey(key, format)  
+    where_expression = ''
+    select_expressions = []
+    if isinstance(value, (list, tuple)):
+      if len(value) > 1:
+        # value should contain at least two items
+        query_min = self.quoteSQLString(value[0], format)
+        query_max = self.quoteSQLString(value[1], format)
+      else:
+        # value contains only one item
+        query_min = query_max = self.quoteSQLString(value[0], format)          
+    else:
+      query_min = query_max = self.quoteSQLString(value, format)
+    if range_value == 'min':
+      where_expression = "%s >= %s" % (key, query_min)
+    elif range_value == 'max':
+      where_expression = "%s < %s" % (key, query_max)
+    elif range_value == 'minmax' :
+      where_expression = "%s >= %s AND %s < %s" % (key, query_min, key, query_max)
+    elif range_value == 'minngt' :
+      where_expression = "%s >= %s AND %s <= %s" % (key, query_min, key, query_max)
+    elif range_value == 'ngt':
+      where_expression =  "%s <= %s" % (key, query_max)
+    elif range_value == 'nlt':
+      where_expression = "%s > %s" % (key, query_max)
+    elif range_value == 'like':
+      where_expression = "%s LIKE %s" % (key, query_max)
+    elif range_value == 'not_like':
+      where_expression = "%s NOT LIKE %s" % (key, query_max)
+    elif range_value in ('=', '>', '<', '>=', '<=','!=',):
+      where_expression = "%s %s %s" % (key, range_value, query_max)    
+    return where_expression, select_expressions
+
+    
+    
+##  def groupByOperator(self, tokens, group_by_operators_list = operators):
+##    """ Generic implementation of splitting tokens into logical
+##        groups defided by respective list of logical operator
+##        defined for respective search key.  """
+##    items = []
+##    last_operator = None
+##    operators_mapping_list = []
+##    last_operator = {'operator': None,
+##                     'tokens': []}
+##    for token in tokens:
+##      token_type = token.type
+##      token_value = token.value
+##      if token_type in group_by_operators_list:
+##        # (re) init it
+##        last_operator = {'operator': token,
+##                         'tokens': []}
+##        operators_mapping_list.append(last_operator)
+##      else:
+##        # not an operator just a value token
+##        last_operator['tokens'].append(token)
+##        if last_operator not in operators_mapping_list:
+##          operators_mapping_list.append(last_operator)
+##    return operators_mapping_list      
diff --git a/product/ZSQLCatalog/SearchKey/KeyWordKey.py b/product/ZSQLCatalog/SearchKey/KeyWordKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bb9dedb09936dad56b146186aa2097de8d15826
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/KeyWordKey.py
@@ -0,0 +1,236 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery as Query
+from Products.ZSQLCatalog.Query.ComplexQuery import ComplexQuery
+from Products.ZSQLCatalog.SQLCatalog import getSearchKeyInstance
+from Key import BaseKey
+from pprint import pprint
+
+class KeyWordKey(BaseKey):
+  """ KeyWordKey key is an ERP5 portal_catalog search key which is used to render
+      SQL expression that will try to match all possible values in a greedy manner.
+      It supports following special operator ['=', '%', '>' , '>=', '<', '<='] in
+      addition to main logical operators like ['OR', 'or', 'AND', 'and'].
+      
+      Examples for title column: 
+        * 'foo or bar'  --> "title LIKE '%foo%' OR title LIKE '%bar%'"
+        * 'foo or =bar'  --> "title LIKE '%foo%' OR title = 'bar'"
+        * 'Organisation Module' -->  "title LIKE '%Organisation Module%'"
+        * '"Organisation Module"' --> "title LIKE '%Organisation Module%'"
+        * '="Organisation Module"' --> "title  = 'Organisation Module'"
+    
+  """
+  
+  tokens =  ('OR', 'AND', 'NOT', 
+             'KEYWORD', 'WORDSET', 'WORD', 'EXPLICITEQUALLITYWORD',
+             'GREATERTHAN', 'GREATERTHANEQUAL', 
+             'LESSTHAN', 'LESSTHANEQUAL')
+  
+  sub_operators = ('GREATERTHAN', 'GREATERTHANEQUAL', 
+                    'LESSTHAN', 'LESSTHANEQUAL', 'NOT')
+  
+  # this is the default operator
+  default_operator = 'like'  
+  
+  # if token's list starts with left sided operator
+  # use this map to transfer it to range operator
+  token_operator_range_map = {'like': 'like',
+                              '!=': 'not_like',
+                              '=': '=',}
+                    
+  # Note: Order of placing rules (t_WORD for example) is very important
+  def t_OR(self, t):
+    r'(\s+OR\s+|\s+or\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'OR'
+    return t
+
+  def t_AND(self, t):
+    r'(\s+AND\s+|\s+and\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'AND'
+    return t  
+
+  def t_NOT(self, t):
+    r'(\s+NOT\s+|\s+not\s+|!=)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = t.value.upper().strip()
+    return t     
+
+  t_GREATERTHANEQUAL = r'>='
+  t_LESSTHANEQUAL = r'<='
+  t_GREATERTHAN = r'>'
+  t_LESSTHAN = r'<'
+
+  def t_EXPLICITEQUALLITYWORD(self, t):
+    r'=[\x7F-\xFF\w\d\/~!@#$^&*()_+][\x7F-\xFF\w\d\/~!@#$^&*()_+]*'
+    # EXPLICITEQUALLITYWORD may contain arbitrary letters and numbers without white space
+    # EXPLICITEQUALLITYWORD must contain '=' at the beginning
+    value = t.value.strip()
+    # get rid of leading '='
+    t.value = value[1:]
+    return t        
+    
+  def t_KEYWORD(self, t):
+    r'%?[\x7F-\xFF\w\d/~!@#$%^&*()_+][\x7F-\xFF\w\d/~!@#$%^&*()_+]*%?'
+    # KEYWORD may starts(1) and may ends (2) with '%' but always must either #1 or #2
+    # be true. It may contains arbitrary letters, numbers and white space
+    value = t.value.strip()
+    if not value.startswith('%') and not value.endswith('%'):  
+      t.type = 'WORD'  
+    t.value = value
+    return t    
+    
+  def t_WORD(self, t):
+    r'[\x7F-\xFF\w\d\/~!@#$^&*()_+][\x7F-\xFF\w\d\/~!@#$^&*()_+]*'
+    # WORD may contain arbitrary letters and numbers without white space
+    # WORD may contain '%' but not at the beginning or end (otherwise it's KEYWORD)
+    value = t.value.strip()
+    t.value = value
+    return t     
+  
+  def t_WORDSET(self, t):
+    r'=?"[\x7F-\xFF\w\d\s\/~!@#$%^&*()_+][\x7F-\xFF\w\d\s\/~!@#$%^&*()_+]*"'
+    # WORDSET is a combination of WORDs separated by white space
+    # and starting/ending with " (optionally with '=')
+    value = t.value.replace('"', '')
+    t.value = "%s" %value
+    return t 
+    
+  def quoteSQLString(self, value, format):
+    """ Return a quoted string of the value. """
+    return "'%s'" %value
+  
+  def getOperatorForTokenList(self, tokens):
+    """ Generic implementation that will return respective 
+        operator for a token list. The first found occurence wins."""
+    token = tokens[0]        
+    token_type = token.type
+    if token_type in self.sub_operators:
+      return token.value, tokens[1:]
+    elif token.type == 'EXPLICITEQUALLITYWORD':
+      # even though it's keyword key we can still explicitly define 
+      # that we want equality
+      return '=', tokens
+    else:
+      return self.default_operator, tokens  
+  
+  def buildQueryForTokenList(self, tokens, key, value, format):
+    """ Build a ComplexQuery for a token list """
+    query_list = []
+    for group_tokens in self.groupByLogicalOperator(tokens, 'AND'):
+      token_values = [x.value for x in group_tokens]
+      sub_operator, sub_tokens = self.getOperatorForTokenList(group_tokens)
+      first_token = sub_tokens[0]
+      range = self.token_operator_range_map.get(sub_operator)
+      
+      sub_tokens_values = [x.value for x in sub_tokens]
+      right_side_expression = ' '.join(sub_tokens_values)
+      if first_token.type == 'WORDSET' and first_token.value.startswith('='):
+        range = '='
+        right_side_expression = first_token.value[1:]
+      elif first_token.type in ('WORDSET', 'WORD',) and range == 'like':
+        # add trailing and leading '%' to get more results
+        right_side_expression = '%%%s%%' %right_side_expression
+      query_kw = {key: right_side_expression,
+                  'range': range}
+      query_list.append(Query(**query_kw))
+
+    # join query list in one really big ComplexQuery
+    complex_query = ComplexQuery(*query_list, 
+                                 **{'operator': 'AND'})
+    return complex_query
+
+    
+##  def buildSQLExpressionFromSearchString(self, key, value, format, mode, range_value, stat__):
+##    """ Tokenize/analyze passed string value and generate SQL query expressions. """
+##    where_expressions = []
+##    select_expressions = []
+##    tokens = self.tokenize(value)
+##    operators_mapping_list = self.groupByOperator(tokens)
+##    
+##    # find if any logical operator exists
+##    tokens_values = []
+##    logical_operator_found = 0
+##    for token in tokens:
+##      if token.type not in ('WORD',):
+##        logical_operator_found = 1
+##        break
+##      tokens_values.append(token.value.replace("'", ""))
+##      
+##    # build expressions
+##    if not logical_operator_found:
+##      # no logical operator found so we assume that we search 
+##      # for a combination of words
+##      where_expressions.append("%s LIKE '%%%s%%'" %(key, ' '.join(tokens_values)))
+##    else:
+##      # in the search string we have explicitly defined an operator
+##      for item in operators_mapping_list:
+##        row_tokens_values = []
+##        tokens = item['tokens']
+##        operator = item['operator']
+##        operator_value = None
+##        if operator is not None:
+##          # operator is standalone expression
+##          where_expressions.append('%s' %operator.value)
+##        if len(tokens):
+##          # no it's not a stand alone expression, 
+##          # determine it from list of tokens
+##          sub_where_expression = ''
+##          tokens_number = len(tokens)
+##          if tokens_number == 1:
+##            # no left sided operator (<, >, >=, <=) found
+##            token = tokens[0]
+##            if token.type == 'WORD':
+##              sub_where_expression = "LIKE '%%%s%%'" %token.value
+##            elif token.type == 'KEYWORD':
+##              sub_where_expression = "LIKE '%s'" %token.value
+##            elif token.type == 'EXPLICITEQUALLITYWORD':
+##             sub_where_expression = "= '%s'" %token.value
+##            elif token.type == 'WORDSET' and token.value.startswith('='):
+##              # if WORDSET starts with '=' it's an equality
+##              sub_where_expression = " = '%s'" %token.value[1:]
+##            else:
+##              sub_where_expression = "LIKE '%%%s%%'" %token.value
+##          else:
+##            # we have two or more tokens, by definition first one should be 
+##            # logical operator like (<, >, >=, <=)
+##            operator = tokens[0]
+##            operator_value = operator.value
+##            if operator.type in ('KEYWORD', 'WORDSET', 'WORD'):
+##              # no operator for this token list, assume it's 'LIKE'
+##              sub_where_expression = "LIKE '%s'" %' '.join([x.value for x in tokens])
+##            else:
+##              # we have operator and by convention if operator is used it's applyied to one token only
+##              sub_where_expression = "%s'%s'" %(operator_value, tokens[1].value)
+##          where_expressions.append('%s %s' %(key, sub_where_expression))
+##    return where_expressions, select_expressions
diff --git a/product/ZSQLCatalog/SearchKey/RawKey.py b/product/ZSQLCatalog/SearchKey/RawKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..90f3e84b67a5ad9f5d0a1764e5f7a3d542e0c9c7
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/RawKey.py
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+class RawKey:
+  """ RawKey key is an ERP5 portal_catalog search key which is used to render
+      SQL expression that will match exactly what's passed to it using equality ."""
+
+  def build(self, **kwargs):
+    # this key doesn't require parsing
+    # It's required to implement it as it's used ONLY for ExactMath
+    pass
+  
+  def buildSQLExpression(self, key, value, 
+                         format=None, mode=None, range_value=None, stat__=None):
+    where_expression = "%s = '%s'" %(key, value)
+    return where_expression, [] 
diff --git a/product/ZSQLCatalog/SearchKey/ScriptableKey.py b/product/ZSQLCatalog/SearchKey/ScriptableKey.py
new file mode 100644
index 0000000000000000000000000000000000000000..47e645c4884ef6db9d23132a941af7d3a28fc98c
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/ScriptableKey.py
@@ -0,0 +1,194 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery as Query
+from Products.ZSQLCatalog.Query.ComplexQuery import ComplexQuery
+from Products.ZSQLCatalog.SQLCatalog import getSearchKeyInstance
+from Products.PythonScripts.Utility import allow_class
+
+from Key import BaseKey
+from pprint import pprint 
+
+# these keys are used to build query in case for ScriptableKey 
+# when no key was specified in fornt of value
+DEFAULT_SEARCH_KEYS = ('SearchableText', 'reference', 'title',)
+
+class KeyMappingKey(BaseKey):
+  """ Usable lexer class used (internally) by ScriptableKey lexer than can parse following:
+      VALUE OPERATOR VALUE
+      
+      Examples:
+        * "portal_type : Person"
+        * "creation_date > 2007-01-01"
+  """
+
+  tokens =  ('OPERATOR', 'COLONOPERATOR', 'VALUE',)
+  
+  t_OPERATOR = r'>=|<=|>|<'
+  t_VALUE = r'[\x7F-\xFF\w\d\/~!@#$^&*()_+-][\x7F-\xFF\w\d\/~!@#$^&*()_+-]*'
+    
+  def t_COLONOPERATOR(self, t):
+    r':'
+    # ':' is the same as '=' (equality)
+    t.value = '='
+    return t
+ 
+  
+class ScriptableKey(BaseKey):
+  """ KeyWordKey key is an ERP5 portal_catalog search key which is used to generate a 
+      ComplexQuery instance out of an arbitrary search string.
+      
+      Examples: 
+        * "John Doe AND portal_type:Person AND creation_date > 2007-01-01" 
+        
+        would be turned into following ComplexQuery:
+        
+        *  ComplexQuery(Query(portal_type='Person'),
+                       Query(creation_date='2007-01-01', operator='>'),
+                       ComplexQuery(Query(searchable_text='John Doe'),
+                                    Query(title='John Doe'),
+                                    Query(reference='John Doe'),
+                                    operator='OR')
+                       operator='AND'))
+  """
+  sub_operators =  ('GREATERTHAN', 'GREATERTHANEQUAL', 
+                    'LESSTHAN', 'LESSTHANEQUAL',)
+  
+  tokens =  ('OR', 'AND',  
+             'DATE', 'WORD', 'KEYMAPPING',
+             'GREATERTHAN', 'GREATERTHANEQUAL', 
+             'LESSTHAN', 'LESSTHANEQUAL', 'EQUAL')
+             
+  t_GREATERTHANEQUAL = r'>='  
+  t_LESSTHANEQUAL = r'<='  
+  t_GREATERTHAN = r'>'
+  t_LESSTHAN = r'<'
+  t_EQUAL = r'='             
+             
+  # Note: Order of placing rules (t_WORD for example) is very important
+  def t_OR(self, t):
+    r'(\s+OR\s+|\s+or\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'OR'
+    return t
+
+  def t_AND(self, t):
+    r'(\s+AND\s+|\s+and\s+)'
+    # operator must have leading and trailing ONLY one white space character
+    # otherwise it's treated as a WORD
+    t.value = 'AND'
+    return t  
+
+  def t_KEYMAPPING(self, t):
+    r'[\x7F-\xFF\w\d\/~!@#$^&*()_+-][\x7F-\xFF\w\d\/~!@#$^&*()_+-]*\s*(>|<|<=|>=|:)\s*[\x7F-\xFF\w\d\/~!@#$^&*()_+-][\x7F-\xFF\w\d\/~!@#$^&*()_+-]*'
+    # KEYMAPPING has following format: KEY OPERATOR VALUE 
+    # where OPERATOR in ['<', '>', '<=', '>=', ':']
+    # example: 'creation_date < 2007-12-12'
+    value = t.value.strip()
+    t.value = value
+    return t 
+    
+  def t_WORD(self, t):
+    r'[\x7F-\xFF\w\d\/~!@#$^&*()_+][\x7F-\xFF\w\d\/~!@#$^&*()_+]*'
+    # WORD may contain arbitrary letters and numbers without white space
+    # WORD may contain '%' but not at the beginning or end (otherwise it's KEYWORD)
+    value = t.value.strip()
+    t.value = value
+    return t     
+
+  def buildQueryForTokenList(self, tokens):
+    """ Build a ComplexQuery for a token list """
+    query_list = []
+    for group in self.groupByLogicalOperator(tokens, 'AND'):
+      group_tokens = group
+      first_group_token = group_tokens[0]
+      if first_group_token.type == 'KEYMAPPING':
+        # user specified a full sub query definition following this format:
+        # 'key operator value'
+        sub_search_string = group_tokens[0].value
+        keymapping_lexer = getSearchKeyInstance(KeyMappingKey)
+        sub_tokens = keymapping_lexer.tokenize(sub_search_string)
+        sub_tokens_values = [x.value for x in sub_tokens]
+        search_key, search_operator, search_value = sub_tokens_values
+        query_kw = {search_key: search_value,
+                    'range' : search_operator,}
+        query_list.append(Query( **query_kw))
+      elif first_group_token.type in self.sub_operators:
+        # user specified a incomplete sub query definition following this format:
+        # 'operator value'. Assume that he ment to search for 'title' and
+        # use supplied 'operator'
+        search_operator = first_group_token.value
+        simple_query_value = ' '.join([x.value for x in group_tokens[1:]])
+        query_kw = {'title': simple_query_value,
+                    'range' : search_operator,}
+        query_list.append(Query( **query_kw))
+      else:
+        # user specified a VERY incomplete sub query definition following this format:
+        # 'value'. Let's search against most common search_keys and assume operator
+        # is '=' (by default) and try to get as much possible results
+        simple_query_value = ' '.join([x.value for x in group_tokens])
+        sub_query_list = []
+        for default_key in DEFAULT_SEARCH_KEYS:
+          query_kw = {default_key: simple_query_value}
+          sub_query_list.append(Query(**query_kw))
+        query_list.append(ComplexQuery(*sub_query_list, 
+                                       **{'operator':'OR'}))
+    # join query list in one really big ComplexQuery
+    complex_query = ComplexQuery(*query_list, 
+                                 **{'operator':'AND'})
+    return complex_query
+
+    
+  def buildQuery(self, key, value, 
+                format=None, mode=None, range_value=None, stat__=None):
+    """ Build ComplexQuery from passed search string value.
+        When grouping expressions we use the following assumptions 
+        that 'OR' operator has higher priority in a sense:
+        
+         * "John Doe AND portal_type:Person OR creation_date>=2005/12/12"
+         
+         is considered as:
+         
+         * (John Doe AND portal_type:Person) OR (creation_date>=2005/12/12)"
+    """
+    query_list = []
+    tokens = self.tokenize(value)
+    
+    # split tokens list into one or many OR concatanated expressions
+    sub_tokens_or_groups = self.groupByLogicalOperator(tokens, 'OR')
+
+    # get a ComplexQuery for a sub token list    
+    for tokens_or_group in sub_tokens_or_groups:
+      query_list.append(self.buildQueryForTokenList(tokens_or_group))
+      
+    # join query list in one really big ComplexQuery
+    complex_query = ComplexQuery(*query_list, 
+                                 **{'operator':'OR'})      
+    return complex_query
+allow_class(ScriptableKey)
diff --git a/product/ZSQLCatalog/SearchKey/__init__.py b/product/ZSQLCatalog/SearchKey/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/product/ZSQLCatalog/SearchKey/__init__.py
@@ -0,0 +1 @@
+
diff --git a/product/ZSQLCatalog/__init__.py b/product/ZSQLCatalog/__init__.py
index fc7dec6649372d9f019f6c5091e36da3c2cd8883..8926814e06ed98e7cdfe9f009d226d701ed71e4a 100644
--- a/product/ZSQLCatalog/__init__.py
+++ b/product/ZSQLCatalog/__init__.py
@@ -43,5 +43,4 @@ def initialize(context):
 
 from AccessControl import ModuleSecurityInfo, ClassSecurityInfo
 ModuleSecurityInfo('Products.ZSQLCatalog.SQLCatalog').declarePublic(
-            'ComplexQuery', 'Query', 'NegatedQuery')
-
+            'ComplexQuery', 'Query', 'NegatedQuery',)
diff --git a/product/ZSQLCatalog/tests/testZSQLCatalog.py b/product/ZSQLCatalog/tests/testZSQLCatalog.py
index fc06a56016ff3577af5c4429bfcd6b99cad3ef8e..eeb4b5dae17cbc03f49d5c472637e2d723ad9854 100644
--- a/product/ZSQLCatalog/tests/testZSQLCatalog.py
+++ b/product/ZSQLCatalog/tests/testZSQLCatalog.py
@@ -102,9 +102,11 @@ class TestQuery(unittest.TestCase):
   def testSimpleQuery(self):
     q = Query(title='Foo')
     self.assertEquals(
-          dict(where_expression="title = 'Foo'",
+          dict(where_expression="((((title = 'Foo'))))",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
 
   def testQueryMultipleKeys(self):
     # using multiple keys is invalid and raises
@@ -116,7 +118,9 @@ class TestQuery(unittest.TestCase):
     self.assertEquals(
           dict(where_expression="title is NULL",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
 
   def testEmptyQueryNotIgnoreEmptyString(self):
     q = Query(title='')
@@ -127,6 +131,7 @@ class TestQuery(unittest.TestCase):
                select_expression_list=[]),
           q.asSQLExpression(ignore_empty_string=0,
                             keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=[]))
 
   def testEmptyQuery(self):
@@ -135,52 +140,67 @@ class TestQuery(unittest.TestCase):
     self.assertEquals(
           dict(where_expression="1",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
     
   def testMultiValuedQuery(self):
     q = Query(title=['Foo', 'Bar'])
     self.assertEquals(
-          dict(where_expression="(title = 'Foo' OR title = 'Bar')",
+          dict(where_expression="(((((title = 'Foo')))) OR ((((title = 'Bar')))))",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
 
   def testINQuery(self):
     q = Query(title=['Foo', 'Bar'], operator='IN')
     self.assertEquals(
           dict(where_expression="title IN ('Foo', 'Bar')",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
 
   def testEmptyINQuery(self):
     q = Query(title=[], operator='IN')
     self.assertEquals(
           dict(where_expression="0",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
 
   def testMinQuery(self):
     q = Query(title='Foo', range='min')
     self.assertEquals(
           dict(where_expression="title >= 'Foo'",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
     
   def testMaxQuery(self):
     q = Query(title='Foo', range='max')
     self.assertEquals(
           dict(where_expression="title < 'Foo'",
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
 
   # format
   def testDateFormat(self):
-    q = Query(date=DateTime(2001, 02, 03), format='%Y/%m/%d', type='date')
+    date = DateTime(2001, 02, 03)
+    q = Query(date=date, format='%Y/%m/%d', type='date')
     self.assertEquals(
           dict(where_expression=
-            "STR_TO_DATE(DATE_FORMAT(date,'%Y/%m/%d'),'%Y/%m/%d')"
-            " = STR_TO_DATE('2001/02/03','%Y/%m/%d')",
+            "((((date >= '%s' AND date < '%s'))))" \
+                 %(date.toZone('UTC').ISO(), (date + 1).toZone('UTC').ISO()),
                select_expression_list=[]),
-          q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
+          q.asSQLExpression(keyword_search_keys=[], 
+                            datetime_search_keys = [],
+                            full_text_search_keys=[]))
   
   # full text
   def testSimpleQueryFullText(self):
@@ -189,6 +209,7 @@ class TestQuery(unittest.TestCase):
                            select_expression_list=
                         ["MATCH title AGAINST ('Foo' ) AS title_relevance"]),
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=['title']))
 
   def testSimpleQueryFullTextSearchMode(self):
@@ -199,6 +220,7 @@ class TestQuery(unittest.TestCase):
       select_expression_list=
         ["MATCH title AGAINST ('Foo' IN BOOLEAN MODE) AS title_relevance"]),
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=['title']))
   
   def testSimpleQueryFullTextStat__(self):
@@ -209,23 +231,26 @@ class TestQuery(unittest.TestCase):
                     where_expression="MATCH title AGAINST ('Foo' )",
                     select_expression_list=[]),
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=['title'],
                             stat__=1))
 
   def testSimpleQueryKeywordSearchKey(self):
     q = Query(title='Foo')
-    self.assertEquals(dict(where_expression="title LIKE '%Foo%'",
+    self.assertEquals(dict(where_expression="((((title LIKE '%Foo%'))))",
                            select_expression_list=[]),
           q.asSQLExpression(keyword_search_keys=['title'],
+                            datetime_search_keys = [],
                             full_text_search_keys=[]))
 
   def testNegatedQuery(self):
     q1 = Query(title='Foo')
     q = NegatedQuery(q1)
     self.assertEquals(
-        dict(where_expression="(NOT (title = 'Foo'))",
+        dict(where_expression="(NOT (((((title = 'Foo'))))))",
                            select_expression_list=[]),
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=[]))
 
   # complex queries
@@ -234,9 +259,10 @@ class TestQuery(unittest.TestCase):
     q2 = Query(reference='Bar')
     q = ComplexQuery(q1, q2)
     self.assertEquals(
-        dict(where_expression="((title = 'Foo') AND (reference = 'Bar'))",
+        dict(where_expression="((((((title = 'Foo'))))) AND (((((reference = 'Bar'))))))",
                            select_expression_list=[]),
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=[]))
 
   def testNegatedComplexQuery(self):
@@ -246,35 +272,40 @@ class TestQuery(unittest.TestCase):
     q = NegatedQuery(q3)
     self.assertEquals(
       # maybe too many parents here
-     dict(where_expression="(NOT (((title = 'Foo') AND (reference = 'Bar'))))",
+     dict(where_expression="(NOT (((((((title = 'Foo'))))) AND (((((reference = 'Bar'))))))))",
           select_expression_list=[]),
      q.asSQLExpression(keyword_search_keys=[],
+                       datetime_search_keys = [],
                        full_text_search_keys=[]))
 
   
   # forced keys
   def testSimpleQueryForcedKeywordSearchKey(self):
     q = Query(title='Foo', key='Keyword')
-    self.assertEquals("title LIKE '%Foo%'",
+    self.assertEquals("((((title LIKE '%Foo%'))))",
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],
                             full_text_search_keys=[])['where_expression'])
 
   def testSimpleQueryForcedFullText(self):
     q = Query(title='Foo', key='FullText')
     self.assertEquals("MATCH title AGAINST ('Foo' )",
           q.asSQLExpression(keyword_search_keys=[],
+                            datetime_search_keys = [],                            
                             full_text_search_keys=[])['where_expression'])
 
   def testSimpleQueryForcedExactMatch(self):
     q = Query(title='Foo', key='ExactMatch')
     self.assertEquals("title = 'Foo'",
           q.asSQLExpression(keyword_search_keys=['title'],
+                            datetime_search_keys = [],  
                             full_text_search_keys=[])['where_expression'])
 
   def testSimpleQueryForcedExactMatchOR(self):
     q = Query(title='Foo% OR %?ar', key='ExactMatch')
     self.assertEquals("title = 'Foo% OR %?ar'",
           q.asSQLExpression(keyword_search_keys=['title'],
+                            datetime_search_keys = [],
                             full_text_search_keys=[])['where_expression'])