| /*------------------------------------------------------------------------- |
| * |
| * tsquery_op.c |
| * Various operations with tsquery |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/utils/adt/tsquery_op.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "lib/qunique.h" |
| #include "tsearch/ts_utils.h" |
| #include "utils/builtins.h" |
| |
| Datum |
| tsquery_numnode(PG_FUNCTION_ARGS) |
| { |
| TSQuery query = PG_GETARG_TSQUERY(0); |
| int nnode = query->size; |
| |
| PG_FREE_IF_COPY(query, 0); |
| PG_RETURN_INT32(nnode); |
| } |
| |
| static QTNode * |
| join_tsqueries(TSQuery a, TSQuery b, int8 operator, uint16 distance) |
| { |
| QTNode *res = (QTNode *) palloc0(sizeof(QTNode)); |
| |
| res->flags |= QTN_NEEDFREE; |
| |
| res->valnode = (QueryItem *) palloc0(sizeof(QueryItem)); |
| res->valnode->type = QI_OPR; |
| res->valnode->qoperator.oper = operator; |
| if (operator == OP_PHRASE) |
| res->valnode->qoperator.distance = distance; |
| |
| res->child = (QTNode **) palloc0(sizeof(QTNode *) * 2); |
| res->child[0] = QT2QTN(GETQUERY(b), GETOPERAND(b)); |
| res->child[1] = QT2QTN(GETQUERY(a), GETOPERAND(a)); |
| res->nchild = 2; |
| |
| return res; |
| } |
| |
| Datum |
| tsquery_and(PG_FUNCTION_ARGS) |
| { |
| TSQuery a = PG_GETARG_TSQUERY_COPY(0); |
| TSQuery b = PG_GETARG_TSQUERY_COPY(1); |
| QTNode *res; |
| TSQuery query; |
| |
| if (a->size == 0) |
| { |
| PG_FREE_IF_COPY(a, 1); |
| PG_RETURN_POINTER(b); |
| } |
| else if (b->size == 0) |
| { |
| PG_FREE_IF_COPY(b, 1); |
| PG_RETURN_POINTER(a); |
| } |
| |
| res = join_tsqueries(a, b, OP_AND, 0); |
| |
| query = QTN2QT(res); |
| |
| QTNFree(res); |
| PG_FREE_IF_COPY(a, 0); |
| PG_FREE_IF_COPY(b, 1); |
| |
| PG_RETURN_TSQUERY(query); |
| } |
| |
| Datum |
| tsquery_or(PG_FUNCTION_ARGS) |
| { |
| TSQuery a = PG_GETARG_TSQUERY_COPY(0); |
| TSQuery b = PG_GETARG_TSQUERY_COPY(1); |
| QTNode *res; |
| TSQuery query; |
| |
| if (a->size == 0) |
| { |
| PG_FREE_IF_COPY(a, 1); |
| PG_RETURN_POINTER(b); |
| } |
| else if (b->size == 0) |
| { |
| PG_FREE_IF_COPY(b, 1); |
| PG_RETURN_POINTER(a); |
| } |
| |
| res = join_tsqueries(a, b, OP_OR, 0); |
| |
| query = QTN2QT(res); |
| |
| QTNFree(res); |
| PG_FREE_IF_COPY(a, 0); |
| PG_FREE_IF_COPY(b, 1); |
| |
| PG_RETURN_TSQUERY(query); |
| } |
| |
| Datum |
| tsquery_phrase_distance(PG_FUNCTION_ARGS) |
| { |
| TSQuery a = PG_GETARG_TSQUERY_COPY(0); |
| TSQuery b = PG_GETARG_TSQUERY_COPY(1); |
| QTNode *res; |
| TSQuery query; |
| int32 distance = PG_GETARG_INT32(2); |
| |
| if (distance < 0 || distance > MAXENTRYPOS) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("distance in phrase operator must be an integer value between zero and %d inclusive", |
| MAXENTRYPOS))); |
| if (a->size == 0) |
| { |
| PG_FREE_IF_COPY(a, 1); |
| PG_RETURN_POINTER(b); |
| } |
| else if (b->size == 0) |
| { |
| PG_FREE_IF_COPY(b, 1); |
| PG_RETURN_POINTER(a); |
| } |
| |
| res = join_tsqueries(a, b, OP_PHRASE, (uint16) distance); |
| |
| query = QTN2QT(res); |
| |
| QTNFree(res); |
| PG_FREE_IF_COPY(a, 0); |
| PG_FREE_IF_COPY(b, 1); |
| |
| PG_RETURN_TSQUERY(query); |
| } |
| |
| Datum |
| tsquery_phrase(PG_FUNCTION_ARGS) |
| { |
| PG_RETURN_POINTER(DirectFunctionCall3(tsquery_phrase_distance, |
| PG_GETARG_DATUM(0), |
| PG_GETARG_DATUM(1), |
| Int32GetDatum(1))); |
| } |
| |
| Datum |
| tsquery_not(PG_FUNCTION_ARGS) |
| { |
| TSQuery a = PG_GETARG_TSQUERY_COPY(0); |
| QTNode *res; |
| TSQuery query; |
| |
| if (a->size == 0) |
| PG_RETURN_POINTER(a); |
| |
| res = (QTNode *) palloc0(sizeof(QTNode)); |
| |
| res->flags |= QTN_NEEDFREE; |
| |
| res->valnode = (QueryItem *) palloc0(sizeof(QueryItem)); |
| res->valnode->type = QI_OPR; |
| res->valnode->qoperator.oper = OP_NOT; |
| |
| res->child = (QTNode **) palloc0(sizeof(QTNode *)); |
| res->child[0] = QT2QTN(GETQUERY(a), GETOPERAND(a)); |
| res->nchild = 1; |
| |
| query = QTN2QT(res); |
| |
| QTNFree(res); |
| PG_FREE_IF_COPY(a, 0); |
| |
| PG_RETURN_POINTER(query); |
| } |
| |
| static int |
| CompareTSQ(TSQuery a, TSQuery b) |
| { |
| if (a->size != b->size) |
| { |
| return (a->size < b->size) ? -1 : 1; |
| } |
| else if (VARSIZE(a) != VARSIZE(b)) |
| { |
| return (VARSIZE(a) < VARSIZE(b)) ? -1 : 1; |
| } |
| else if (a->size != 0) |
| { |
| QTNode *an = QT2QTN(GETQUERY(a), GETOPERAND(a)); |
| QTNode *bn = QT2QTN(GETQUERY(b), GETOPERAND(b)); |
| int res = QTNodeCompare(an, bn); |
| |
| QTNFree(an); |
| QTNFree(bn); |
| |
| return res; |
| } |
| |
| return 0; |
| } |
| |
| Datum |
| tsquery_cmp(PG_FUNCTION_ARGS) |
| { |
| TSQuery a = PG_GETARG_TSQUERY_COPY(0); |
| TSQuery b = PG_GETARG_TSQUERY_COPY(1); |
| int res = CompareTSQ(a, b); |
| |
| PG_FREE_IF_COPY(a, 0); |
| PG_FREE_IF_COPY(b, 1); |
| |
| PG_RETURN_INT32(res); |
| } |
| |
| #define CMPFUNC( NAME, CONDITION ) \ |
| Datum \ |
| NAME(PG_FUNCTION_ARGS) { \ |
| TSQuery a = PG_GETARG_TSQUERY_COPY(0); \ |
| TSQuery b = PG_GETARG_TSQUERY_COPY(1); \ |
| int res = CompareTSQ(a,b); \ |
| \ |
| PG_FREE_IF_COPY(a,0); \ |
| PG_FREE_IF_COPY(b,1); \ |
| \ |
| PG_RETURN_BOOL( CONDITION ); \ |
| } \ |
| /* keep compiler quiet - no extra ; */ \ |
| extern int no_such_variable |
| |
| CMPFUNC(tsquery_lt, res < 0); |
| CMPFUNC(tsquery_le, res <= 0); |
| CMPFUNC(tsquery_eq, res == 0); |
| CMPFUNC(tsquery_ge, res >= 0); |
| CMPFUNC(tsquery_gt, res > 0); |
| CMPFUNC(tsquery_ne, res != 0); |
| |
| TSQuerySign |
| makeTSQuerySign(TSQuery a) |
| { |
| int i; |
| QueryItem *ptr = GETQUERY(a); |
| TSQuerySign sign = 0; |
| |
| for (i = 0; i < a->size; i++) |
| { |
| if (ptr->type == QI_VAL) |
| sign |= ((TSQuerySign) 1) << (((unsigned int) ptr->qoperand.valcrc) % TSQS_SIGLEN); |
| ptr++; |
| } |
| |
| return sign; |
| } |
| |
| static char ** |
| collectTSQueryValues(TSQuery a, int *nvalues_p) |
| { |
| QueryItem *ptr = GETQUERY(a); |
| char *operand = GETOPERAND(a); |
| char **values; |
| int nvalues = 0; |
| int i; |
| |
| values = (char **) palloc(sizeof(char *) * a->size); |
| |
| for (i = 0; i < a->size; i++) |
| { |
| if (ptr->type == QI_VAL) |
| { |
| int len = ptr->qoperand.length; |
| char *val; |
| |
| val = palloc(len + 1); |
| memcpy(val, operand + ptr->qoperand.distance, len); |
| val[len] = '\0'; |
| |
| values[nvalues++] = val; |
| } |
| ptr++; |
| } |
| |
| *nvalues_p = nvalues; |
| return values; |
| } |
| |
| static int |
| cmp_string(const void *a, const void *b) |
| { |
| const char *sa = *((char *const *) a); |
| const char *sb = *((char *const *) b); |
| |
| return strcmp(sa, sb); |
| } |
| |
| Datum |
| tsq_mcontains(PG_FUNCTION_ARGS) |
| { |
| TSQuery query = PG_GETARG_TSQUERY(0); |
| TSQuery ex = PG_GETARG_TSQUERY(1); |
| char **query_values; |
| int query_nvalues; |
| char **ex_values; |
| int ex_nvalues; |
| bool result = true; |
| |
| /* Extract the query terms into arrays */ |
| query_values = collectTSQueryValues(query, &query_nvalues); |
| ex_values = collectTSQueryValues(ex, &ex_nvalues); |
| |
| /* Sort and remove duplicates from both arrays */ |
| qsort(query_values, query_nvalues, sizeof(char *), cmp_string); |
| query_nvalues = qunique(query_values, query_nvalues, sizeof(char *), |
| cmp_string); |
| qsort(ex_values, ex_nvalues, sizeof(char *), cmp_string); |
| ex_nvalues = qunique(ex_values, ex_nvalues, sizeof(char *), cmp_string); |
| |
| if (ex_nvalues > query_nvalues) |
| result = false; |
| else |
| { |
| int i; |
| int j = 0; |
| |
| for (i = 0; i < ex_nvalues; i++) |
| { |
| for (; j < query_nvalues; j++) |
| { |
| if (strcmp(ex_values[i], query_values[j]) == 0) |
| break; |
| } |
| if (j == query_nvalues) |
| { |
| result = false; |
| break; |
| } |
| } |
| } |
| |
| PG_RETURN_BOOL(result); |
| } |
| |
| Datum |
| tsq_mcontained(PG_FUNCTION_ARGS) |
| { |
| PG_RETURN_DATUM(DirectFunctionCall2(tsq_mcontains, |
| PG_GETARG_DATUM(1), |
| PG_GETARG_DATUM(0))); |
| } |