310 lines
8.4 KiB
C++
310 lines
8.4 KiB
C++
/*
|
|
* op function for ltree and lquery
|
|
* Teodor Sigaev <teodor@stack.net>
|
|
* contrib/ltree/lquery_op.c
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "catalog/pg_collation.h"
|
|
#include "utils/formatting.h"
|
|
#include "ltree.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);
|
|
|
|
extern "C" Datum ltq_regex(PG_FUNCTION_ARGS);
|
|
extern "C" Datum ltq_rregex(PG_FUNCTION_ARGS);
|
|
extern "C" Datum lt_q_regex(PG_FUNCTION_ARGS);
|
|
extern "C" Datum lt_q_rregex(PG_FUNCTION_ARGS);
|
|
|
|
#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 = NULL;
|
|
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 = NULL;
|
|
int lent, lenq;
|
|
bool isok = false;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static struct {
|
|
bool muse;
|
|
uint32 high_pos;
|
|
} SomeStack =
|
|
|
|
{
|
|
false,
|
|
0,
|
|
};
|
|
|
|
static bool checkCond(lquery_level* curq, int query_numlevel, ltree_level* curt, int tree_numlevel, FieldNot* ptr)
|
|
{
|
|
uint32 low_pos = 0, high_pos = 0, cur_tpos = 0;
|
|
int tlen = tree_numlevel, qlen = query_numlevel;
|
|
int isok;
|
|
lquery_level* prevq = NULL;
|
|
ltree_level* prevt = NULL;
|
|
|
|
if (SomeStack.muse) {
|
|
high_pos = SomeStack.high_pos;
|
|
qlen--;
|
|
prevq = curq;
|
|
curq = LQL_NEXT(curq);
|
|
SomeStack.muse = false;
|
|
}
|
|
|
|
while (tlen > 0 && qlen > 0) {
|
|
if (curq->numvar) {
|
|
prevt = curt;
|
|
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) {
|
|
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 {
|
|
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));
|
|
SomeStack.high_pos = high_pos - cur_tpos;
|
|
SomeStack.muse = true;
|
|
if (checkCond(prevq, qlen + 1, curt, tlen, (ptr) ? &tmpptr : NULL))
|
|
return true;
|
|
}
|
|
if (!isok && ptr)
|
|
ptr->nt++;
|
|
}
|
|
if (!isok)
|
|
return false;
|
|
|
|
if (ptr && ptr->q) {
|
|
if (checkCond(ptr->q, ptr->nq, ptr->t, ptr->nt, NULL))
|
|
return false;
|
|
ptr->q = NULL;
|
|
}
|
|
low_pos = cur_tpos;
|
|
high_pos = cur_tpos;
|
|
}
|
|
} else {
|
|
low_pos = cur_tpos + curq->low;
|
|
high_pos = cur_tpos + curq->high;
|
|
if (ptr && ptr->q) {
|
|
ptr->nq++;
|
|
if (qlen == 1)
|
|
ptr->nt = tree_numlevel - ptr->post;
|
|
}
|
|
}
|
|
|
|
prevq = curq;
|
|
curq = LQL_NEXT(curq);
|
|
qlen--;
|
|
}
|
|
|
|
if (low_pos > tree_numlevel || tree_numlevel > high_pos)
|
|
return false;
|
|
|
|
while (qlen > 0) {
|
|
if (curq->numvar) {
|
|
if (!(curq->flag & LQL_NOT))
|
|
return false;
|
|
} else {
|
|
low_pos = cur_tpos + curq->low;
|
|
high_pos = cur_tpos + curq->high;
|
|
}
|
|
|
|
curq = LQL_NEXT(curq);
|
|
qlen--;
|
|
}
|
|
|
|
if (low_pos > tree_numlevel || tree_numlevel > high_pos)
|
|
return false;
|
|
|
|
if (ptr && ptr->q && checkCond(ptr->q, ptr->nq, ptr->t, ptr->nt, NULL))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
Datum ltq_regex(PG_FUNCTION_ARGS)
|
|
{
|
|
ltree* tree = PG_GETARG_LTREE(0);
|
|
lquery* query = PG_GETARG_LQUERY(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);
|
|
} else {
|
|
res = checkCond(LQUERY_FIRST(query), query->numlevel, LTREE_FIRST(tree), tree->numlevel, NULL);
|
|
}
|
|
|
|
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(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)));
|
|
}
|