Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 2a2b439

Browse files
committed
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.
1 parent 48a6592 commit 2a2b439

File tree

3 files changed

+68
-23
lines changed

3 files changed

+68
-23
lines changed

src/backend/utils/adt/tsquery_util.c

+59-23
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#include "tsearch/ts_utils.h"
1818
#include "miscadmin.h"
1919

20+
/*
21+
* Build QTNode tree for a tsquery given in QueryItem array format.
22+
*/
2023
QTNode *
2124
QT2QTN(QueryItem *in, char *operand)
2225
{
@@ -50,6 +53,12 @@ QT2QTN(QueryItem *in, char *operand)
5053
return node;
5154
}
5255

56+
/*
57+
* Free a QTNode tree.
58+
*
59+
* Referenced "word" and "valnode" items are freed if marked as transient
60+
* by flags.
61+
*/
5362
void
5463
QTNFree(QTNode *in)
5564
{
@@ -62,26 +71,27 @@ QTNFree(QTNode *in)
6271
if (in->valnode->type == QI_VAL && in->word && (in->flags & QTN_WORDFREE) != 0)
6372
pfree(in->word);
6473

65-
if (in->child)
74+
if (in->valnode->type == QI_OPR)
6675
{
67-
if (in->valnode)
68-
{
69-
if (in->valnode->type == QI_OPR && in->nchild > 0)
70-
{
71-
int i;
72-
73-
for (i = 0; i < in->nchild; i++)
74-
QTNFree(in->child[i]);
75-
}
76-
if (in->flags & QTN_NEEDFREE)
77-
pfree(in->valnode);
78-
}
79-
pfree(in->child);
76+
int i;
77+
78+
for (i = 0; i < in->nchild; i++)
79+
QTNFree(in->child[i]);
8080
}
81+
if (in->child)
82+
pfree(in->child);
83+
84+
if (in->flags & QTN_NEEDFREE)
85+
pfree(in->valnode);
8186

8287
pfree(in);
8388
}
8489

90+
/*
91+
* Sort comparator for QTNodes.
92+
*
93+
* The sort order is somewhat arbitrary.
94+
*/
8595
int
8696
QTNodeCompare(QTNode *an, QTNode *bn)
8797
{
@@ -135,12 +145,19 @@ QTNodeCompare(QTNode *an, QTNode *bn)
135145
}
136146
}
137147

148+
/*
149+
* qsort comparator for QTNode pointers.
150+
*/
138151
static int
139152
cmpQTN(const void *a, const void *b)
140153
{
141154
return QTNodeCompare(*(QTNode *const *) a, *(QTNode *const *) b);
142155
}
143156

157+
/*
158+
* Canonicalize a QTNode tree by sorting the children of AND/OR nodes
159+
* into an arbitrary but well-defined order.
160+
*/
144161
void
145162
QTNSort(QTNode *in)
146163
{
@@ -158,13 +175,16 @@ QTNSort(QTNode *in)
158175
qsort((void *) in->child, in->nchild, sizeof(QTNode *), cmpQTN);
159176
}
160177

178+
/*
179+
* Are two QTNode trees equal according to QTNodeCompare?
180+
*/
161181
bool
162182
QTNEq(QTNode *a, QTNode *b)
163183
{
164184
uint32 sign = a->sign & b->sign;
165185

166186
if (!(sign == a->sign && sign == b->sign))
167-
return 0;
187+
return false;
168188

169189
return (QTNodeCompare(a, b) == 0) ? true : false;
170190
}
@@ -190,14 +210,17 @@ QTNTernary(QTNode *in)
190210
for (i = 0; i < in->nchild; i++)
191211
QTNTernary(in->child[i]);
192212

213+
/* Only AND and OR are associative, so don't flatten other node types */
214+
if (in->valnode->qoperator.oper != OP_AND &&
215+
in->valnode->qoperator.oper != OP_OR)
216+
return;
217+
193218
for (i = 0; i < in->nchild; i++)
194219
{
195220
QTNode *cc = in->child[i];
196221

197-
/* OP_Phrase isn't associative */
198222
if (cc->valnode->type == QI_OPR &&
199-
in->valnode->qoperator.oper == cc->valnode->qoperator.oper &&
200-
in->valnode->qoperator.oper != OP_PHRASE)
223+
in->valnode->qoperator.oper == cc->valnode->qoperator.oper)
201224
{
202225
int oldnchild = in->nchild;
203226

@@ -236,9 +259,6 @@ QTNBinary(QTNode *in)
236259
for (i = 0; i < in->nchild; i++)
237260
QTNBinary(in->child[i]);
238261

239-
if (in->nchild <= 2)
240-
return;
241-
242262
while (in->nchild > 2)
243263
{
244264
QTNode *nn = (QTNode *) palloc0(sizeof(QTNode));
@@ -263,8 +283,9 @@ QTNBinary(QTNode *in)
263283
}
264284

265285
/*
266-
* Count the total length of operand string in tree, including '\0'-
267-
* terminators.
286+
* Count the total length of operand strings in tree (including '\0'-
287+
* terminators) and the total number of nodes.
288+
* Caller must initialize *sumlen and *nnode to zeroes.
268289
*/
269290
static void
270291
cntsize(QTNode *in, int *sumlen, int *nnode)
@@ -293,6 +314,10 @@ typedef struct
293314
char *curoperand;
294315
} QTN2QTState;
295316

317+
/*
318+
* Recursively convert a QTNode tree into flat tsquery format.
319+
* Caller must have allocated arrays of the correct size.
320+
*/
296321
static void
297322
fillQT(QTN2QTState *state, QTNode *in)
298323
{
@@ -330,6 +355,9 @@ fillQT(QTN2QTState *state, QTNode *in)
330355
}
331356
}
332357

358+
/*
359+
* Build flat tsquery from a QTNode tree.
360+
*/
333361
TSQuery
334362
QTN2QT(QTNode *in)
335363
{
@@ -358,6 +386,11 @@ QTN2QT(QTNode *in)
358386
return out;
359387
}
360388

389+
/*
390+
* Copy a QTNode tree.
391+
*
392+
* Modifiable copies of the words and valnodes are made, too.
393+
*/
361394
QTNode *
362395
QTNCopy(QTNode *in)
363396
{
@@ -393,6 +426,9 @@ QTNCopy(QTNode *in)
393426
return out;
394427
}
395428

429+
/*
430+
* Clear the specified flag bit(s) in all nodes of a QTNode tree.
431+
*/
396432
void
397433
QTNClearFlags(QTNode *in, uint32 flags)
398434
{

src/test/regress/expected/tsearch.out

+7
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,13 @@ SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big &
11841184
'foo' & 'bar' & 'qq' & ( 'city' & 'new' & 'york' | 'nyc' | 'big' & 'apple' )
11851185
(1 row)
11861186

1187+
SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
1188+
'jersey', 'mexico');
1189+
ts_rewrite
1190+
--------------------
1191+
'new' & !!'mexico'
1192+
(1 row)
1193+
11871194
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
11881195
ts_rewrite
11891196
---------------------

src/test/regress/sql/tsearch.sql

+2
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
402402
RESET enable_seqscan;
403403

404404
SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big & apple | nyc | new & york & city');
405+
SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
406+
'jersey', 'mexico');
405407

406408
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
407409
SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::text );

0 commit comments

Comments
 (0)