Fix ts_headline() edge cases for empty query and empty search text.
authorTom Lane
Thu, 6 Apr 2023 19:52:37 +0000 (15:52 -0400)
committerTom Lane
Thu, 6 Apr 2023 19:52:37 +0000 (15:52 -0400)
tsquery's GETQUERY() macro is only safe to apply to a tsquery
that is known non-empty; otherwise it gives a pointer to garbage.
Before commit 5a617d75d, ts_headline() avoided this pitfall, but
only in a very indirect, nonobvious way.  (hlCover could not reach
its TS_execute call, because if the query contains no lexemes
then hlFirstIndex would surely return -1.)  After that commit,
it fell into the trap, resulting in weird errors such as
"unrecognized operator" and/or valgrind complaints.  In HEAD,
fix this by not calling TS_execute_locations() at all for an
empty query.  In the back branches, add a defensive check to
hlCover() --- that's not fixing any live bug, but I judge the
code a bit too fragile as-is.

Also, both mark_hl_fragments() and mark_hl_words() were careless
about the possibility of empty search text: in the cases where
no match has been found, they'd end up telling mark_fragment() to
mark from word indexes 0 to 0 inclusive, even when there is no
word 0.  This is harmless since we over-allocated the prs->words
array, but it does annoy valgrind.  Fix so that the end index is -1
and thus mark_fragment() will do nothing in such cases.

Bottom line is that this fixes a live bug in HEAD, but in the
back branches it's only getting rid of a valgrind nitpick.
Back-patch anyway.

Per report from Alexander Lakhin.

Discussion: https://postgr.es/m/c27f642d-020b-01ff-ae61-086af287c4fd@gmail.com

src/backend/tsearch/wparser_def.c
src/test/regress/expected/tsearch.out
src/test/regress/sql/tsearch.sql

index a3e5baf9782a03681d80f4c08acdc092a588d232..98029e78fb4766e652f1d5097d35cbbbbd1cd489 100644 (file)
@@ -2039,6 +2039,9 @@ hlCover(HeadlineParsedText *prs, TSQuery query, int max_cover,
                nextpmax;
    hlCheck     ch;
 
+   if (query->size <= 0)
+       return false;           /* empty query matches nothing */
+
    /*
     * We look for the earliest, shortest substring of prs->words that
     * satisfies the query.  Both the pmin and pmax indices must be words
@@ -2343,7 +2346,8 @@ mark_hl_fragments(HeadlineParsedText *prs, TSQuery query, bool highlightall,
    /* show the first min_words words if we have not marked anything */
    if (num_f <= 0)
    {
-       startpos = endpos = curlen = 0;
+       startpos = curlen = 0;
+       endpos = -1;
        for (i = 0; i < prs->curwords && curlen < min_words; i++)
        {
            if (!NONWORDTOKEN(prs->words[i].type))
@@ -2498,7 +2502,7 @@ mark_hl_words(HeadlineParsedText *prs, TSQuery query, bool highlightall,
        if (bestlen < 0)
        {
            curlen = 0;
-           pose = 0;
+           pose = -1;
            for (i = 0; i < prs->curwords && curlen < min_words; i++)
            {
                if (!NONWORDTOKEN(prs->words[i].type))
index 45b92a6338836c7f88b2ae948c0d4f036a2f3abb..4e52edf610fd90f0be8131c48e52d503ce741110 100644 (file)
@@ -1990,6 +1990,27 @@ to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'),
  Lorem ipsum urna.  Nullam nullam ullamcorper urna
 (1 row)
 
+-- Edge cases with empty query
+SELECT ts_headline('english',
+'', ''::tsquery);
+NOTICE:  text-search query doesn't contain lexemes: ""
+LINE 2: '', ''::tsquery);
+            ^
+ ts_headline 
+-------------
+(1 row)
+
+SELECT ts_headline('english',
+'foo bar', ''::tsquery);
+NOTICE:  text-search query doesn't contain lexemes: ""
+LINE 2: 'foo bar', ''::tsquery);
+                   ^
+ ts_headline 
+-------------
+ foo bar
+(1 row)
+
 --Rewrite sub system
 CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT);
 \set ECHO none
index d929210998ae5ec6e37b9b82e45a85440f843451..168eb0b971305015aaa855281a4e4b8b8024202b 100644 (file)
@@ -549,6 +549,12 @@ SELECT ts_headline('english',
 to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'),
 'MaxFragments=100, MaxWords=100, MinWords=1');
 
+-- Edge cases with empty query
+SELECT ts_headline('english',
+'', ''::tsquery);
+SELECT ts_headline('english',
+'foo bar', ''::tsquery);
+
 --Rewrite sub system
 
 CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT);