Age Owner Branch data TLA 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 : :
7929 bruce@momjian.us 16 :CBC 3 : PG_FUNCTION_INFO_V1(ltq_regex);
17 : 2 : PG_FUNCTION_INFO_V1(ltq_rregex);
18 : :
7725 19 : 3 : PG_FUNCTION_INFO_V1(lt_q_regex);
20 : 2 : PG_FUNCTION_INFO_V1(lt_q_rregex);
21 : :
22 : : #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
23 : :
24 : : static char *
6619 neilc@samurai.com 25 : 36 : getlexeme(char *start, char *end, int *len)
26 : : {
27 : : char *ptr;
28 : :
556 tgl@sss.pgh.pa.us 29 [ + + + + ]: 44 : while (start < end && t_iseq(start, '_'))
30 : 8 : start += pg_mblen(start);
31 : :
7929 bruce@momjian.us 32 : 36 : ptr = start;
5767 teodor@sigaev.ru 33 [ + + ]: 36 : if (ptr >= end)
7929 bruce@momjian.us 34 : 8 : return NULL;
35 : :
556 tgl@sss.pgh.pa.us 36 [ + + + + ]: 114 : while (ptr < end && !t_iseq(ptr, '_'))
37 : 86 : ptr += pg_mblen(ptr);
38 : :
7929 bruce@momjian.us 39 : 28 : *len = ptr - start;
40 : 28 : return start;
41 : : }
42 : :
43 : : bool
1789 tgl@sss.pgh.pa.us 44 : 8 : compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *, const char *, size_t), bool anyend)
45 : : {
7893 bruce@momjian.us 46 : 8 : char *endt = t->name + t->len;
47 : 8 : char *endq = qn + len;
48 : : char *tn;
49 : : int lent,
50 : : lenq;
51 : : bool isok;
52 : :
6619 neilc@samurai.com 53 [ + + ]: 16 : while ((qn = getlexeme(qn, endq, &lenq)) != NULL)
54 : : {
7893 bruce@momjian.us 55 : 12 : tn = t->name;
7929 56 : 12 : isok = false;
6619 neilc@samurai.com 57 [ + + ]: 20 : while ((tn = getlexeme(tn, endt, &lent)) != NULL)
58 : : {
1536 alvherre@alvh.no-ip. 59 [ + - + - : 32 : if ((lent == lenq || (lent > lenq && anyend)) &&
+ - + + ]
7893 bruce@momjian.us 60 : 16 : (*cmpptr) (qn, tn, lenq) == 0)
61 : : {
62 : :
63 : 8 : isok = true;
7929 64 : 8 : break;
65 : : }
66 : 8 : tn += lent;
67 : : }
68 : :
7893 69 [ + + ]: 12 : if (!isok)
7929 70 : 4 : return false;
71 : 8 : qn += lenq;
72 : : }
73 : :
74 : 4 : return true;
75 : : }
76 : :
77 : : int
5767 teodor@sigaev.ru 78 : 26 : ltree_strncasecmp(const char *a, const char *b, size_t s)
79 : : {
4814 peter_e@gmx.net 80 : 26 : char *al = str_tolower(a, s, DEFAULT_COLLATION_OID);
81 : 26 : char *bl = str_tolower(b, s, DEFAULT_COLLATION_OID);
82 : : int res;
83 : :
5421 bruce@momjian.us 84 : 26 : res = strncmp(al, bl, s);
85 : :
5767 teodor@sigaev.ru 86 : 26 : pfree(al);
87 : 26 : pfree(bl);
88 : :
89 : 26 : return res;
90 : : }
91 : :
92 : : /*
93 : : * See if an lquery_level matches an ltree_level
94 : : *
95 : : * This accounts for all flags including LQL_NOT, but does not
96 : : * consider repetition counts.
97 : : */
98 : : static bool
5421 bruce@momjian.us 99 : 216743 : checkLevel(lquery_level *curq, ltree_level *curt)
100 : : {
7929 101 : 216743 : lquery_variant *curvar = LQL_FIRST(curq);
102 : : bool success;
103 : :
1475 tgl@sss.pgh.pa.us 104 : 216743 : success = (curq->flag & LQL_NOT) ? false : true;
105 : :
106 : : /* numvar == 0 means '*' which matches anything */
107 [ + + ]: 216743 : if (curq->numvar == 0)
108 : 83195 : return success;
109 : :
110 [ + + ]: 260547 : for (int i = 0; i < curq->numvar; i++)
111 : : {
112 : : int (*cmpptr) (const char *, const char *, size_t);
113 : :
5767 teodor@sigaev.ru 114 [ + + ]: 133553 : cmpptr = (curvar->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;
115 : :
6619 neilc@samurai.com 116 [ + + ]: 133553 : if (curvar->flag & LVAR_SUBLEXEME)
117 : : {
1475 tgl@sss.pgh.pa.us 118 [ + + ]: 4 : if (compare_subnode(curt, curvar->name, curvar->len, cmpptr,
119 : 4 : (curvar->flag & LVAR_ANYEND)))
120 : 3 : return success;
121 : : }
1536 alvherre@alvh.no-ip. 122 [ + + ]: 133549 : else if ((curvar->len == curt->len ||
123 [ + + + + : 133552 : (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
+ + ]
7893 bruce@momjian.us 124 : 54991 : (*cmpptr) (curvar->name, curt->name, curvar->len) == 0)
1475 tgl@sss.pgh.pa.us 125 : 6551 : return success;
126 : :
7893 bruce@momjian.us 127 : 126999 : curvar = LVAR_NEXT(curvar);
128 : : }
1475 tgl@sss.pgh.pa.us 129 : 126994 : return !success;
130 : : }
131 : :
132 : : /*
133 : : * Try to match an lquery (of qlen items) to an ltree (of tlen items)
134 : : */
135 : : static bool
136 : 143414 : checkCond(lquery_level *curq, int qlen,
137 : : ltree_level *curt, int tlen)
138 : : {
139 : : /* Since this function recurses, it could be driven to stack overflow */
140 : 143414 : check_stack_depth();
141 : :
142 : : /* Pathological patterns could take awhile, too */
143 [ - + ]: 143414 : CHECK_FOR_INTERRUPTS();
144 : :
145 : : /* Loop while we have query items to consider */
146 [ + + ]: 162693 : while (qlen > 0)
147 : : {
148 : : int low,
149 : : high;
150 : : lquery_level *nextq;
151 : :
152 : : /*
153 : : * Get min and max repetition counts for this query item, dealing with
154 : : * the backwards-compatibility hack that the low/high fields aren't
155 : : * meaningful for non-'*' items unless LQL_COUNT is set.
156 : : */
157 [ + + + + ]: 158980 : if ((curq->flag & LQL_COUNT) || curq->numvar == 0)
158 : 13307 : low = curq->low, high = curq->high;
159 : : else
160 : 145673 : low = high = 1;
161 : :
162 : : /*
163 : : * We may limit "high" to the remaining text length; this avoids
164 : : * separate tests below.
165 : : */
166 [ + + ]: 158980 : if (high > tlen)
167 : 24988 : high = tlen;
168 : :
169 : : /* Fail if a match of required number of items is impossible */
170 [ + + ]: 158980 : if (high < low)
171 : 12173 : return false;
172 : :
173 : : /*
174 : : * Recursively check the rest of the pattern against each possible
175 : : * start point following some of this item's match(es).
176 : : */
177 : 146807 : nextq = LQL_NEXT(curq);
7929 bruce@momjian.us 178 : 146807 : qlen--;
179 : :
1475 tgl@sss.pgh.pa.us 180 [ + + ]: 236616 : for (int matchcnt = 0; matchcnt < high; matchcnt++)
181 : : {
182 : : /*
183 : : * If we've consumed an acceptable number of matches of this item,
184 : : * and the rest of the pattern matches beginning here, we're good.
185 : : */
186 [ + + + + ]: 217337 : if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen))
187 : 594 : return true;
188 : :
189 : : /*
190 : : * Otherwise, try to match one more text item to this query item.
191 : : */
192 [ + + ]: 216743 : if (!checkLevel(curq, curt))
1478 193 : 126934 : return false;
194 : :
1475 195 : 89809 : curt = LEVEL_NEXT(curt);
196 : 89809 : tlen--;
197 : : }
198 : :
199 : : /*
200 : : * Once we've consumed "high" matches, we can succeed only if the rest
201 : : * of the pattern matches beginning here. Loop around (if you prefer,
202 : : * think of this as tail recursion).
203 : : */
204 : 19279 : curq = nextq;
205 : : }
206 : :
207 : : /*
208 : : * Once we're out of query items, we match only if there's no remaining
209 : : * text either.
210 : : */
211 : 3713 : return (tlen == 0);
212 : : }
213 : :
214 : : Datum
7893 bruce@momjian.us 215 : 60096 : ltq_regex(PG_FUNCTION_ARGS)
216 : : {
2400 tgl@sss.pgh.pa.us 217 : 60096 : ltree *tree = PG_GETARG_LTREE_P(0);
218 : 60096 : lquery *query = PG_GETARG_LQUERY_P(1);
219 : : bool res;
220 : :
1475 221 : 60096 : res = checkCond(LQUERY_FIRST(query), query->numlevel,
222 : 60096 : LTREE_FIRST(tree), tree->numlevel);
223 : :
7893 bruce@momjian.us 224 [ + + ]: 60096 : PG_FREE_IF_COPY(tree, 0);
225 [ - + ]: 60096 : PG_FREE_IF_COPY(query, 1);
226 : 60096 : PG_RETURN_BOOL(res);
227 : : }
228 : :
229 : : Datum
7893 bruce@momjian.us 230 :UBC 0 : ltq_rregex(PG_FUNCTION_ARGS)
231 : : {
232 : 0 : PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex,
233 : : PG_GETARG_DATUM(1),
234 : : PG_GETARG_DATUM(0)
235 : : ));
236 : : }
237 : :
238 : : Datum
7725 bruce@momjian.us 239 :CBC 1565 : lt_q_regex(PG_FUNCTION_ARGS)
240 : : {
2400 tgl@sss.pgh.pa.us 241 : 1565 : ltree *tree = PG_GETARG_LTREE_P(0);
7559 bruce@momjian.us 242 : 1565 : ArrayType *_query = PG_GETARG_ARRAYTYPE_P(1);
243 [ - + ]: 1565 : lquery *query = (lquery *) ARR_DATA_PTR(_query);
244 : 1565 : bool res = false;
245 : 1565 : int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query));
246 : :
5163 tgl@sss.pgh.pa.us 247 [ - + ]: 1565 : if (ARR_NDIM(_query) > 1)
7559 bruce@momjian.us 248 [ # # ]:UBC 0 : ereport(ERROR,
249 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
250 : : errmsg("array must be one-dimensional")));
4844 tgl@sss.pgh.pa.us 251 [ - + ]:CBC 1565 : if (array_contains_nulls(_query))
6721 tgl@sss.pgh.pa.us 252 [ # # ]:UBC 0 : ereport(ERROR,
253 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
254 : : errmsg("array must not contain nulls")));
255 : :
7559 bruce@momjian.us 256 [ + + ]:CBC 4670 : while (num > 0)
257 : : {
7725 258 [ + + ]: 3119 : if (DatumGetBool(DirectFunctionCall2(ltq_regex,
259 : : PointerGetDatum(tree), PointerGetDatum(query))))
260 : : {
261 : :
262 : 14 : res = true;
263 : 14 : break;
264 : : }
265 : 3105 : num--;
266 : 3105 : query = NEXTVAL(query);
267 : : }
268 : :
269 [ + + ]: 1565 : PG_FREE_IF_COPY(tree, 0);
270 [ - + ]: 1565 : PG_FREE_IF_COPY(_query, 1);
271 : 1565 : PG_RETURN_BOOL(res);
272 : : }
273 : :
274 : : Datum
7725 bruce@momjian.us 275 :UBC 0 : lt_q_rregex(PG_FUNCTION_ARGS)
276 : : {
277 : 0 : PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex,
278 : : PG_GETARG_DATUM(1),
279 : : PG_GETARG_DATUM(0)
280 : : ));
281 : : }
|