summaryrefslogtreecommitdiffstats
path: root/contrib/ltree/lquery_op.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/ltree/lquery_op.c')
-rw-r--r--contrib/ltree/lquery_op.c281
1 files changed, 281 insertions, 0 deletions
diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
new file mode 100644
index 0000000..a6466f5
--- /dev/null
+++ b/contrib/ltree/lquery_op.c
@@ -0,0 +1,281 @@
+/*
+ * op function for ltree and lquery
+ * Teodor Sigaev <teodor@stack.net>
+ * contrib/ltree/lquery_op.c
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "catalog/pg_collation.h"
+#include "ltree.h"
+#include "miscadmin.h"
+#include "utils/array.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) ) ) )
+
+static char *
+getlexeme(char *start, char *end, int *len)
+{
+ char *ptr;
+
+ while (start < end && t_iseq(start, '_'))
+ start += pg_mblen(start);
+
+ ptr = start;
+ if (ptr >= end)
+ return NULL;
+
+ while (ptr < end && !t_iseq(ptr, '_'))
+ ptr += pg_mblen(ptr);
+
+ *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 an lquery_level matches an ltree_level
+ *
+ * This accounts for all flags including LQL_NOT, but does not
+ * consider repetition counts.
+ */
+static bool
+checkLevel(lquery_level *curq, ltree_level *curt)
+{
+ lquery_variant *curvar = LQL_FIRST(curq);
+ bool success;
+
+ success = (curq->flag & LQL_NOT) ? false : true;
+
+ /* numvar == 0 means '*' which matches anything */
+ if (curq->numvar == 0)
+ return success;
+
+ for (int i = 0; i < curq->numvar; i++)
+ {
+ int (*cmpptr) (const char *, const char *, size_t);
+
+ 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 success;
+ }
+ else if ((curvar->len == curt->len ||
+ (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
+ (*cmpptr) (curvar->name, curt->name, curvar->len) == 0)
+ return success;
+
+ curvar = LVAR_NEXT(curvar);
+ }
+ return !success;
+}
+
+/*
+ * Try to match an lquery (of qlen items) to an ltree (of tlen items)
+ */
+static bool
+checkCond(lquery_level *curq, int qlen,
+ ltree_level *curt, int tlen)
+{
+ /* Since this function recurses, it could be driven to stack overflow */
+ check_stack_depth();
+
+ /* Pathological patterns could take awhile, too */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Loop while we have query items to consider */
+ while (qlen > 0)
+ {
+ int low,
+ high;
+ lquery_level *nextq;
+
+ /*
+ * Get min and max repetition counts for this query item, dealing with
+ * the backwards-compatibility hack that the low/high fields aren't
+ * meaningful for non-'*' items unless LQL_COUNT is set.
+ */
+ if ((curq->flag & LQL_COUNT) || curq->numvar == 0)
+ low = curq->low, high = curq->high;
+ else
+ low = high = 1;
+
+ /*
+ * We may limit "high" to the remaining text length; this avoids
+ * separate tests below.
+ */
+ if (high > tlen)
+ high = tlen;
+
+ /* Fail if a match of required number of items is impossible */
+ if (high < low)
+ return false;
+
+ /*
+ * Recursively check the rest of the pattern against each possible
+ * start point following some of this item's match(es).
+ */
+ nextq = LQL_NEXT(curq);
+ qlen--;
+
+ for (int matchcnt = 0; matchcnt < high; matchcnt++)
+ {
+ /*
+ * If we've consumed an acceptable number of matches of this item,
+ * and the rest of the pattern matches beginning here, we're good.
+ */
+ if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen))
+ return true;
+
+ /*
+ * Otherwise, try to match one more text item to this query item.
+ */
+ if (!checkLevel(curq, curt))
+ return false;
+
+ curt = LEVEL_NEXT(curt);
+ tlen--;
+ }
+
+ /*
+ * Once we've consumed "high" matches, we can succeed only if the rest
+ * of the pattern matches beginning here. Loop around (if you prefer,
+ * think of this as tail recursion).
+ */
+ curq = nextq;
+ }
+
+ /*
+ * Once we're out of query items, we match only if there's no remaining
+ * text either.
+ */
+ return (tlen == 0);
+}
+
+Datum
+ltq_regex(PG_FUNCTION_ARGS)
+{
+ ltree *tree = PG_GETARG_LTREE_P(0);
+ lquery *query = PG_GETARG_LQUERY_P(1);
+ bool res;
+
+ res = checkCond(LQUERY_FIRST(query), query->numlevel,
+ LTREE_FIRST(tree), tree->numlevel);
+
+ 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)
+ ));
+}