]> granicus.if.org Git - postgresql/commitdiff
Prevent crash when ts_rewrite() replaces a non-top-level subtree with null.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 11 Dec 2016 18:09:57 +0000 (13:09 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 11 Dec 2016 18:09:57 +0000 (13:09 -0500)
When ts_rewrite()'s replacement argument is an empty tsquery, it's supposed
to simplify any operator nodes whose operand(s) become NULL; but it failed
to do that reliably, because dropvoidsubtree() only examined the top level
of the result tree.  Rather than make a second recursive pass, let's just
give the responsibility to dofindsubquery() to simplify while it's doing
the main replacement pass.  Per report from Andreas Seltenreich.

Artur Zakirov, with some cosmetic changes by me.  Back-patch to all
supported branches.

Discussion: https://postgr.es/m/8737i01dew.fsf@credativ.de

src/backend/utils/adt/tsquery_rewrite.c
src/test/regress/expected/tsearch.out
src/test/regress/sql/tsearch.sql

index 961a8ffc89893dccdec0d49250b56c3e98dd7727..007aa743845f439b538d33daf2002d37f87777ba 100644 (file)
@@ -140,6 +140,12 @@ findeq(QTNode *node, QTNode *ex, QTNode *subs, bool *isfind)
 
                                node->nchild = j;
 
+                               /*
+                                * At this point we might have a node with zero or one child,
+                                * which should be simplified.  But we leave it to our caller
+                                * (dofindsubquery) to take care of that.
+                                */
+
                                /*
                                 * Re-sort the node to put new child in the right place.  This
                                 * is a bit bogus, because it won't matter for findsubquery's
@@ -186,6 +192,15 @@ findeq(QTNode *node, QTNode *ex, QTNode *subs, bool *isfind)
  * Recursive guts of findsubquery(): attempt to replace "ex" with "subs"
  * at the root node, and if we failed to do so, recursively match against
  * child nodes.
+ *
+ * Delete any void subtrees resulting from the replacement.
+ * In the following example '5' is replaced by empty operand:
+ *
+ *       AND           ->        6
+ *      /       \
+ *     5        OR
+ *             /  \
+ *        6    5
  */
 static QTNode *
 dofindsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind)
@@ -196,45 +211,33 @@ dofindsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind)
        /* also, since it's a bit expensive, let's check for query cancel. */
        CHECK_FOR_INTERRUPTS();
 
+       /* match at the node itself */
        root = findeq(root, ex, subs, isfind);
 
-       if (root && (root->flags & QTN_NOCHANGE) == 0 && root->valnode->type == QI_OPR)
-       {
-               int                     i;
-
-               for (i = 0; i < root->nchild; i++)
-                       root->child[i] = dofindsubquery(root->child[i], ex, subs, isfind);
-       }
-
-       return root;
-}
-
-/*
- * Delete any void subtrees that may have been inserted when the replacement
- * subtree is void.
- */
-static QTNode *
-dropvoidsubtree(QTNode *root)
-{
-       if (!root)
-               return NULL;
-
-       if (root->valnode->type == QI_OPR)
+       /* unless we matched here, consider matches at child nodes */
+       if (root && (root->flags & QTN_NOCHANGE) == 0 &&
+               root->valnode->type == QI_OPR)
        {
                int                     i,
                                        j = 0;
 
+               /*
+                * Any subtrees that are replaced by NULL must be dropped from the
+                * tree.
+                */
                for (i = 0; i < root->nchild; i++)
                {
-                       if (root->child[i])
-                       {
-                               root->child[j] = root->child[i];
+                       root->child[j] = dofindsubquery(root->child[i], ex, subs, isfind);
+                       if (root->child[j])
                                j++;
-                       }
                }
 
                root->nchild = j;
 
+               /*
+                * If we have just zero or one remaining child node, simplify out this
+                * operator node.
+                */
                if (root->nchild == 0)
                {
                        QTNFree(root);
@@ -267,9 +270,6 @@ findsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind)
 
        root = dofindsubquery(root, ex, subs, &DidFind);
 
-       if (!subs && DidFind)
-               root = dropvoidsubtree(root);
-
        if (isfind)
                *isfind = DidFind;
 
index 1466582c3eac5e2b6241b1f34893094292de3dc5..31dab9309aa20eb0bdfb879529be1f143e8dc841 100644 (file)
@@ -894,6 +894,21 @@ SELECT ts_rewrite( 'bar & new & qq & foo & york', 'SELECT keyword, sample FROM t
  'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | ( 'big' & 'appl' | 'new' & 'york' ) )
 (1 row)
 
+-- Check empty substitution
+SELECT ts_rewrite(to_tsquery('5 & (6 | 5)'), to_tsquery('5'), to_tsquery(''));
+NOTICE:  text-search query doesn't contain lexemes: ""
+ ts_rewrite 
+------------
+ '6'
+(1 row)
+
+SELECT ts_rewrite(to_tsquery('!5'), to_tsquery('5'), to_tsquery(''));
+NOTICE:  text-search query doesn't contain lexemes: ""
+ ts_rewrite 
+------------
+(1 row)
+
 SELECT keyword FROM test_tsquery WHERE keyword @> 'new';
     keyword     
 ----------------
index 6035d5e4272c9de170cbb66d15ded92db0c97f70..df2d54581410126923f5eba281c229ddf4cde754 100644 (file)
@@ -316,6 +316,9 @@ SELECT ts_rewrite( 'moscow', 'SELECT keyword, sample FROM test_tsquery');
 SELECT ts_rewrite( 'moscow & hotel', 'SELECT keyword, sample FROM test_tsquery');
 SELECT ts_rewrite( 'bar & new & qq & foo & york', 'SELECT keyword, sample FROM test_tsquery');
 
+-- Check empty substitution
+SELECT ts_rewrite(to_tsquery('5 & (6 | 5)'), to_tsquery('5'), to_tsquery(''));
+SELECT ts_rewrite(to_tsquery('!5'), to_tsquery('5'), to_tsquery(''));
 
 SELECT keyword FROM test_tsquery WHERE keyword @> 'new';
 SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow';