/* * op function for ltree and lquery * Teodor Sigaev * contrib/ltree/lquery_op.c */ #include "postgres.h" #include #include "catalog/pg_collation.h" #include "ltree.h" #include "utils/formatting.h" PG_FUNCTION_INFO_V1(ltq_regex); PG_FUNCTION_INFO_V1(ltq_rregex); PG_FUNCTION_INFO_V1(lt_q_regex); PG_FUNCTION_INFO_V1(lt_q_rregex); #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) ) typedef struct { lquery_level *q; int nq; ltree_level *t; int nt; int posq; int post; } FieldNot; static char * getlexeme(char *start, char *end, int *len) { char *ptr; int charlen; while (start < end && (charlen = pg_mblen(start)) == 1 && t_iseq(start, '_')) start += charlen; ptr = start; if (ptr >= end) return NULL; while (ptr < end && !((charlen = pg_mblen(ptr)) == 1 && t_iseq(ptr, '_'))) ptr += charlen; *len = ptr - start; return start; } bool compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *, const char *, size_t), bool anyend) { char *endt = t->name + t->len; char *endq = qn + len; char *tn; int lent, lenq; bool isok; while ((qn = getlexeme(qn, endq, &lenq)) != NULL) { tn = t->name; isok = false; while ((tn = getlexeme(tn, endt, &lent)) != NULL) { if ((lent == lenq || (lent > lenq && anyend)) && (*cmpptr) (qn, tn, lenq) == 0) { isok = true; break; } tn += lent; } if (!isok) return false; qn += lenq; } return true; } int ltree_strncasecmp(const char *a, const char *b, size_t s) { char *al = str_tolower(a, s, DEFAULT_COLLATION_OID); char *bl = str_tolower(b, s, DEFAULT_COLLATION_OID); int res; res = strncmp(al, bl, s); pfree(al); pfree(bl); return res; } /* * See if a (non-star) lquery_level matches an ltree_level * * Does not consider level's possible LQL_NOT flag. */ static bool checkLevel(lquery_level *curq, ltree_level *curt) { int (*cmpptr) (const char *, const char *, size_t); lquery_variant *curvar = LQL_FIRST(curq); int i; for (i = 0; i < curq->numvar; i++) { cmpptr = (curvar->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp; if (curvar->flag & LVAR_SUBLEXEME) { if (compare_subnode(curt, curvar->name, curvar->len, cmpptr, (curvar->flag & LVAR_ANYEND))) return true; } else if ((curvar->len == curt->len || (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) && (*cmpptr) (curvar->name, curt->name, curvar->len) == 0) { return true; } curvar = LVAR_NEXT(curvar); } return false; } /* void printFieldNot(FieldNot *fn ) { while(fn->q) { elog(NOTICE,"posQ:%d lenQ:%d posT:%d lenT:%d", fn->posq,fn->nq,fn->post,fn->nt); fn++; } } */ /* * Try to match an lquery (of query_numlevel items) to an ltree (of * tree_numlevel items) * * If the query contains any NOT flags, "ptr" must point to a FieldNot * workspace initialized with ptr->q == NULL. Otherwise it can be NULL. * (LQL_NOT flags will be ignored if ptr == NULL.) * * high_pos is the last ltree position the first lquery item is allowed * to match at; it should be zero for external calls. * * force_advance must be false except in internal recursive calls. */ static bool checkCond(lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_numlevel, FieldNot *ptr, uint32 high_pos, bool force_advance) { uint32 low_pos = 0, /* first allowed ltree position for match */ cur_tpos = 0; /* ltree position of curt */ int tlen = tree_numlevel, /* counts of remaining items */ qlen = query_numlevel; lquery_level *prevq = NULL; /* advance curq (setting up prevq) if requested */ if (force_advance) { Assert(qlen > 0); prevq = curq; curq = LQL_NEXT(curq); qlen--; } while (tlen > 0 && qlen > 0) { if (curq->numvar) { /* Current query item is not '*' */ ltree_level *prevt = curt; /* skip tree items that must be ignored due to prior * items */ while (cur_tpos < low_pos) { curt = LEVEL_NEXT(curt); tlen--; cur_tpos++; if (tlen == 0) return false; if (ptr && ptr->q) ptr->nt++; } if (ptr && (curq->flag & LQL_NOT)) { /* Deal with a NOT item */ if (!(prevq && prevq->numvar == 0)) prevq = curq; if (ptr->q == NULL) { ptr->t = prevt; ptr->q = prevq; ptr->nt = 1; ptr->nq = 1 + ((prevq == curq) ? 0 : 1); ptr->posq = query_numlevel - qlen - ((prevq == curq) ? 0 : 1); ptr->post = cur_tpos; } else { ptr->nt++; ptr->nq++; } if (qlen == 1 && ptr->q->numvar == 0) ptr->nt = tree_numlevel - ptr->post; curt = LEVEL_NEXT(curt); tlen--; cur_tpos++; if (high_pos < cur_tpos) high_pos++; } else { /* Not a NOT item, check for normal match */ bool isok = false; while (cur_tpos <= high_pos && tlen > 0 && !isok) { isok = checkLevel(curq, curt); curt = LEVEL_NEXT(curt); tlen--; cur_tpos++; if (isok && prevq && prevq->numvar == 0 && tlen > 0 && cur_tpos <= high_pos) { FieldNot tmpptr; if (ptr) memcpy(&tmpptr, ptr, sizeof(FieldNot)); if (checkCond(prevq, qlen + 1, curt, tlen, (ptr) ? &tmpptr : NULL, high_pos - cur_tpos, true)) return true; } if (!isok && ptr && ptr->q) ptr->nt++; } if (!isok) return false; if (ptr && ptr->q) { if (checkCond(ptr->q, ptr->nq, ptr->t, ptr->nt, NULL, 0, false)) return false; ptr->q = NULL; } low_pos = cur_tpos; high_pos = cur_tpos; } } else { /* Current query item is '*' */ low_pos += curq->low; if (low_pos > tree_numlevel) return false; high_pos = Min(high_pos + curq->high, tree_numlevel); if (ptr && ptr->q) { ptr->nq++; if (qlen == 1) ptr->nt = tree_numlevel - ptr->post; } } prevq = curq; curq = LQL_NEXT(curq); qlen--; } /* Fail if we've already run out of ltree items */ if (low_pos > tree_numlevel || tree_numlevel > high_pos) return false; /* Remaining lquery items must be NOT or '*' items */ while (qlen > 0) { if (curq->numvar) { if (!(curq->flag & LQL_NOT)) return false; } else { low_pos += curq->low; if (low_pos > tree_numlevel) return false; high_pos = Min(high_pos + curq->high, tree_numlevel); } curq = LQL_NEXT(curq); qlen--; } /* Fail if trailing '*'s require more ltree items than we have */ if (low_pos > tree_numlevel || tree_numlevel > high_pos) return false; /* Finish pending NOT check, if any */ if (ptr && ptr->q && checkCond(ptr->q, ptr->nq, ptr->t, ptr->nt, NULL, 0, false)) return false; return true; } Datum ltq_regex(PG_FUNCTION_ARGS) { ltree *tree = PG_GETARG_LTREE_P(0); lquery *query = PG_GETARG_LQUERY_P(1); bool res = false; if (query->flag & LQUERY_HASNOT) { FieldNot fn; fn.q = NULL; res = checkCond(LQUERY_FIRST(query), query->numlevel, LTREE_FIRST(tree), tree->numlevel, &fn, 0, false); } else { res = checkCond(LQUERY_FIRST(query), query->numlevel, LTREE_FIRST(tree), tree->numlevel, NULL, 0, false); } PG_FREE_IF_COPY(tree, 0); PG_FREE_IF_COPY(query, 1); PG_RETURN_BOOL(res); } Datum ltq_rregex(PG_FUNCTION_ARGS) { PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex, PG_GETARG_DATUM(1), PG_GETARG_DATUM(0) )); } Datum lt_q_regex(PG_FUNCTION_ARGS) { ltree *tree = PG_GETARG_LTREE_P(0); ArrayType *_query = PG_GETARG_ARRAYTYPE_P(1); lquery *query = (lquery *) ARR_DATA_PTR(_query); bool res = false; int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query)); if (ARR_NDIM(_query) > 1) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must be one-dimensional"))); if (array_contains_nulls(_query)) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("array must not contain nulls"))); while (num > 0) { if (DatumGetBool(DirectFunctionCall2(ltq_regex, PointerGetDatum(tree), PointerGetDatum(query)))) { res = true; break; } num--; query = NEXTVAL(query); } PG_FREE_IF_COPY(tree, 0); PG_FREE_IF_COPY(_query, 1); PG_RETURN_BOOL(res); } Datum lt_q_rregex(PG_FUNCTION_ARGS) { PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex, PG_GETARG_DATUM(1), PG_GETARG_DATUM(0) )); }