1 /*-------------------------------------------------------------------------
4 * Utilities for reconstructing tsquery
6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
10 * src/backend/utils/adt/tsquery_rewrite.c
12 *-------------------------------------------------------------------------
17 #include "catalog/pg_type.h"
18 #include "executor/spi.h"
19 #include "miscadmin.h"
20 #include "tsearch/ts_utils.h"
21 #include "utils/builtins.h"
25 * If "node" is equal to "ex", return a copy of "subs" instead.
26 * If "ex" matches a subset of node's children, return a modified version
27 * of "node" in which those children are replaced with a copy of "subs".
28 * Otherwise return "node" unmodified.
30 * The QTN_NOCHANGE bit is set in successfully modified nodes, so that
31 * we won't uselessly recurse into them.
32 * Also, set *isfind true if we make a replacement.
35 findeq(QTNode *node, QTNode *ex, QTNode *subs, bool *isfind)
37 /* Can't match unless signature matches and node type matches. */
38 if ((node->sign & ex->sign) != ex->sign ||
39 node->valnode->type != ex->valnode->type)
42 /* Ignore nodes marked NOCHANGE, too. */
43 if (node->flags & QTN_NOCHANGE)
46 if (node->valnode->type == QI_OPR)
48 /* Must be same operator. */
49 if (node->valnode->qoperator.oper != ex->valnode->qoperator.oper)
52 if (node->nchild == ex->nchild)
55 * Simple case: when same number of children, match if equal.
56 * (This is reliable when the children were sorted earlier.)
60 /* Match; delete node and return a copy of subs instead. */
65 node->flags |= QTN_NOCHANGE;
72 else if (node->nchild > ex->nchild && ex->nchild > 0)
75 * AND and OR are commutative/associative, so we should check if a
76 * subset of the children match. For example, if node is A|B|C,
77 * and ex is B|C, we have a match after we notionally convert node
78 * to A|(B|C). This does not work for NOT or PHRASE nodes, but we
79 * can't get here for those node types because they have a fixed
82 * Because we expect that the children are sorted, it suffices to
83 * make one pass through the two lists to find the matches.
90 /* Assert that the subset rule is OK */
91 Assert(node->valnode->qoperator.oper == OP_AND ||
92 node->valnode->qoperator.oper == OP_OR);
94 /* matched[] will record which children of node matched */
95 matched = (bool *) palloc0(node->nchild * sizeof(bool));
98 while (i < node->nchild && j < ex->nchild)
100 int cmp = QTNodeCompare(node->child[i], ex->child[j]);
111 /* node->child[i] has no match, ignore it */
116 /* ex->child[j] has no match; we can give up immediately */
121 if (nmatched == ex->nchild)
123 /* collapse out the matched children of node */
125 for (i = 0; i < node->nchild; i++)
128 QTNFree(node->child[i]);
130 node->child[j++] = node->child[i];
133 /* and instead insert a copy of subs */
136 subs = QTNCopy(subs);
137 subs->flags |= QTN_NOCHANGE;
138 node->child[j++] = subs;
144 * At this point we might have a node with zero or one child,
145 * which should be simplified. But we leave it to our caller
146 * (dofindsubquery) to take care of that.
150 * Re-sort the node to put new child in the right place. This
151 * is a bit bogus, because it won't matter for findsubquery's
152 * remaining processing, and it's insufficient to prepare the
153 * tree for another search (we would need to re-flatten as
154 * well, and we don't want to do that because we'd lose the
155 * QTN_NOCHANGE marking on the new child). But it's needed to
156 * keep the results the same as the regression tests expect.
168 Assert(node->valnode->type == QI_VAL);
170 if (node->valnode->qoperand.valcrc != ex->valnode->qoperand.valcrc)
172 else if (QTNEq(node, ex))
177 node = QTNCopy(subs);
178 node->flags |= QTN_NOCHANGE;
192 * Recursive guts of findsubquery(): attempt to replace "ex" with "subs"
193 * at the root node, and if we failed to do so, recursively match against
196 * Delete any void subtrees resulting from the replacement.
197 * In the following example '5' is replaced by empty operand:
206 dofindsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind)
208 /* since this function recurses, it could be driven to stack overflow. */
211 /* also, since it's a bit expensive, let's check for query cancel. */
212 CHECK_FOR_INTERRUPTS();
214 /* match at the node itself */
215 root = findeq(root, ex, subs, isfind);
217 /* unless we matched here, consider matches at child nodes */
218 if (root && (root->flags & QTN_NOCHANGE) == 0 &&
219 root->valnode->type == QI_OPR)
225 * Any subtrees that are replaced by NULL must be dropped from the
228 for (i = 0; i < root->nchild; i++)
230 root->child[j] = dofindsubquery(root->child[i], ex, subs, isfind);
238 * If we have just zero or one remaining child node, simplify out this
241 if (root->nchild == 0)
246 else if (root->nchild == 1 && root->valnode->qoperator.oper != OP_NOT)
248 QTNode *nroot = root->child[0];
259 * Substitute "subs" for "ex" throughout the QTNode tree at root.
261 * If isfind isn't NULL, set *isfind to show whether we made any substitution.
263 * Both "root" and "ex" must have been through QTNTernary and QTNSort
264 * to ensure reliable matching.
267 findsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind)
269 bool DidFind = false;
271 root = dofindsubquery(root, ex, subs, &DidFind);
280 tsquery_rewrite_query(PG_FUNCTION_ARGS)
282 TSQuery query = PG_GETARG_TSQUERY_COPY(0);
283 text *in = PG_GETARG_TEXT_PP(1);
284 TSQuery rewrited = query;
285 MemoryContext outercontext = CurrentMemoryContext;
286 MemoryContext oldcontext;
293 if (query->size == 0)
295 PG_FREE_IF_COPY(in, 1);
296 PG_RETURN_POINTER(rewrited);
299 tree = QT2QTN(GETQUERY(query), GETOPERAND(query));
303 buf = text_to_cstring(in);
307 if ((plan = SPI_prepare(buf, 0, NULL)) == NULL)
308 elog(ERROR, "SPI_prepare(\"%s\") failed", buf);
310 if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL)
311 elog(ERROR, "SPI_cursor_open(\"%s\") failed", buf);
313 SPI_cursor_fetch(portal, true, 100);
315 if (SPI_tuptable == NULL ||
316 SPI_tuptable->tupdesc->natts != 2 ||
317 SPI_gettypeid(SPI_tuptable->tupdesc, 1) != TSQUERYOID ||
318 SPI_gettypeid(SPI_tuptable->tupdesc, 2) != TSQUERYOID)
320 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
321 errmsg("ts_rewrite query must return two tsquery columns")));
323 while (SPI_processed > 0 && tree)
327 for (i = 0; i < SPI_processed && tree; i++)
329 Datum qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull);
335 sdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull);
339 TSQuery qtex = DatumGetTSQuery(qdata);
340 TSQuery qtsubs = DatumGetTSQuery(sdata);
346 if (qtex != (TSQuery) DatumGetPointer(qdata))
348 if (qtsubs != (TSQuery) DatumGetPointer(sdata))
353 qex = QT2QTN(GETQUERY(qtex), GETOPERAND(qtex));
359 qsubs = QT2QTN(GETQUERY(qtsubs), GETOPERAND(qtsubs));
361 oldcontext = MemoryContextSwitchTo(outercontext);
362 tree = findsubquery(tree, qex, qsubs, NULL);
363 MemoryContextSwitchTo(oldcontext);
366 if (qtex != (TSQuery) DatumGetPointer(qdata))
369 if (qtsubs != (TSQuery) DatumGetPointer(sdata))
374 /* ready the tree for another pass */
375 QTNClearFlags(tree, QTN_NOCHANGE);
382 SPI_freetuptable(SPI_tuptable);
383 SPI_cursor_fetch(portal, true, 100);
386 SPI_freetuptable(SPI_tuptable);
387 SPI_cursor_close(portal);
394 rewrited = QTN2QT(tree);
396 PG_FREE_IF_COPY(query, 0);
400 SET_VARSIZE(rewrited, HDRSIZETQ);
405 PG_FREE_IF_COPY(in, 1);
406 PG_RETURN_POINTER(rewrited);
410 tsquery_rewrite(PG_FUNCTION_ARGS)
412 TSQuery query = PG_GETARG_TSQUERY_COPY(0);
413 TSQuery ex = PG_GETARG_TSQUERY(1);
414 TSQuery subst = PG_GETARG_TSQUERY(2);
415 TSQuery rewrited = query;
420 if (query->size == 0 || ex->size == 0)
422 PG_FREE_IF_COPY(ex, 1);
423 PG_FREE_IF_COPY(subst, 2);
424 PG_RETURN_POINTER(rewrited);
427 tree = QT2QTN(GETQUERY(query), GETOPERAND(query));
431 qex = QT2QTN(GETQUERY(ex), GETOPERAND(ex));
436 subs = QT2QTN(GETQUERY(subst), GETOPERAND(subst));
438 tree = findsubquery(tree, qex, subs, NULL);
445 SET_VARSIZE(rewrited, HDRSIZETQ);
447 PG_FREE_IF_COPY(ex, 1);
448 PG_FREE_IF_COPY(subst, 2);
449 PG_RETURN_POINTER(rewrited);
454 rewrited = QTN2QT(tree);
458 PG_FREE_IF_COPY(query, 0);
459 PG_FREE_IF_COPY(ex, 1);
460 PG_FREE_IF_COPY(subst, 2);
461 PG_RETURN_POINTER(rewrited);