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

Commit de73876

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 7151e72 commit de73876

File tree

3 files changed

+69
-21
lines changed

3 files changed

+69
-21
lines changed

src/backend/utils/adt/tsquery_util.c

Lines changed: 60 additions & 21 deletions
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
{
@@ -131,12 +141,19 @@ QTNodeCompare(QTNode *an, QTNode *bn)
131141
}
132142
}
133143

144+
/*
145+
* qsort comparator for QTNode pointers.
146+
*/
134147
static int
135148
cmpQTN(const void *a, const void *b)
136149
{
137150
return QTNodeCompare(*(QTNode *const *) a, *(QTNode *const *) b);
138151
}
139152

153+
/*
154+
* Canonicalize a QTNode tree by sorting the children of AND/OR nodes
155+
* into an arbitrary but well-defined order.
156+
*/
140157
void
141158
QTNSort(QTNode *in)
142159
{
@@ -154,13 +171,16 @@ QTNSort(QTNode *in)
154171
qsort((void *) in->child, in->nchild, sizeof(QTNode *), cmpQTN);
155172
}
156173

174+
/*
175+
* Are two QTNode trees equal according to QTNodeCompare?
176+
*/
157177
bool
158178
QTNEq(QTNode *a, QTNode *b)
159179
{
160180
uint32 sign = a->sign & b->sign;
161181

162182
if (!(sign == a->sign && sign == b->sign))
163-
return 0;
183+
return false;
164184

165185
return (QTNodeCompare(a, b) == 0) ? true : false;
166186
}
@@ -186,11 +206,17 @@ QTNTernary(QTNode *in)
186206
for (i = 0; i < in->nchild; i++)
187207
QTNTernary(in->child[i]);
188208

209+
/* Only AND and OR are associative, so don't flatten other node types */
210+
if (in->valnode->qoperator.oper != OP_AND &&
211+
in->valnode->qoperator.oper != OP_OR)
212+
return;
213+
189214
for (i = 0; i < in->nchild; i++)
190215
{
191216
QTNode *cc = in->child[i];
192217

193-
if (cc->valnode->type == QI_OPR && in->valnode->qoperator.oper == cc->valnode->qoperator.oper)
218+
if (cc->valnode->type == QI_OPR &&
219+
in->valnode->qoperator.oper == cc->valnode->qoperator.oper)
194220
{
195221
int oldnchild = in->nchild;
196222

@@ -229,9 +255,6 @@ QTNBinary(QTNode *in)
229255
for (i = 0; i < in->nchild; i++)
230256
QTNBinary(in->child[i]);
231257

232-
if (in->nchild <= 2)
233-
return;
234-
235258
while (in->nchild > 2)
236259
{
237260
QTNode *nn = (QTNode *) palloc0(sizeof(QTNode));
@@ -256,8 +279,9 @@ QTNBinary(QTNode *in)
256279
}
257280

258281
/*
259-
* Count the total length of operand string in tree, including '\0'-
260-
* terminators.
282+
* Count the total length of operand strings in tree (including '\0'-
283+
* terminators) and the total number of nodes.
284+
* Caller must initialize *sumlen and *nnode to zeroes.
261285
*/
262286
static void
263287
cntsize(QTNode *in, int *sumlen, int *nnode)
@@ -286,6 +310,10 @@ typedef struct
286310
char *curoperand;
287311
} QTN2QTState;
288312

313+
/*
314+
* Recursively convert a QTNode tree into flat tsquery format.
315+
* Caller must have allocated arrays of the correct size.
316+
*/
289317
static void
290318
fillQT(QTN2QTState *state, QTNode *in)
291319
{
@@ -323,6 +351,9 @@ fillQT(QTN2QTState *state, QTNode *in)
323351
}
324352
}
325353

354+
/*
355+
* Build flat tsquery from a QTNode tree.
356+
*/
326357
TSQuery
327358
QTN2QT(QTNode *in)
328359
{
@@ -351,6 +382,11 @@ QTN2QT(QTNode *in)
351382
return out;
352383
}
353384

385+
/*
386+
* Copy a QTNode tree.
387+
*
388+
* Modifiable copies of the words and valnodes are made, too.
389+
*/
354390
QTNode *
355391
QTNCopy(QTNode *in)
356392
{
@@ -386,6 +422,9 @@ QTNCopy(QTNode *in)
386422
return out;
387423
}
388424

425+
/*
426+
* Clear the specified flag bit(s) in all nodes of a QTNode tree.
427+
*/
389428
void
390429
QTNClearFlags(QTNode *in, uint32 flags)
391430
{

src/test/regress/expected/tsearch.out

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,13 @@ SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big &
865865
'foo' & 'bar' & 'qq' & ( 'city' & 'new' & 'york' | ( 'nyc' | 'big' & 'apple' ) )
866866
(1 row)
867867

868+
SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
869+
'jersey', 'mexico');
870+
ts_rewrite
871+
------------------------
872+
'new' & !( !'mexico' )
873+
(1 row)
874+
868875
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
869876
ts_rewrite
870877
---------------------

src/test/regress/sql/tsearch.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
311311
RESET enable_seqscan;
312312

313313
SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big & apple | nyc | new & york & city');
314+
SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
315+
'jersey', 'mexico');
314316

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

0 commit comments

Comments
 (0)