Skip to content

Commit 9950c8a

Browse files
committed
Fix lquery's behavior for consecutive '*' items.
Something like "*{2}.*{3}" should presumably mean the same as "*{5}", but it didn't. Improve that. Get rid of an undocumented and remarkably ugly (though not, as far as I can tell, actually unsafe) static variable in favor of passing more arguments to checkCond(). Reverse-engineer some commentary. This function, like all of ltree, is still far short of what I would consider the minimum acceptable level of internal documentation, but at least now it has more than zero comments. Although this certainly seems like a bug fix, people might not thank us for changing query behavior in stable branches, so no back-patch. Nikita Glukhov, with cosmetic improvements by me Discussion: https://postgr.es/m/CAP_rww=waX2Oo6q+MbMSiZ9ktdj6eaJj0cQzNu=Ry2cCDij5fw@mail.gmail.com
1 parent 95f7ddf commit 9950c8a

File tree

4 files changed

+123
-46
lines changed

4 files changed

+123
-46
lines changed

contrib/ltree/expected/ltree.out

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,30 @@ SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*';
959959
f
960960
(1 row)
961961

962+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2}.*{2}';
963+
?column?
964+
----------
965+
t
966+
(1 row)
967+
968+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{2}.e';
969+
?column?
970+
----------
971+
t
972+
(1 row)
973+
974+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{4}';
975+
?column?
976+
----------
977+
f
978+
(1 row)
979+
980+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{5}.*';
981+
?column?
982+
----------
983+
f
984+
(1 row)
985+
962986
SELECT 'QWER_TY'::ltree ~ 'q%@*';
963987
?column?
964988
----------

contrib/ltree/lquery_op.c

Lines changed: 84 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ ltree_strncasecmp(const char *a, const char *b, size_t s)
9898
return res;
9999
}
100100

