mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-15 23:20:41 +02:00
ca3b37487b
Backpatch-through: 9.5
463 lines
10 KiB
C
463 lines
10 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* tsquery_rewrite.c
|
|
* Utilities for reconstructing tsquery
|
|
*
|
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/tsquery_rewrite.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "executor/spi.h"
|
|
#include "miscadmin.h"
|
|
#include "tsearch/ts_utils.h"
|
|
#include "utils/builtins.h"
|
|
|
|
|
|
/*
|
|
* If "node" is equal to "ex", return a copy of "subs" instead.
|
|
* If "ex" matches a subset of node's children, return a modified version
|
|
* of "node" in which those children are replaced with a copy of "subs".
|
|
* Otherwise return "node" unmodified.
|
|
*
|
|
* The QTN_NOCHANGE bit is set in successfully modified nodes, so that
|
|
* we won't uselessly recurse into them.
|
|
* Also, set *isfind true if we make a replacement.
|
|
*/
|
|
static QTNode *
|
|
findeq(QTNode *node, QTNode *ex, QTNode *subs, bool *isfind)
|
|
{
|
|
/* Can't match unless signature matches and node type matches. */
|
|
if ((node->sign & ex->sign) != ex->sign ||
|
|
node->valnode->type != ex->valnode->type)
|
|
return node;
|
|
|
|
/* Ignore nodes marked NOCHANGE, too. */
|
|
if (node->flags & QTN_NOCHANGE)
|
|
return node;
|
|
|
|
if (node->valnode->type == QI_OPR)
|
|
{
|
|
/* Must be same operator. */
|
|
if (node->valnode->qoperator.oper != ex->valnode->qoperator.oper)
|
|
return node;
|
|
|
|
if (node->nchild == ex->nchild)
|
|
{
|
|
/*
|
|
* Simple case: when same number of children, match if equal.
|
|
* (This is reliable when the children were sorted earlier.)
|
|
*/
|
|
if (QTNEq(node, ex))
|
|
{
|
|
/* Match; delete node and return a copy of subs instead. */
|
|
QTNFree(node);
|
|
if (subs)
|
|
{
|
|
node = QTNCopy(subs);
|
|
node->flags |= QTN_NOCHANGE;
|
|
}
|
|
else
|
|
node = NULL;
|
|
*isfind = true;
|
|
}
|
|
}
|
|
else if (node->nchild > ex->nchild && ex->nchild > 0)
|
|
{
|
|
/*
|
|
* AND and OR are commutative/associative, so we should check if a
|
|
* subset of the children match. For example, if node is A|B|C,
|
|
* and ex is B|C, we have a match after we notionally convert node
|
|
* to A|(B|C). This does not work for NOT or PHRASE nodes, but we
|
|
* can't get here for those node types because they have a fixed
|
|
* number of children.
|
|
*
|
|
* Because we expect that the children are sorted, it suffices to
|
|
* make one pass through the two lists to find the matches.
|
|
*/
|
|
bool *matched;
|
|
int nmatched;
|
|
int i,
|
|
j;
|
|
|
|
/* Assert that the subset rule is OK */
|
|
Assert(node->valnode->qoperator.oper == OP_AND ||
|
|
node->valnode->qoperator.oper == OP_OR);
|
|
|
|
/* matched[] will record which children of node matched */
|
|
matched = (bool *) palloc0(node->nchild * sizeof(bool));
|
|
nmatched = 0;
|
|
i = j = 0;
|
|
while (i < node->nchild && j < ex->nchild)
|
|
{
|
|
int cmp = QTNodeCompare(node->child[i], ex->child[j]);
|
|
|
|
if (cmp == 0)
|
|
{
|
|
/* match! */
|
|
matched[i] = true;
|
|
nmatched++;
|
|
i++, j++;
|
|
}
|
|
else if (cmp < 0)
|
|
{
|
|
/* node->child[i] has no match, ignore it */
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
/* ex->child[j] has no match; we can give up immediately */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nmatched == ex->nchild)
|
|
{
|
|
/* collapse out the matched children of node */
|
|
j = 0;
|
|
for (i = 0; i < node->nchild; i++)
|
|
{
|
|
if (matched[i])
|
|
QTNFree(node->child[i]);
|
|
else
|
|
node->child[j++] = node->child[i];
|
|
}
|
|
|
|
/* and instead insert a copy of subs */
|
|
if (subs)
|
|
{
|
|
subs = QTNCopy(subs);
|
|
subs->flags |= QTN_NOCHANGE;
|
|
node->child[j++] = subs;
|
|
}
|
|
|
|
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
|
|
* remaining processing, and it's insufficient to prepare the
|
|
* tree for another search (we would need to re-flatten as
|
|
* well, and we don't want to do that because we'd lose the
|
|
* QTN_NOCHANGE marking on the new child). But it's needed to
|
|
* keep the results the same as the regression tests expect.
|
|
*/
|
|
QTNSort(node);
|
|
|
|
*isfind = true;
|
|
}
|
|
|
|
pfree(matched);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert(node->valnode->type == QI_VAL);
|
|
|
|
if (node->valnode->qoperand.valcrc != ex->valnode->qoperand.valcrc)
|
|
return node;
|
|
else if (QTNEq(node, ex))
|
|
{
|
|
QTNFree(node);
|
|
if (subs)
|
|
{
|
|
node = QTNCopy(subs);
|
|
node->flags |= QTN_NOCHANGE;
|
|
}
|
|
else
|
|
{
|
|
node = NULL;
|
|
}
|
|
*isfind = true;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
/* since this function recurses, it could be driven to stack overflow. */
|
|
check_stack_depth();
|
|
|
|
/* 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);
|
|
|
|
/* 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++)
|
|
{
|
|
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);
|
|
root = NULL;
|
|
}
|
|
else if (root->nchild == 1 && root->valnode->qoperator.oper != OP_NOT)
|
|
{
|
|
QTNode *nroot = root->child[0];
|
|
|
|
pfree(root);
|
|
root = nroot;
|
|
}
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
/*
|
|
* Substitute "subs" for "ex" throughout the QTNode tree at root.
|
|
*
|
|
* If isfind isn't NULL, set *isfind to show whether we made any substitution.
|
|
*
|
|
* Both "root" and "ex" must have been through QTNTernary and QTNSort
|
|
* to ensure reliable matching.
|
|
*/
|
|
QTNode *
|
|
findsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind)
|
|
{
|
|
bool DidFind = false;
|
|
|
|
root = dofindsubquery(root, ex, subs, &DidFind);
|
|
|
|
if (isfind)
|
|
*isfind = DidFind;
|
|
|
|
return root;
|
|
}
|
|
|
|
Datum
|
|
tsquery_rewrite_query(PG_FUNCTION_ARGS)
|
|
{
|
|
TSQuery query = PG_GETARG_TSQUERY_COPY(0);
|
|
text *in = PG_GETARG_TEXT_PP(1);
|
|
TSQuery rewrited = query;
|
|
MemoryContext outercontext = CurrentMemoryContext;
|
|
MemoryContext oldcontext;
|
|
QTNode *tree;
|
|
char *buf;
|
|
SPIPlanPtr plan;
|
|
Portal portal;
|
|
bool isnull;
|
|
|
|
if (query->size == 0)
|
|
{
|
|
PG_FREE_IF_COPY(in, 1);
|
|
PG_RETURN_POINTER(rewrited);
|
|
}
|
|
|
|
tree = QT2QTN(GETQUERY(query), GETOPERAND(query));
|
|
QTNTernary(tree);
|
|
QTNSort(tree);
|
|
|
|
buf = text_to_cstring(in);
|
|
|
|
SPI_connect();
|
|
|
|
if ((plan = SPI_prepare(buf, 0, NULL)) == NULL)
|
|
elog(ERROR, "SPI_prepare(\"%s\") failed", buf);
|
|
|
|
if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL)
|
|
elog(ERROR, "SPI_cursor_open(\"%s\") failed", buf);
|
|
|
|
SPI_cursor_fetch(portal, true, 100);
|
|
|
|
if (SPI_tuptable == NULL ||
|
|
SPI_tuptable->tupdesc->natts != 2 ||
|
|
SPI_gettypeid(SPI_tuptable->tupdesc, 1) != TSQUERYOID ||
|
|
SPI_gettypeid(SPI_tuptable->tupdesc, 2) != TSQUERYOID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("ts_rewrite query must return two tsquery columns")));
|
|
|
|
while (SPI_processed > 0 && tree)
|
|
{
|
|
uint64 i;
|
|
|
|
for (i = 0; i < SPI_processed && tree; i++)
|
|
{
|
|
Datum qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull);
|
|
Datum sdata;
|
|
|
|
if (isnull)
|
|
continue;
|
|
|
|
sdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull);
|
|
|
|
if (!isnull)
|
|
{
|
|
TSQuery qtex = DatumGetTSQuery(qdata);
|
|
TSQuery qtsubs = DatumGetTSQuery(sdata);
|
|
QTNode *qex,
|
|
*qsubs = NULL;
|
|
|
|
if (qtex->size == 0)
|
|
{
|
|
if (qtex != (TSQuery) DatumGetPointer(qdata))
|
|
pfree(qtex);
|
|
if (qtsubs != (TSQuery) DatumGetPointer(sdata))
|
|
pfree(qtsubs);
|
|
continue;
|
|
}
|
|
|
|
qex = QT2QTN(GETQUERY(qtex), GETOPERAND(qtex));
|
|
|
|
QTNTernary(qex);
|
|
QTNSort(qex);
|
|
|
|
if (qtsubs->size)
|
|
qsubs = QT2QTN(GETQUERY(qtsubs), GETOPERAND(qtsubs));
|
|
|
|
oldcontext = MemoryContextSwitchTo(outercontext);
|
|
tree = findsubquery(tree, qex, qsubs, NULL);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
QTNFree(qex);
|
|
if (qtex != (TSQuery) DatumGetPointer(qdata))
|
|
pfree(qtex);
|
|
QTNFree(qsubs);
|
|
if (qtsubs != (TSQuery) DatumGetPointer(sdata))
|
|
pfree(qtsubs);
|
|
|
|
if (tree)
|
|
{
|
|
/* ready the tree for another pass */
|
|
QTNClearFlags(tree, QTN_NOCHANGE);
|
|
QTNTernary(tree);
|
|
QTNSort(tree);
|
|
}
|
|
}
|
|
}
|
|
|
|
SPI_freetuptable(SPI_tuptable);
|
|
SPI_cursor_fetch(portal, true, 100);
|
|
}
|
|
|
|
SPI_freetuptable(SPI_tuptable);
|
|
SPI_cursor_close(portal);
|
|
SPI_freeplan(plan);
|
|
SPI_finish();
|
|
|
|
if (tree)
|
|
{
|
|
QTNBinary(tree);
|
|
rewrited = QTN2QT(tree);
|
|
QTNFree(tree);
|
|
PG_FREE_IF_COPY(query, 0);
|
|
}
|
|
else
|
|
{
|
|
SET_VARSIZE(rewrited, HDRSIZETQ);
|
|
rewrited->size = 0;
|
|
}
|
|
|
|
pfree(buf);
|
|
PG_FREE_IF_COPY(in, 1);
|
|
PG_RETURN_POINTER(rewrited);
|
|
}
|
|
|
|
Datum
|
|
tsquery_rewrite(PG_FUNCTION_ARGS)
|
|
{
|
|
TSQuery query = PG_GETARG_TSQUERY_COPY(0);
|
|
TSQuery ex = PG_GETARG_TSQUERY(1);
|
|
TSQuery subst = PG_GETARG_TSQUERY(2);
|
|
TSQuery rewrited = query;
|
|
QTNode *tree,
|
|
*qex,
|
|
*subs = NULL;
|
|
|
|
if (query->size == 0 || ex->size == 0)
|
|
{
|
|
PG_FREE_IF_COPY(ex, 1);
|
|
PG_FREE_IF_COPY(subst, 2);
|
|
PG_RETURN_POINTER(rewrited);
|
|
}
|
|
|
|
tree = QT2QTN(GETQUERY(query), GETOPERAND(query));
|
|
QTNTernary(tree);
|
|
QTNSort(tree);
|
|
|
|
qex = QT2QTN(GETQUERY(ex), GETOPERAND(ex));
|
|
QTNTernary(qex);
|
|
QTNSort(qex);
|
|
|
|
if (subst->size)
|
|
subs = QT2QTN(GETQUERY(subst), GETOPERAND(subst));
|
|
|
|
tree = findsubquery(tree, qex, subs, NULL);
|
|
|
|
QTNFree(qex);
|
|
QTNFree(subs);
|
|
|
|
if (!tree)
|
|
{
|
|
SET_VARSIZE(rewrited, HDRSIZETQ);
|
|
rewrited->size = 0;
|
|
PG_FREE_IF_COPY(ex, 1);
|
|
PG_FREE_IF_COPY(subst, 2);
|
|
PG_RETURN_POINTER(rewrited);
|
|
}
|
|
else
|
|
{
|
|
QTNBinary(tree);
|
|
rewrited = QTN2QT(tree);
|
|
QTNFree(tree);
|
|
}
|
|
|
|
PG_FREE_IF_COPY(query, 0);
|
|
PG_FREE_IF_COPY(ex, 1);
|
|
PG_FREE_IF_COPY(subst, 2);
|
|
PG_RETURN_POINTER(rewrited);
|
|
}
|