mirror of
https://github.com/postgres/postgres.git
synced 2025-05-06 19:59:18 +03:00
Fix bogus tree-flattening logic in QTNTernary().
QTNTernary() contains logic to flatten, eg, '(a & b) & c' into 'a & b & c', which is all well and good, but it tries to do that to NOT nodes as well, so that '!!a' gets changed to '!a'. Explicitly restrict the conversion to be done only on AND and OR nodes, and add a test case illustrating the bug. In passing, provide some comments for the sadly naked functions in tsquery_util.c, and simplify some baroque logic in QTNFree(), which I think may have been leaking some items it intended to free. Noted while investigating a complaint from Andreas Seltenreich. Back-patch to all supported versions.
This commit is contained in:
parent
48a6592dae
commit
2a2b439cc1
@ -17,6 +17,9 @@
|
|||||||
#include "tsearch/ts_utils.h"
|
#include "tsearch/ts_utils.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build QTNode tree for a tsquery given in QueryItem array format.
|
||||||
|
*/
|
||||||
QTNode *
|
QTNode *
|
||||||
QT2QTN(QueryItem *in, char *operand)
|
QT2QTN(QueryItem *in, char *operand)
|
||||||
{
|
{
|
||||||
@ -50,6 +53,12 @@ QT2QTN(QueryItem *in, char *operand)
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free a QTNode tree.
|
||||||
|
*
|
||||||
|
* Referenced "word" and "valnode" items are freed if marked as transient
|
||||||
|
* by flags.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
QTNFree(QTNode *in)
|
QTNFree(QTNode *in)
|
||||||
{
|
{
|
||||||
@ -62,26 +71,27 @@ QTNFree(QTNode *in)
|
|||||||
if (in->valnode->type == QI_VAL && in->word && (in->flags & QTN_WORDFREE) != 0)
|
if (in->valnode->type == QI_VAL && in->word && (in->flags & QTN_WORDFREE) != 0)
|
||||||
pfree(in->word);
|
pfree(in->word);
|
||||||
|
|
||||||
if (in->child)
|
if (in->valnode->type == QI_OPR)
|
||||||
{
|
|
||||||
if (in->valnode)
|
|
||||||
{
|
|
||||||
if (in->valnode->type == QI_OPR && in->nchild > 0)
|
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < in->nchild; i++)
|
for (i = 0; i < in->nchild; i++)
|
||||||
QTNFree(in->child[i]);
|
QTNFree(in->child[i]);
|
||||||
}
|
}
|
||||||
|
if (in->child)
|
||||||
|
pfree(in->child);
|
||||||
|
|
||||||
if (in->flags & QTN_NEEDFREE)
|
if (in->flags & QTN_NEEDFREE)
|
||||||
pfree(in->valnode);
|
pfree(in->valnode);
|
||||||
}
|
|
||||||
pfree(in->child);
|
|
||||||
}
|
|
||||||
|
|
||||||
pfree(in);
|
pfree(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sort comparator for QTNodes.
|
||||||
|
*
|
||||||
|
* The sort order is somewhat arbitrary.
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
QTNodeCompare(QTNode *an, QTNode *bn)
|
QTNodeCompare(QTNode *an, QTNode *bn)
|
||||||
{
|
{
|
||||||
@ -135,12 +145,19 @@ QTNodeCompare(QTNode *an, QTNode *bn)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* qsort comparator for QTNode pointers.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
cmpQTN(const void *a, const void *b)
|
cmpQTN(const void *a, const void *b)
|
||||||
{
|
{
|
||||||
return QTNodeCompare(*(QTNode *const *) a, *(QTNode *const *) b);
|
return QTNodeCompare(*(QTNode *const *) a, *(QTNode *const *) b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Canonicalize a QTNode tree by sorting the children of AND/OR nodes
|
||||||
|
* into an arbitrary but well-defined order.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
QTNSort(QTNode *in)
|
QTNSort(QTNode *in)
|
||||||
{
|
{
|
||||||
@ -158,13 +175,16 @@ QTNSort(QTNode *in)
|
|||||||
qsort((void *) in->child, in->nchild, sizeof(QTNode *), cmpQTN);
|
qsort((void *) in->child, in->nchild, sizeof(QTNode *), cmpQTN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Are two QTNode trees equal according to QTNodeCompare?
|
||||||
|
*/
|
||||||
bool
|
bool
|
||||||
QTNEq(QTNode *a, QTNode *b)
|
QTNEq(QTNode *a, QTNode *b)
|
||||||
{
|
{
|
||||||
uint32 sign = a->sign & b->sign;
|
uint32 sign = a->sign & b->sign;
|
||||||
|
|
||||||
if (!(sign == a->sign && sign == b->sign))
|
if (!(sign == a->sign && sign == b->sign))
|
||||||
return 0;
|
return false;
|
||||||
|
|
||||||
return (QTNodeCompare(a, b) == 0) ? true : false;
|
return (QTNodeCompare(a, b) == 0) ? true : false;
|
||||||
}
|
}
|
||||||
@ -190,14 +210,17 @@ QTNTernary(QTNode *in)
|
|||||||
for (i = 0; i < in->nchild; i++)
|
for (i = 0; i < in->nchild; i++)
|
||||||
QTNTernary(in->child[i]);
|
QTNTernary(in->child[i]);
|
||||||
|
|
||||||
|
/* Only AND and OR are associative, so don't flatten other node types */
|
||||||
|
if (in->valnode->qoperator.oper != OP_AND &&
|
||||||
|
in->valnode->qoperator.oper != OP_OR)
|
||||||
|
return;
|
||||||
|
|
||||||
for (i = 0; i < in->nchild; i++)
|
for (i = 0; i < in->nchild; i++)
|
||||||
{
|
{
|
||||||
QTNode *cc = in->child[i];
|
QTNode *cc = in->child[i];
|
||||||
|
|
||||||
/* OP_Phrase isn't associative */
|
|
||||||
if (cc->valnode->type == QI_OPR &&
|
if (cc->valnode->type == QI_OPR &&
|
||||||
in->valnode->qoperator.oper == cc->valnode->qoperator.oper &&
|
in->valnode->qoperator.oper == cc->valnode->qoperator.oper)
|
||||||
in->valnode->qoperator.oper != OP_PHRASE)
|
|
||||||
{
|
{
|
||||||
int oldnchild = in->nchild;
|
int oldnchild = in->nchild;
|
||||||
|
|
||||||
@ -236,9 +259,6 @@ QTNBinary(QTNode *in)
|
|||||||
for (i = 0; i < in->nchild; i++)
|
for (i = 0; i < in->nchild; i++)
|
||||||
QTNBinary(in->child[i]);
|
QTNBinary(in->child[i]);
|
||||||
|
|
||||||
if (in->nchild <= 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (in->nchild > 2)
|
while (in->nchild > 2)
|
||||||
{
|
{
|
||||||
QTNode *nn = (QTNode *) palloc0(sizeof(QTNode));
|
QTNode *nn = (QTNode *) palloc0(sizeof(QTNode));
|
||||||
@ -263,8 +283,9 @@ QTNBinary(QTNode *in)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Count the total length of operand string in tree, including '\0'-
|
* Count the total length of operand strings in tree (including '\0'-
|
||||||
* terminators.
|
* terminators) and the total number of nodes.
|
||||||
|
* Caller must initialize *sumlen and *nnode to zeroes.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
cntsize(QTNode *in, int *sumlen, int *nnode)
|
cntsize(QTNode *in, int *sumlen, int *nnode)
|
||||||
@ -293,6 +314,10 @@ typedef struct
|
|||||||
char *curoperand;
|
char *curoperand;
|
||||||
} QTN2QTState;
|
} QTN2QTState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recursively convert a QTNode tree into flat tsquery format.
|
||||||
|
* Caller must have allocated arrays of the correct size.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
fillQT(QTN2QTState *state, QTNode *in)
|
fillQT(QTN2QTState *state, QTNode *in)
|
||||||
{
|
{
|
||||||
@ -330,6 +355,9 @@ fillQT(QTN2QTState *state, QTNode *in)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build flat tsquery from a QTNode tree.
|
||||||
|
*/
|
||||||
TSQuery
|
TSQuery
|
||||||
QTN2QT(QTNode *in)
|
QTN2QT(QTNode *in)
|
||||||
{
|
{
|
||||||
@ -358,6 +386,11 @@ QTN2QT(QTNode *in)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy a QTNode tree.
|
||||||
|
*
|
||||||
|
* Modifiable copies of the words and valnodes are made, too.
|
||||||
|
*/
|
||||||
QTNode *
|
QTNode *
|
||||||
QTNCopy(QTNode *in)
|
QTNCopy(QTNode *in)
|
||||||
{
|
{
|
||||||
@ -393,6 +426,9 @@ QTNCopy(QTNode *in)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear the specified flag bit(s) in all nodes of a QTNode tree.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
QTNClearFlags(QTNode *in, uint32 flags)
|
QTNClearFlags(QTNode *in, uint32 flags)
|
||||||
{
|
{
|
||||||
|
@ -1184,6 +1184,13 @@ SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big &
|
|||||||
'foo' & 'bar' & 'qq' & ( 'city' & 'new' & 'york' | 'nyc' | 'big' & 'apple' )
|
'foo' & 'bar' & 'qq' & ( 'city' & 'new' & 'york' | 'nyc' | 'big' & 'apple' )
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
|
||||||
|
'jersey', 'mexico');
|
||||||
|
ts_rewrite
|
||||||
|
--------------------
|
||||||
|
'new' & !!'mexico'
|
||||||
|
(1 row)
|
||||||
|
|
||||||
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
|
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
|
||||||
ts_rewrite
|
ts_rewrite
|
||||||
---------------------
|
---------------------
|
||||||
|
@ -402,6 +402,8 @@ SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
|
|||||||
RESET enable_seqscan;
|
RESET enable_seqscan;
|
||||||
|
|
||||||
SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big & apple | nyc | new & york & city');
|
SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big & apple | nyc | new & york & city');
|
||||||
|
SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
|
||||||
|
'jersey', 'mexico');
|
||||||
|
|
||||||
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
|
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
|
||||||
SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::text );
|
SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::text );
|
||||||
|
Loading…
x
Reference in New Issue
Block a user