2 * op function for ltree and lquery
3 * Teodor Sigaev <teodor@stack.net>
4 * contrib/ltree/lquery_op.c
10 #include "catalog/pg_collation.h"
12 #include "miscadmin.h"
13 #include "utils/array.h"
14 #include "utils/formatting.h"
16 PG_FUNCTION_INFO_V1(ltq_regex
);
17 PG_FUNCTION_INFO_V1(ltq_rregex
);
19 PG_FUNCTION_INFO_V1(lt_q_regex
);
20 PG_FUNCTION_INFO_V1(lt_q_rregex
);
22 #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
25 getlexeme(char *start
, char *end
, int *len
)
30 while (start
< end
&& (charlen
= pg_mblen(start
)) == 1 && t_iseq(start
, '_'))
37 while (ptr
< end
&& !((charlen
= pg_mblen(ptr
)) == 1 && t_iseq(ptr
, '_')))
45 compare_subnode(ltree_level
*t
, char *qn
, int len
, int (*cmpptr
) (const char *, const char *, size_t), bool anyend
)
47 char *endt
= t
->name
+ t
->len
;
48 char *endq
= qn
+ len
;
54 while ((qn
= getlexeme(qn
, endq
, &lenq
)) != NULL
)
58 while ((tn
= getlexeme(tn
, endt
, &lent
)) != NULL
)
60 if ((lent
== lenq
|| (lent
> lenq
&& anyend
)) &&
61 (*cmpptr
) (qn
, tn
, lenq
) == 0)
79 ltree_strncasecmp(const char *a
, const char *b
, size_t s
)
81 char *al
= str_tolower(a
, s
, DEFAULT_COLLATION_OID
);
82 char *bl
= str_tolower(b
, s
, DEFAULT_COLLATION_OID
);
85 res
= strncmp(al
, bl
, s
);
94 * See if an lquery_level matches an ltree_level
96 * This accounts for all flags including LQL_NOT, but does not
97 * consider repetition counts.
100 checkLevel(lquery_level
*curq
, ltree_level
*curt
)
102 lquery_variant
*curvar
= LQL_FIRST(curq
);
105 success
= (curq
->flag
& LQL_NOT
) ? false : true;
107 /* numvar == 0 means '*' which matches anything */
108 if (curq
->numvar
== 0)
111 for (int i
= 0; i
< curq
->numvar
; i
++)
113 int (*cmpptr
) (const char *, const char *, size_t);
115 cmpptr
= (curvar
->flag
& LVAR_INCASE
) ? ltree_strncasecmp
: strncmp
;
117 if (curvar
->flag
& LVAR_SUBLEXEME
)
119 if (compare_subnode(curt
, curvar
->name
, curvar
->len
, cmpptr
,
120 (curvar
->flag
& LVAR_ANYEND
)))
123 else if ((curvar
->len
== curt
->len
||
124 (curt
->len
> curvar
->len
&& (curvar
->flag
& LVAR_ANYEND
))) &&
125 (*cmpptr
) (curvar
->name
, curt
->name
, curvar
->len
) == 0)
128 curvar
= LVAR_NEXT(curvar
);
134 * Try to match an lquery (of qlen items) to an ltree (of tlen items)
137 checkCond(lquery_level
*curq
, int qlen
,
138 ltree_level
*curt
, int tlen
)
140 /* Since this function recurses, it could be driven to stack overflow */
143 /* Pathological patterns could take awhile, too */
144 CHECK_FOR_INTERRUPTS();
146 /* Loop while we have query items to consider */
154 * Get min and max repetition counts for this query item, dealing with
155 * the backwards-compatibility hack that the low/high fields aren't
156 * meaningful for non-'*' items unless LQL_COUNT is set.
158 if ((curq
->flag
& LQL_COUNT
) || curq
->numvar
== 0)
159 low
= curq
->low
, high
= curq
->high
;
164 * We may limit "high" to the remaining text length; this avoids
165 * separate tests below.
170 /* Fail if a match of required number of items is impossible */
175 * Recursively check the rest of the pattern against each possible
176 * start point following some of this item's match(es).
178 nextq
= LQL_NEXT(curq
);
181 for (int matchcnt
= 0; matchcnt
< high
; matchcnt
++)
184 * If we've consumed an acceptable number of matches of this item,
185 * and the rest of the pattern matches beginning here, we're good.
187 if (matchcnt
>= low
&& checkCond(nextq
, qlen
, curt
, tlen
))
191 * Otherwise, try to match one more text item to this query item.
193 if (!checkLevel(curq
, curt
))
196 curt
= LEVEL_NEXT(curt
);
201 * Once we've consumed "high" matches, we can succeed only if the rest
202 * of the pattern matches beginning here. Loop around (if you prefer,
203 * think of this as tail recursion).
209 * Once we're out of query items, we match only if there's no remaining
216 ltq_regex(PG_FUNCTION_ARGS
)
218 ltree
*tree
= PG_GETARG_LTREE_P(0);
219 lquery
*query
= PG_GETARG_LQUERY_P(1);
222 res
= checkCond(LQUERY_FIRST(query
), query
->numlevel
,
223 LTREE_FIRST(tree
), tree
->numlevel
);
225 PG_FREE_IF_COPY(tree
, 0);
226 PG_FREE_IF_COPY(query
, 1);
231 ltq_rregex(PG_FUNCTION_ARGS
)
233 PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex
,
240 lt_q_regex(PG_FUNCTION_ARGS
)
242 ltree
*tree
= PG_GETARG_LTREE_P(0);
243 ArrayType
*_query
= PG_GETARG_ARRAYTYPE_P(1);
244 lquery
*query
= (lquery
*) ARR_DATA_PTR(_query
);
246 int num
= ArrayGetNItems(ARR_NDIM(_query
), ARR_DIMS(_query
));
248 if (ARR_NDIM(_query
) > 1)
250 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR
),
251 errmsg("array must be one-dimensional")));
252 if (array_contains_nulls(_query
))
254 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED
),
255 errmsg("array must not contain nulls")));
259 if (DatumGetBool(DirectFunctionCall2(ltq_regex
,
260 PointerGetDatum(tree
), PointerGetDatum(query
))))
267 query
= NEXTVAL(query
);
270 PG_FREE_IF_COPY(tree
, 0);
271 PG_FREE_IF_COPY(_query
, 1);
276 lt_q_rregex(PG_FUNCTION_ARGS
)
278 PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex
,