101+
/*
102+
* See if a (non-star) lquery_level matches an ltree_level
103+
*
104+
* Does not consider level's possible LQL_NOT flag.
105+
*/
101106
static bool
102107
checkLevel(lquery_level *curq, ltree_level *curt)
103108
{
@@ -136,42 +141,49 @@ printFieldNot(FieldNot *fn ) {
136141
}
137142
*/
138143

139-
static struct
140-
{
141-
bool muse;
142-
uint32 high_pos;
143-
} SomeStack =
144-
145-
{
146-
false, 0,
147-
};
148-
144+
/*
145+
* Try to match an lquery (of query_numlevel items) to an ltree (of
146+
* tree_numlevel items)
147+
*
148+
* If the query contains any NOT flags, "ptr" must point to a FieldNot
149+
* workspace initialized with ptr->q == NULL. Otherwise it can be NULL.
150+
* (LQL_NOT flags will be ignored if ptr == NULL.)
151+
*
152+
* high_pos is the last ltree position the first lquery item is allowed
153+
* to match at; it should be zero for external calls.
154+
*
155+
* force_advance must be false except in internal recursive calls.
156+
*/
149157
static bool
150-
checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_numlevel, FieldNot *ptr)
158+
checkCond(lquery_level *curq, int query_numlevel,
159+
ltree_level *curt, int tree_numlevel,
160+
FieldNot *ptr,
161+
uint32 high_pos,
162+
bool force_advance)
151163
{
152-
uint32 low_pos = 0,
153-
high_pos = 0,
154-
cur_tpos = 0;
155-
int tlen = tree_numlevel,
164+
uint32 low_pos = 0, /* first allowed ltree position for match */
165+
cur_tpos = 0; /* ltree position of curt */
166+
int tlen = tree_numlevel, /* counts of remaining items */
156167
qlen = query_numlevel;
157-
int isok;
158168
lquery_level *prevq = NULL;
159-
ltree_level *prevt = NULL;
160169

161-
if (SomeStack.muse)
170+
/* advance curq (setting up prevq) if requested */
171+
if (force_advance)
162172
{
163-
high_pos = SomeStack.high_pos;
164-
qlen--;
173+
Assert(qlen > 0);
165174
prevq = curq;
166175
curq = LQL_NEXT(curq);
167-
SomeStack.muse = false;
176+
qlen--;
168177
}
169178

170179
while (tlen > 0 && qlen > 0)
171180
{
172181
if (curq->numvar)
173182
{
174-
prevt = curt;
183+
/* Current query item is not '*' */
184+
ltree_level *prevt = curt;
185+
186+
/* skip tree items that must be ignored due to prior * items */
175187
while (cur_tpos < low_pos)
176188
{
177189
curt = LEVEL_NEXT(curt);
@@ -183,8 +195,9 @@ checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_nu
183195
ptr->nt++;
184196
}
185197

186-
if (ptr && curq->flag & LQL_NOT)
198+
if (ptr && (curq->flag & LQL_NOT))
187199
{
200+
/* Deal with a NOT item */
188201
if (!(prevq && prevq->numvar == 0))
189202
prevq = curq;
190203
if (ptr->q == NULL)
@@ -212,33 +225,42 @@ checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_nu
212225
}
213226
else
214227
{
215-
isok = false;
228+
/* Not a NOT item, check for normal match */
229+
bool isok = false;
230+
216231
while (cur_tpos <= high_pos && tlen > 0 && !isok)
217232
{
218233
isok = checkLevel(curq, curt);
219234
curt = LEVEL_NEXT(curt);
220235
tlen--;
221236
cur_tpos++;
222-
if (isok && prevq && prevq->numvar == 0 && tlen > 0 && cur_tpos <= high_pos)
237+
if (isok && prevq && prevq->numvar == 0 &&
238+
tlen > 0 && cur_tpos <= high_pos)
223239
{
224240
FieldNot tmpptr;
225241

226242
if (ptr)
227243
memcpy(&tmpptr, ptr, sizeof(FieldNot));
228-
SomeStack.high_pos = high_pos - cur_tpos;
229-
SomeStack.muse = true;
230-
if (checkCond(prevq, qlen + 1, curt, tlen, (ptr) ? &tmpptr : NULL))
244+
if (checkCond(prevq, qlen + 1,
245+
curt, tlen,
246+
(ptr) ? &tmpptr : NULL,
247+
high_pos - cur_tpos,
248+
true))
231249
return true;
232250
}
233-
if (!isok && ptr)
251+
if (!isok && ptr && ptr->q)
234252
ptr->nt++;
235253
}
236254
if (!isok)
237255
return false;
238256

239257
if (ptr && ptr->q)
240258
{
241-
if (checkCond(ptr->q, ptr->nq, ptr->t, ptr->nt, NULL))
259+
if (checkCond(ptr->q, ptr->nq,
260+
ptr->t, ptr->nt,
261+
NULL,
262+
0,
263+
false))
242264
return false;
243265
ptr->q = NULL;
244266
}
@@ -248,8 +270,14 @@ checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_nu
248270
}
249271
else
250272
{
251-
low_pos = cur_tpos + curq->low;
252-
high_pos = cur_tpos + curq->high;
273+
/* Current query item is '*' */
274+
low_pos += curq->low;
275+
276+
if (low_pos > tree_numlevel)
277+
return false;
278+
279+
high_pos = Min(high_pos + curq->high, tree_numlevel);
280+
253281
if (ptr && ptr->q)
254282
{
255283
ptr->nq++;
@@ -263,9 +291,11 @@ checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_nu
263291
qlen--;
264292
}
265293

294+
/* Fail if we've already run out of ltree items */
266295
if (low_pos > tree_numlevel || tree_numlevel > high_pos)
267296
return false;
268297

298+
/* Remaining lquery items must be NOT or '*' items */
269299
while (qlen > 0)
270300
{
271301
if (curq->numvar)
@@ -275,18 +305,29 @@ checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_nu
275305
}
276306
else
277307
{
278-
low_pos = cur_tpos + curq->low;
279-
high_pos = cur_tpos + curq->high;
308+
low_pos += curq->low;
309+
310+
if (low_pos > tree_numlevel)
311+
return false;
312+
313+
high_pos = Min(high_pos + curq->high, tree_numlevel);
280314
}
281315

282316
curq = LQL_NEXT(curq);
283317
qlen--;
284318
}
285319

320+
/* Fail if trailing '*'s require more ltree items than we have */
286321
if (low_pos > tree_numlevel || tree_numlevel > high_pos)
287322
return false;
288323

289-
if (ptr && ptr->q && checkCond(ptr->q, ptr->nq, ptr->t, ptr->nt, NULL))
324+
/* Finish pending NOT check, if any */
325+
if (ptr && ptr->q &&
326+
checkCond(ptr->q, ptr->nq,
327+
ptr->t, ptr->nt,
328+
NULL,
329+
0,
330+
false))
290331
return false;
291332

292333
return true;
@@ -306,12 +347,18 @@ ltq_regex(PG_FUNCTION_ARGS)
306347
fn.q = NULL;
307348

308349
res = checkCond(LQUERY_FIRST(query), query->numlevel,
309-
LTREE_FIRST(tree), tree->numlevel, &fn);
350+
LTREE_FIRST(tree), tree->numlevel,
351+
&fn,
352+
0,
353+
false);
310354
}
311355
else
312356
{
313357
res = checkCond(LQUERY_FIRST(query), query->numlevel,
314-
LTREE_FIRST(tree), tree->numlevel, NULL);
358+
LTREE_FIRST(tree), tree->numlevel,
359+
NULL,
360+
0,
361+
false);
315362
}
316363

