diff --git a/product/ZSQLCatalog/SearchKey/FullTextKey.py b/product/ZSQLCatalog/SearchKey/FullTextKey.py index 5041b6324000990f045104a5e10b65e978854518..fc22da3d987902415c674f77ff441d9dde2c49f3 100644 --- a/product/ZSQLCatalog/SearchKey/FullTextKey.py +++ b/product/ZSQLCatalog/SearchKey/FullTextKey.py @@ -30,7 +30,7 @@ from SearchKey import SearchKey from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery -from Products.ZSQLCatalog.SearchText import parse +from Products.ZSQLCatalog.SearchText import FullText_parse from Products.ZSQLCatalog.interfaces.search_key import ISearchKey from zope.interface.verify import verifyClass from Products.ZSQLCatalog.SQLCatalog import profiler_decorator @@ -46,7 +46,10 @@ class FullTextKey(SearchKey): get_operator_from_value = False def parseSearchText(self, value, is_column): - return parse(value, is_column) + return FullText_parse(value, is_column) + + def _renderValueAsSearchText(self, value, operator): + return operator.asSearchText(value) @profiler_decorator def _processSearchValue(self, search_value, logical_operator, diff --git a/product/ZSQLCatalog/SearchKey/SphinxSEFullTextKey.py b/product/ZSQLCatalog/SearchKey/SphinxSEFullTextKey.py index b15e448d503895bf34b554878ea7d5cff8a00620..20f4fe8d15b8de5807e40d924834e77102b4b034 100644 --- a/product/ZSQLCatalog/SearchKey/SphinxSEFullTextKey.py +++ b/product/ZSQLCatalog/SearchKey/SphinxSEFullTextKey.py @@ -27,7 +27,7 @@ from SearchKey import SearchKey from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery -from Products.ZSQLCatalog.SearchText import parse +from Products.ZSQLCatalog.SearchText import FullText_parse from Products.ZSQLCatalog.interfaces.search_key import ISearchKey from zope.interface.verify import verifyClass from Products.ZSQLCatalog.SQLCatalog import profiler_decorator @@ -40,7 +40,10 @@ class SphinxSEFullTextKey(SearchKey): get_operator_from_value = False def parseSearchText(self, value, is_column): - return parse(value, is_column) + return FullText_parse(value, is_column) + + def _renderValueAsSearchText(self, value, operator): + return operator.asSearchText(value) @profiler_decorator def _buildQuery(self, operator_value_dict, logical_operator, parsed, group): diff --git a/product/ZSQLCatalog/SearchText/FullTextSearchTextParser.py b/product/ZSQLCatalog/SearchText/FullTextSearchTextParser.py new file mode 100644 index 0000000000000000000000000000000000000000..9801d7c19bc43642c402c659dc34db045a06543c --- /dev/null +++ b/product/ZSQLCatalog/SearchText/FullTextSearchTextParser.py @@ -0,0 +1,75 @@ +############################################################################## +# +# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved. +# Vincent Pelletier <vincent@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from lexer import update_docstrings +from AdvancedSearchTextParser import ValueNode, NotNode, LogicalNode +from AdvancedSearchTextParser import ColumnNode, AdvancedSearchTextParser + +class FullTextSearchTextParser(AdvancedSearchTextParser): + + # IMPORTANT: + # In short: Don't remove any token definition below even if they look + # useless. + # In detail: The lex methods below are redefined here because of ply nice + # feature of prioritizing tokens using the *line* *number* at which they + # are defined. As we inherit those methods from another class from another + # file (which doesn't match this file's content, of course) we must redefine + # wrapper methods to enforce token priority. Kudos to ply for so much + # customisable behaviour. Not. + + def t_LEFT_PARENTHESE(self, t): + return AdvancedSearchTextParser.t_LEFT_PARENTHESE(self, t) + + def t_RIGHT_PARENTHESE(self, t): + return AdvancedSearchTextParser.t_RIGHT_PARENTHESE(self, t) + + def t_OPERATOR(self, t): + return AdvancedSearchTextParser.t_OPERATOR(self, t) + + def t_STRING(self, t): + # Here is the only difference between AdvancedSearchTextParser and this + # class: strings are kept escaped (ie, they are considered as WORDs). + return AdvancedSearchTextParser.t_WORD(self, t) + + def t_COLUMN(self, t): + return AdvancedSearchTextParser.t_COLUMN(self, t) + + def t_OR(self, t): + return AdvancedSearchTextParser.t_OR(self, t) + + def t_AND(self, t): + return AdvancedSearchTextParser.t_AND(self, t) + + def t_NOT(self, t): + return AdvancedSearchTextParser.t_NOT(self, t) + + def t_WORD(self, t): + return AdvancedSearchTextParser.t_WORD(self, t) + +update_docstrings(FullTextSearchTextParser) + diff --git a/product/ZSQLCatalog/SearchText/SearchTextParser.py b/product/ZSQLCatalog/SearchText/SearchTextParser.py index b496c68ea1c39042dded512db9565388dde1639c..0abfb1b0fdf759bf071f04451afddc99e2e323df 100755 --- a/product/ZSQLCatalog/SearchText/SearchTextParser.py +++ b/product/ZSQLCatalog/SearchText/SearchTextParser.py @@ -31,6 +31,7 @@ import threading from AdvancedSearchTextDetector import AdvancedSearchTextDetector from AdvancedSearchTextParser import AdvancedSearchTextParser +from FullTextSearchTextParser import FullTextSearchTextParser from lexer import ParserOrLexerError try: from Products.ZSQLCatalog.SQLCatalog import profiler_decorator @@ -72,6 +73,7 @@ class ParserPool(object): parser_pool = ParserPool() DETECTOR_ID = parser_pool.register(AdvancedSearchTextDetector) PARSER_ID = parser_pool.register(AdvancedSearchTextParser) +FULLTEXT_PARSER_ID = parser_pool.register(FullTextSearchTextParser) def safeParsingDecorator(func): """ @@ -103,6 +105,11 @@ def parse(input, is_column, *args, **kw): result = None return result +@profiler_decorator +@safeParsingDecorator +def FullText_parse(input, is_column, *args, **kw): + return parser_pool.get(FULLTEXT_PARSER_ID)(input, is_column, *args, **kw) + if __name__ == '__main__': class Query: def __init__(self, column, value, comparison_operator='='): diff --git a/product/ZSQLCatalog/SearchText/__init__.py b/product/ZSQLCatalog/SearchText/__init__.py index 8ba00f5ff1b560e549b600c977ee38ebe582adc3..922fb34bb1157fa1062c1cf386d3da555ccf7869 100644 --- a/product/ZSQLCatalog/SearchText/__init__.py +++ b/product/ZSQLCatalog/SearchText/__init__.py @@ -1,2 +1,2 @@ -from SearchTextParser import parse, isAdvancedSearchText +from SearchTextParser import parse, isAdvancedSearchText, FullText_parse diff --git a/product/ZSQLCatalog/tests/testSQLCatalog.py b/product/ZSQLCatalog/tests/testSQLCatalog.py index 10d0d5ce6ffe380470494ba4756de5a94ad39d55..a0d675518e1131ce299065a9a23ca81756ac7bc6 100644 --- a/product/ZSQLCatalog/tests/testSQLCatalog.py +++ b/product/ZSQLCatalog/tests/testSQLCatalog.py @@ -526,6 +526,15 @@ class TestSQLCatalog(unittest.TestCase): fulltext=MatchList(['+a b', 'b +a'])), operator='and'), operator='and'), {'fulltext': '+a b uid:foo'}) + def test_FullTextQuoting(self): + # Quotes must be kept + self.catalog(ReferenceQuery(ReferenceQuery(operator='match', + fulltext='"a"'), operator='and'), + {'fulltext': '"a"'}) + self.catalog(ReferenceQuery(ReferenceQuery(operator='match', + fulltext='"foo" bar "baz"'), operator='and'), + {'fulltext': '"foo" bar "baz"'}) + def test_DefaultKeyTextRendering(self): self.catalog(ReferenceQuery(ReferenceQuery(operator='like', default='a% b'), operator='and'), {'default': 'a% b'})