Line data Source code
1 : /*
2 : * op function for ltree and lquery
3 : * Teodor Sigaev <teodor@stack.net>
4 : * contrib/ltree/lquery_op.c
5 : */
6 : #include "postgres.h"
7 :
8 : #include <ctype.h>
9 :
10 : #include "catalog/pg_collation.h"
11 : #include "ltree.h"
12 : #include "miscadmin.h"
13 : #include "utils/array.h"
14 : #include "utils/formatting.h"
15 :
16 0 : PG_FUNCTION_INFO_V1(ltq_regex);
17 0 : PG_FUNCTION_INFO_V1(ltq_rregex);
18 :
19 0 : PG_FUNCTION_INFO_V1(lt_q_regex);
20 0 : PG_FUNCTION_INFO_V1(lt_q_rregex);
21 :
22 : #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
23 :
24 : static char *
25 0 : getlexeme(char *start, char *end, int *len)
26 : {
27 0 : char *ptr;
28 :
29 0 : while (start < end && t_iseq(start, '_'))
30 0 : start += pg_mblen(start);
31 :
32 0 : ptr = start;
33 0 : if (ptr >= end)
34 0 : return NULL;
35 :
36 0 : while (ptr < end && !t_iseq(ptr, '_'))
37 0 : ptr += pg_mblen(ptr);
38 :
39 0 : *len = ptr - start;
40 0 : return start;
41 0 : }
42 :
43 : bool
44 0 : compare_subnode(ltree_level *t, char *qn, int len,
45 : ltree_prefix_eq_func prefix_eq, bool anyend)
46 : {
47 0 : char *endt = t->name + t->len;
48 0 : char *endq = qn + len;
49 0 : char *tn;
50 0 : int lent,
51 : lenq;
52 0 : bool isok;
53 :
54 0 : while ((qn = getlexeme(qn, endq, &lenq)) != NULL)
55 : {
56 0 : tn = t->name;
57 0 : isok = false;
58 0 : while ((tn = getlexeme(tn, endt, &lent)) != NULL)
59 : {
60 0 : if ((lent == lenq || (lent > lenq && anyend)) &&
61 0 : (*prefix_eq) (qn, lenq, tn, lent))
62 : {
63 :
64 0 : isok = true;
65 0 : break;
66 : }
67 0 : tn += lent;
68 : }
69 :
70 0 : if (!isok)
71 0 : return false;
72 0 : qn += lenq;
73 : }
74 :
75 0 : return true;
76 0 : }
77 :
78 : /*
79 : * Check if 'a' is a prefix of 'b'.
80 : */
81 : bool
82 0 : ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
83 : {
84 0 : if (a_sz > b_sz)
85 0 : return false;
86 : else
87 0 : return (strncmp(a, b, a_sz) == 0);
88 0 : }
89 :
90 : /*
91 : * Case-insensitive check if 'a' is a prefix of 'b'.
92 : */
93 : bool
94 0 : ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
95 : {
96 : static pg_locale_t locale = NULL;
97 0 : size_t al_sz = a_sz + 1;
98 0 : size_t al_len;
99 0 : char *al = palloc(al_sz);
100 0 : size_t bl_sz = b_sz + 1;
101 0 : size_t bl_len;
102 0 : char *bl = palloc(bl_sz);
103 0 : bool res;
104 :
105 0 : if (!locale)
106 0 : locale = pg_database_locale();
107 :
108 : /* casefold both a and b */
109 :
110 0 : al_len = pg_strfold(al, al_sz, a, a_sz, locale);
111 0 : if (al_len + 1 > al_sz)
112 : {
113 : /* grow buffer if needed and retry */
114 0 : al_sz = al_len + 1;
115 0 : al = repalloc(al, al_sz);
116 0 : al_len = pg_strfold(al, al_sz, a, a_sz, locale);
117 0 : Assert(al_len + 1 <= al_sz);
118 0 : }
119 :
120 0 : bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
121 0 : if (bl_len + 1 > bl_sz)
122 : {
123 : /* grow buffer if needed and retry */
124 0 : bl_sz = bl_len + 1;
125 0 : bl = repalloc(bl, bl_sz);
126 0 : bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
127 0 : Assert(bl_len + 1 <= bl_sz);
128 0 : }
129 :
130 0 : if (al_len > bl_len)
131 0 : res = false;
132 : else
133 0 : res = (strncmp(al, bl, al_len) == 0);
134 :
135 0 : pfree(al);
136 0 : pfree(bl);
137 :
138 0 : return res;
139 0 : }
140 :
141 : /*
142 : * See if an lquery_level matches an ltree_level
143 : *
144 : * This accounts for all flags including LQL_NOT, but does not
145 : * consider repetition counts.
146 : */
147 : static bool
148 0 : checkLevel(lquery_level *curq, ltree_level *curt)
149 : {
150 0 : lquery_variant *curvar = LQL_FIRST(curq);
151 0 : bool success;
152 :
153 0 : success = (curq->flag & LQL_NOT) ? false : true;
154 :
155 : /* numvar == 0 means '*' which matches anything */
156 0 : if (curq->numvar == 0)
157 0 : return success;
158 :
159 0 : for (int i = 0; i < curq->numvar; i++)
160 : {
161 0 : ltree_prefix_eq_func prefix_eq;
162 :
163 0 : prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
164 :
165 0 : if (curvar->flag & LVAR_SUBLEXEME)
166 : {
167 0 : if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
168 0 : (curvar->flag & LVAR_ANYEND)))
169 0 : return success;
170 0 : }
171 0 : else if ((curvar->len == curt->len ||
172 0 : (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
173 0 : (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
174 0 : return success;
175 :
176 0 : curvar = LVAR_NEXT(curvar);
177 0 : }
178 0 : return !success;
179 0 : }
180 :
181 : /*
182 : * Try to match an lquery (of qlen items) to an ltree (of tlen items)
183 : */
184 : static bool
185 0 : checkCond(lquery_level *curq, int qlen,
186 : ltree_level *curt, int tlen)
187 : {
188 : /* Since this function recurses, it could be driven to stack overflow */
189 0 : check_stack_depth();
190 :
191 : /* Pathological patterns could take awhile, too */
192 0 : CHECK_FOR_INTERRUPTS();
193 :
194 : /* Loop while we have query items to consider */
195 0 : while (qlen > 0)
196 : {
197 0 : int low,
198 : high;
199 0 : lquery_level *nextq;
200 :
201 : /*
202 : * Get min and max repetition counts for this query item, dealing with
203 : * the backwards-compatibility hack that the low/high fields aren't
204 : * meaningful for non-'*' items unless LQL_COUNT is set.
205 : */
206 0 : if ((curq->flag & LQL_COUNT) || curq->numvar == 0)
207 0 : low = curq->low, high = curq->high;
208 : else
209 0 : low = high = 1;
210 :
211 : /*
212 : * We may limit "high" to the remaining text length; this avoids
213 : * separate tests below.
214 : */
215 0 : if (high > tlen)
216 0 : high = tlen;
217 :
218 : /* Fail if a match of required number of items is impossible */
219 0 : if (high < low)
220 0 : return false;
221 :
222 : /*
223 : * Recursively check the rest of the pattern against each possible
224 : * start point following some of this item's match(es).
225 : */
226 0 : nextq = LQL_NEXT(curq);
227 0 : qlen--;
228 :
229 0 : for (int matchcnt = 0; matchcnt < high; matchcnt++)
230 : {
231 : /*
232 : * If we've consumed an acceptable number of matches of this item,
233 : * and the rest of the pattern matches beginning here, we're good.
234 : */
235 0 : if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen))
236 0 : return true;
237 :
238 : /*
239 : * Otherwise, try to match one more text item to this query item.
240 : */
241 0 : if (!checkLevel(curq, curt))
242 0 : return false;
243 :
244 0 : curt = LEVEL_NEXT(curt);
245 0 : tlen--;
246 0 : }
247 :
248 : /*
249 : * Once we've consumed "high" matches, we can succeed only if the rest
250 : * of the pattern matches beginning here. Loop around (if you prefer,
251 : * think of this as tail recursion).
252 : */
253 0 : curq = nextq;
254 0 : }
255 :
256 : /*
257 : * Once we're out of query items, we match only if there's no remaining
258 : * text either.
259 : */
260 0 : return (tlen == 0);
261 0 : }
262 :
263 : Datum
264 0 : ltq_regex(PG_FUNCTION_ARGS)
265 : {
266 0 : ltree *tree = PG_GETARG_LTREE_P(0);
267 0 : lquery *query = PG_GETARG_LQUERY_P(1);
268 0 : bool res;
269 :
270 0 : res = checkCond(LQUERY_FIRST(query), query->numlevel,
271 0 : LTREE_FIRST(tree), tree->numlevel);
272 :
273 0 : PG_FREE_IF_COPY(tree, 0);
274 0 : PG_FREE_IF_COPY(query, 1);
275 0 : PG_RETURN_BOOL(res);
276 0 : }
277 :
278 : Datum
279 0 : ltq_rregex(PG_FUNCTION_ARGS)
280 : {
281 0 : PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex,
282 : PG_GETARG_DATUM(1),
283 : PG_GETARG_DATUM(0)
284 : ));
285 : }
286 :
287 : Datum
288 0 : lt_q_regex(PG_FUNCTION_ARGS)
289 : {
290 0 : ltree *tree = PG_GETARG_LTREE_P(0);
291 0 : ArrayType *_query = PG_GETARG_ARRAYTYPE_P(1);
292 0 : lquery *query = (lquery *) ARR_DATA_PTR(_query);
293 0 : bool res = false;
294 0 : int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query));
295 :
296 0 : if (ARR_NDIM(_query) > 1)
297 0 : ereport(ERROR,
298 : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
299 : errmsg("array must be one-dimensional")));
300 0 : if (array_contains_nulls(_query))
301 0 : ereport(ERROR,
302 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
303 : errmsg("array must not contain nulls")));
304 :
305 0 : while (num > 0)
306 : {
307 0 : if (DatumGetBool(DirectFunctionCall2(ltq_regex,
308 : PointerGetDatum(tree), PointerGetDatum(query))))
309 : {
310 :
311 0 : res = true;
312 0 : break;
313 : }
314 0 : num--;
315 0 : query = NEXTVAL(query);
316 : }
317 :
318 0 : PG_FREE_IF_COPY(tree, 0);
319 0 : PG_FREE_IF_COPY(_query, 1);
320 0 : PG_RETURN_BOOL(res);
321 0 : }
322 :
323 : Datum
324 0 : lt_q_rregex(PG_FUNCTION_ARGS)
325 : {
326 0 : PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex,
327 : PG_GETARG_DATUM(1),
328 : PG_GETARG_DATUM(0)
329 : ));
330 : }
|