317364
PG_FREE_IF_COPY(tree, 0);

contrib/ltree/ltree.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ typedef struct
3434
{
3535
int32 val;
3636
uint16 len;
37-
uint8 flag;
37+
uint8 flag; /* see LVAR_xxx flags below */
3838
char name[FLEXIBLE_ARRAY_MEMBER];
3939
} lquery_variant;
4040

@@ -47,11 +47,12 @@ typedef struct
4747

4848
typedef struct
4949
{
50-
uint16 totallen;
51-
uint16 flag;
52-
uint16 numvar;
53-
uint16 low;
54-
uint16 high;
50+
uint16 totallen; /* total length of this level, in bytes */
51+
uint16 flag; /* see LQL_xxx flags below */
52+
uint16 numvar; /* number of variants; 0 means '*' */
53+
uint16 low; /* minimum repeat count for '*' */
54+
uint16 high; /* maximum repeat count for '*' */
55+
/* Array of maxalign'd lquery_variant structs follows: */
5556
char variants[FLEXIBLE_ARRAY_MEMBER];
5657
} lquery_level;
5758

@@ -60,6 +61,7 @@ typedef struct
6061
#define LQL_FIRST(x) ( (lquery_variant*)( ((char*)(x))+LQL_HDRSIZE ) )
6162

6263
#define LQL_NOT 0x10
64+
6365
#ifdef LOWER_NODE
6466
#define FLG_CANLOOKSIGN(x) ( ( (x) & ( LQL_NOT | LVAR_ANYEND | LVAR_SUBLEXEME ) ) == 0 )
6567
#else
@@ -70,9 +72,10 @@ typedef struct
7072
typedef struct
7173
{
7274
int32 vl_len_; /* varlena header (do not touch directly!) */
73-
uint16 numlevel;
75+
uint16 numlevel; /* number of lquery_levels */
7476
uint16 firstgood;
75-
uint16 flag;
77+
uint16 flag; /* see LQUERY_xxx flags below */
78+
/* Array of maxalign'd lquery_level structs follows: */
7679
char data[FLEXIBLE_ARRAY_MEMBER];
7780
} lquery;
7881

contrib/ltree/sql/ltree.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ SELECT 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*';
179179
SELECT 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*';
180180
SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*';
181181
SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*';
182-
182+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2}.*{2}';
183+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{2}.e';
184+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{4}';
185+
SELECT 'a.b.c.d.e'::ltree ~ 'a.*{5}.*';
183186

184187
SELECT 'QWER_TY'::ltree ~ 'q%@*';
185188
SELECT 'QWER_TY'::ltree ~ 'Q_t%@*';

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy