Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * tsquery.c
4 : * I/O functions for tsquery
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/utils/adt/tsquery.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "libpq/pqformat.h"
18 : #include "miscadmin.h"
19 : #include "nodes/miscnodes.h"
20 : #include "tsearch/ts_locale.h"
21 : #include "tsearch/ts_type.h"
22 : #include "tsearch/ts_utils.h"
23 : #include "utils/builtins.h"
24 : #include "utils/memutils.h"
25 : #include "utils/pg_crc.h"
26 : #include "varatt.h"
27 :
28 : /* FTS operator priorities, see ts_type.h */
29 : const int tsearch_op_priority[OP_COUNT] =
30 : {
31 : 4, /* OP_NOT */
32 : 2, /* OP_AND */
33 : 1, /* OP_OR */
34 : 3 /* OP_PHRASE */
35 : };
36 :
37 : /*
38 : * parser's states
39 : */
40 : typedef enum
41 : {
42 : WAITOPERAND = 1,
43 : WAITOPERATOR = 2,
44 : WAITFIRSTOPERAND = 3,
45 : } ts_parserstate;
46 :
47 : /*
48 : * token types for parsing
49 : */
50 : typedef enum
51 : {
52 : PT_END = 0,
53 : PT_ERR = 1,
54 : PT_VAL = 2,
55 : PT_OPR = 3,
56 : PT_OPEN = 4,
57 : PT_CLOSE = 5,
58 : } ts_tokentype;
59 :
60 : /*
61 : * get token from query string
62 : *
63 : * All arguments except "state" are output arguments.
64 : *
65 : * If return value is PT_OPR, then *operator is filled with an OP_* code
66 : * and *weight will contain a distance value in case of phrase operator.
67 : *
68 : * If return value is PT_VAL, then *lenval, *strval, *weight, and *prefix
69 : * are filled.
70 : *
71 : * If PT_ERR is returned then a soft error has occurred. If state->escontext
72 : * isn't already filled then this should be reported as a generic parse error.
73 : */
74 : typedef ts_tokentype (*ts_tokenizer) (TSQueryParserState state, int8 *operator,
75 : int *lenval, char **strval,
76 : int16 *weight, bool *prefix);
77 :
78 : struct TSQueryParserStateData
79 : {
80 : /* Tokenizer used for parsing tsquery */
81 : ts_tokenizer gettoken;
82 :
83 : /* State of tokenizer function */
84 : char *buffer; /* entire string we are scanning */
85 : char *buf; /* current scan point */
86 : int count; /* nesting count, incremented by (,
87 : * decremented by ) */
88 : ts_parserstate state;
89 :
90 : /* polish (prefix) notation in list, filled in by push* functions */
91 : List *polstr;
92 :
93 : /*
94 : * Strings from operands are collected in op. curop is a pointer to the
95 : * end of used space of op.
96 : */
97 : char *op;
98 : char *curop;
99 : int lenop; /* allocated size of op */
100 : int sumlen; /* used size of op */
101 :
102 : /* state for value's parser */
103 : TSVectorParseState valstate;
104 :
105 : /* context object for soft errors - must match valstate's escontext */
106 : Node *escontext;
107 : };
108 :
109 : /*
110 : * subroutine to parse the modifiers (weight and prefix flag currently)
111 : * part, like ':AB*' of a query.
112 : */
113 : static char *
114 3603 : get_modifiers(char *buf, int16 *weight, bool *prefix)
115 : {
116 3603 : *weight = 0;
117 3603 : *prefix = false;
118 :
119 3603 : if (!t_iseq(buf, ':'))
120 3285 : return buf;
121 :
122 318 : buf++;
123 744 : while (*buf && pg_mblen_cstr(buf) == 1)
124 : {
125 534 : switch (*buf)
126 : {
127 117 : case 'a':
128 : case 'A':
129 117 : *weight |= 1 << 3;
130 117 : break;
131 33 : case 'b':
132 : case 'B':
133 33 : *weight |= 1 << 2;
134 33 : break;
135 57 : case 'c':
136 : case 'C':
137 57 : *weight |= 1 << 1;
138 57 : break;
139 60 : case 'd':
140 : case 'D':
141 60 : *weight |= 1;
142 60 : break;
143 159 : case '*':
144 159 : *prefix = true;
145 159 : break;
146 108 : default:
147 108 : return buf;
148 : }
149 426 : buf++;
150 : }
151 :
152 210 : return buf;
153 : }
154 :
155 : /*
156 : * Parse phrase operator. The operator
157 : * may take the following forms:
158 : *
159 : * a <N> b (distance is exactly N lexemes)
160 : * a <-> b (default distance = 1)
161 : *
162 : * The buffer should begin with '<' char
163 : */
164 : static bool
165 4539 : parse_phrase_operator(TSQueryParserState pstate, int16 *distance)
166 : {
167 : enum
168 : {
169 : PHRASE_OPEN = 0,
170 : PHRASE_DIST,
171 : PHRASE_CLOSE,
172 : PHRASE_FINISH
173 4539 : } state = PHRASE_OPEN;
174 4539 : char *ptr = pstate->buf;
175 : char *endptr;
176 4539 : long l = 1; /* default distance */
177 :
178 11682 : while (*ptr)
179 : {
180 5492 : switch (state)
181 : {
182 2888 : case PHRASE_OPEN:
183 2888 : if (t_iseq(ptr, '<'))
184 : {
185 870 : state = PHRASE_DIST;
186 870 : ptr++;
187 : }
188 : else
189 2018 : return false;
190 870 : break;
191 :
192 870 : case PHRASE_DIST:
193 870 : if (t_iseq(ptr, '-'))
194 : {
195 723 : state = PHRASE_CLOSE;
196 723 : ptr++;
197 723 : continue;
198 : }
199 :
200 147 : if (!isdigit((unsigned char) *ptr))
201 0 : return false;
202 :
203 147 : errno = 0;
204 147 : l = strtol(ptr, &endptr, 10);
205 147 : if (ptr == endptr)
206 0 : return false;
207 147 : else if (errno == ERANGE || l < 0 || l > MAXENTRYPOS)
208 3 : ereturn(pstate->escontext, false,
209 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
210 : errmsg("distance in phrase operator must be an integer value between zero and %d inclusive",
211 : MAXENTRYPOS)));
212 : else
213 : {
214 144 : state = PHRASE_CLOSE;
215 144 : ptr = endptr;
216 : }
217 144 : break;
218 :
219 867 : case PHRASE_CLOSE:
220 867 : if (t_iseq(ptr, '>'))
221 : {
222 867 : state = PHRASE_FINISH;
223 867 : ptr++;
224 : }
225 : else
226 0 : return false;
227 867 : break;
228 :
229 867 : case PHRASE_FINISH:
230 867 : *distance = (int16) l;
231 867 : pstate->buf = ptr;
232 867 : return true;
233 : }
234 : }
235 :
236 1651 : return false;
237 : }
238 :
239 : /*
240 : * Parse OR operator used in websearch_to_tsquery(), returns true if we
241 : * believe that "OR" literal could be an operator OR
242 : */
243 : static bool
244 699 : parse_or_operator(TSQueryParserState pstate)
245 : {
246 699 : char *ptr = pstate->buf;
247 :
248 : /* it should begin with "OR" literal */
249 699 : if (pg_strncasecmp(ptr, "or", 2) != 0)
250 624 : return false;
251 :
252 75 : ptr += 2;
253 :
254 : /*
255 : * it shouldn't be a part of any word but somewhere later it should be
256 : * some operand
257 : */
258 75 : if (*ptr == '\0') /* no operand */
259 3 : return false;
260 :
261 : /* it shouldn't be a part of any word */
262 72 : if (t_iseq(ptr, '-') || t_iseq(ptr, '_') || t_isalnum_cstr(ptr))
263 12 : return false;
264 :
265 : for (;;)
266 : {
267 60 : ptr += pg_mblen_cstr(ptr);
268 :
269 60 : if (*ptr == '\0') /* got end of string without operand */
270 6 : return false;
271 :
272 : /*
273 : * Suppose, we found an operand, but could be a not correct operand.
274 : * So we still treat OR literal as operation with possibly incorrect
275 : * operand and will not search it as lexeme
276 : */
277 54 : if (!isspace((unsigned char) *ptr))
278 54 : break;
279 : }
280 :
281 54 : pstate->buf += 2;
282 54 : return true;
283 : }
284 :
285 : static ts_tokentype
286 8745 : gettoken_query_standard(TSQueryParserState state, int8 *operator,
287 : int *lenval, char **strval,
288 : int16 *weight, bool *prefix)
289 : {
290 8745 : *weight = 0;
291 8745 : *prefix = false;
292 :
293 : while (true)
294 : {
295 11692 : switch (state->state)
296 : {
297 6077 : case WAITFIRSTOPERAND:
298 : case WAITOPERAND:
299 6077 : if (t_iseq(state->buf, '!'))
300 : {
301 465 : state->buf++;
302 465 : state->state = WAITOPERAND;
303 465 : *operator = OP_NOT;
304 465 : return PT_OPR;
305 : }
306 5612 : else if (t_iseq(state->buf, '('))
307 : {
308 531 : state->buf++;
309 531 : state->state = WAITOPERAND;
310 531 : state->count++;
311 531 : return PT_OPEN;
312 : }
313 5081 : else if (t_iseq(state->buf, ':'))
314 : {
315 : /* generic syntax error message is fine */
316 0 : return PT_ERR;
317 : }
318 5081 : else if (!isspace((unsigned char) *state->buf))
319 : {
320 : /*
321 : * We rely on the tsvector parser to parse the value for
322 : * us
323 : */
324 3615 : reset_tsvector_parser(state->valstate, state->buf);
325 3615 : if (gettoken_tsvector(state->valstate, strval, lenval,
326 : NULL, NULL, &state->buf))
327 : {
328 3603 : state->buf = get_modifiers(state->buf, weight, prefix);
329 3603 : state->state = WAITOPERATOR;
330 3603 : return PT_VAL;
331 : }
332 12 : else if (SOFT_ERROR_OCCURRED(state->escontext))
333 : {
334 : /* gettoken_tsvector reported a soft error */
335 0 : return PT_ERR;
336 : }
337 12 : else if (state->state == WAITFIRSTOPERAND)
338 : {
339 12 : return PT_END;
340 : }
341 : else
342 0 : ereturn(state->escontext, PT_ERR,
343 : (errcode(ERRCODE_SYNTAX_ERROR),
344 : errmsg("no operand in tsquery: \"%s\"",
345 : state->buffer)));
346 : }
347 1466 : break;
348 :
349 5615 : case WAITOPERATOR:
350 5615 : if (t_iseq(state->buf, '&'))
351 : {
352 665 : state->buf++;
353 665 : state->state = WAITOPERAND;
354 665 : *operator = OP_AND;
355 665 : return PT_OPR;
356 : }
357 4950 : else if (t_iseq(state->buf, '|'))
358 : {
359 411 : state->buf++;
360 411 : state->state = WAITOPERAND;
361 411 : *operator = OP_OR;
362 411 : return PT_OPR;
363 : }
364 4539 : else if (parse_phrase_operator(state, weight))
365 : {
366 : /* weight var is used as storage for distance */
367 867 : state->state = WAITOPERAND;
368 867 : *operator = OP_PHRASE;
369 867 : return PT_OPR;
370 : }
371 3672 : else if (SOFT_ERROR_OCCURRED(state->escontext))
372 : {
373 : /* parse_phrase_operator reported a soft error */
374 3 : return PT_ERR;
375 : }
376 3669 : else if (t_iseq(state->buf, ')'))
377 : {
378 531 : state->buf++;
379 531 : state->count--;
380 531 : return (state->count < 0) ? PT_ERR : PT_CLOSE;
381 : }
382 3138 : else if (*state->buf == '\0')
383 : {
384 1651 : return (state->count) ? PT_ERR : PT_END;
385 : }
386 1487 : else if (!isspace((unsigned char) *state->buf))
387 : {
388 6 : return PT_ERR;
389 : }
390 1481 : break;
391 : }
392 :
393 2947 : state->buf += pg_mblen_cstr(state->buf);
394 : }
395 : }
396 :
397 : static ts_tokentype
398 1131 : gettoken_query_websearch(TSQueryParserState state, int8 *operator,
399 : int *lenval, char **strval,
400 : int16 *weight, bool *prefix)
401 : {
402 1131 : *weight = 0;
403 1131 : *prefix = false;
404 :
405 : while (true)
406 : {
407 1578 : switch (state->state)
408 : {
409 672 : case WAITFIRSTOPERAND:
410 : case WAITOPERAND:
411 672 : if (t_iseq(state->buf, '-'))
412 : {
413 33 : state->buf++;
414 33 : state->state = WAITOPERAND;
415 :
416 33 : *operator = OP_NOT;
417 33 : return PT_OPR;
418 : }
419 639 : else if (t_iseq(state->buf, '"'))
420 : {
421 : /* Everything in quotes is processed as a single token */
422 :
423 : /* skip opening quote */
424 96 : state->buf++;
425 96 : *strval = state->buf;
426 :
427 : /* iterate to the closing quote or end of the string */
428 870 : while (*state->buf != '\0' && !t_iseq(state->buf, '"'))
429 774 : state->buf++;
430 96 : *lenval = state->buf - *strval;
431 :
432 : /* skip closing quote if not end of the string */
433 96 : if (*state->buf != '\0')
434 84 : state->buf++;
435 :
436 96 : state->state = WAITOPERATOR;
437 96 : state->count++;
438 96 : return PT_VAL;
439 : }
440 543 : else if (ISOPERATOR(state->buf))
441 : {
442 : /* ignore, else gettoken_tsvector() will raise an error */
443 51 : state->buf++;
444 51 : state->state = WAITOPERAND;
445 51 : continue;
446 : }
447 492 : else if (!isspace((unsigned char) *state->buf))
448 : {
449 : /*
450 : * We rely on the tsvector parser to parse the value for
451 : * us
452 : */
453 453 : reset_tsvector_parser(state->valstate, state->buf);
454 453 : if (gettoken_tsvector(state->valstate, strval, lenval,
455 : NULL, NULL, &state->buf))
456 : {
457 453 : state->state = WAITOPERATOR;
458 453 : return PT_VAL;
459 : }
460 0 : else if (SOFT_ERROR_OCCURRED(state->escontext))
461 : {
462 : /* gettoken_tsvector reported a soft error */
463 0 : return PT_ERR;
464 : }
465 0 : else if (state->state == WAITFIRSTOPERAND)
466 : {
467 0 : return PT_END;
468 : }
469 : else
470 : {
471 : /* finally, we have to provide an operand */
472 0 : pushStop(state);
473 0 : return PT_END;
474 : }
475 : }
476 39 : break;
477 :
478 906 : case WAITOPERATOR:
479 906 : if (*state->buf == '\0')
480 : {
481 207 : return PT_END;
482 : }
483 699 : else if (parse_or_operator(state))
484 : {
485 54 : state->state = WAITOPERAND;
486 54 : *operator = OP_OR;
487 54 : return PT_OPR;
488 : }
489 645 : else if (ISOPERATOR(state->buf))
490 : {
491 : /* ignore other operators in this state too */
492 57 : state->buf++;
493 57 : continue;
494 : }
495 588 : else if (!isspace((unsigned char) *state->buf))
496 : {
497 : /* insert implicit AND between operands */
498 288 : state->state = WAITOPERAND;
499 288 : *operator = OP_AND;
500 288 : return PT_OPR;
501 : }
502 300 : break;
503 : }
504 :
505 339 : state->buf += pg_mblen_cstr(state->buf);
506 : }
507 : }
508 :
509 : static ts_tokentype
510 108 : gettoken_query_plain(TSQueryParserState state, int8 *operator,
511 : int *lenval, char **strval,
512 : int16 *weight, bool *prefix)
513 : {
514 108 : *weight = 0;
515 108 : *prefix = false;
516 :
517 108 : if (*state->buf == '\0')
518 54 : return PT_END;
519 :
520 54 : *strval = state->buf;
521 54 : *lenval = strlen(state->buf);
522 54 : state->buf += *lenval;
523 54 : state->count++;
524 54 : return PT_VAL;
525 : }
526 :
527 : /*
528 : * Push an operator to state->polstr
529 : */
530 : void
531 3119 : pushOperator(TSQueryParserState state, int8 oper, int16 distance)
532 : {
533 : QueryOperator *tmp;
534 :
535 : Assert(oper == OP_NOT || oper == OP_AND || oper == OP_OR || oper == OP_PHRASE);
536 :
537 3119 : tmp = palloc0_object(QueryOperator);
538 3119 : tmp->type = QI_OPR;
539 3119 : tmp->oper = oper;
540 3119 : tmp->distance = (oper == OP_PHRASE) ? distance : 0;
541 : /* left is filled in later with findoprnd */
542 :
543 3119 : state->polstr = lcons(tmp, state->polstr);
544 3119 : }
545 :
546 : static void
547 4209 : pushValue_internal(TSQueryParserState state, pg_crc32 valcrc, int distance, int lenval, int weight, bool prefix)
548 : {
549 : QueryOperand *tmp;
550 :
551 4209 : if (distance >= MAXSTRPOS)
552 0 : ereturn(state->escontext,,
553 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
554 : errmsg("value is too big in tsquery: \"%s\"",
555 : state->buffer)));
556 4209 : if (lenval >= MAXSTRLEN)
557 0 : ereturn(state->escontext,,
558 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
559 : errmsg("operand is too long in tsquery: \"%s\"",
560 : state->buffer)));
561 :
562 4209 : tmp = palloc0_object(QueryOperand);
563 4209 : tmp->type = QI_VAL;
564 4209 : tmp->weight = weight;
565 4209 : tmp->prefix = prefix;
566 4209 : tmp->valcrc = (int32) valcrc;
567 4209 : tmp->length = lenval;
568 4209 : tmp->distance = distance;
569 :
570 4209 : state->polstr = lcons(tmp, state->polstr);
571 : }
572 :
573 : /*
574 : * Push an operand to state->polstr.
575 : *
576 : * strval must point to a string equal to state->curop. lenval is the length
577 : * of the string.
578 : */
579 : void
580 4209 : pushValue(TSQueryParserState state, char *strval, int lenval, int16 weight, bool prefix)
581 : {
582 : pg_crc32 valcrc;
583 :
584 4209 : if (lenval >= MAXSTRLEN)
585 0 : ereturn(state->escontext,,
586 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
587 : errmsg("word is too long in tsquery: \"%s\"",
588 : state->buffer)));
589 :
590 4209 : INIT_LEGACY_CRC32(valcrc);
591 14856 : COMP_LEGACY_CRC32(valcrc, strval, lenval);
592 4209 : FIN_LEGACY_CRC32(valcrc);
593 4209 : pushValue_internal(state, valcrc, state->curop - state->op, lenval, weight, prefix);
594 :
595 : /* append the value string to state.op, enlarging buffer if needed first */
596 4209 : while (state->curop - state->op + lenval + 1 >= state->lenop)
597 : {
598 0 : int used = state->curop - state->op;
599 :
600 0 : state->lenop *= 2;
601 0 : state->op = (char *) repalloc(state->op, state->lenop);
602 0 : state->curop = state->op + used;
603 : }
604 4209 : memcpy(state->curop, strval, lenval);
605 4209 : state->curop += lenval;
606 4209 : *(state->curop) = '\0';
607 4209 : state->curop++;
608 4209 : state->sumlen += lenval + 1 /* \0 */ ;
609 : }
610 :
611 :
612 : /*
613 : * Push a stopword placeholder to state->polstr
614 : */
615 : void
616 333 : pushStop(TSQueryParserState state)
617 : {
618 : QueryOperand *tmp;
619 :
620 333 : tmp = palloc0_object(QueryOperand);
621 333 : tmp->type = QI_VALSTOP;
622 :
623 333 : state->polstr = lcons(tmp, state->polstr);
624 333 : }
625 :
626 :
627 : #define STACKDEPTH 32
628 :
629 : typedef struct OperatorElement
630 : {
631 : int8 op;
632 : int16 distance;
633 : } OperatorElement;
634 :
635 : static void
636 2783 : pushOpStack(OperatorElement *stack, int *lenstack, int8 op, int16 distance)
637 : {
638 2783 : if (*lenstack == STACKDEPTH) /* internal error */
639 0 : elog(ERROR, "tsquery stack too small");
640 :
641 2783 : stack[*lenstack].op = op;
642 2783 : stack[*lenstack].distance = distance;
643 :
644 2783 : (*lenstack)++;
645 2783 : }
646 :
647 : static void
648 5238 : cleanOpStack(TSQueryParserState state,
649 : OperatorElement *stack, int *lenstack, int8 op)
650 : {
651 5238 : int opPriority = OP_PRIORITY(op);
652 :
653 8021 : while (*lenstack)
654 : {
655 : /* NOT is right associative unlike to others */
656 3026 : if ((op != OP_NOT && opPriority > OP_PRIORITY(stack[*lenstack - 1].op)) ||
657 159 : (op == OP_NOT && opPriority >= OP_PRIORITY(stack[*lenstack - 1].op)))
658 : break;
659 :
660 2783 : (*lenstack)--;
661 2783 : pushOperator(state, stack[*lenstack].op,
662 2783 : stack[*lenstack].distance);
663 : }
664 5238 : }
665 :
666 : /*
667 : * Make polish (prefix) notation of query.
668 : *
669 : * See parse_tsquery for explanation of pushval.
670 : */
671 : static void
672 2464 : makepol(TSQueryParserState state,
673 : PushFunction pushval,
674 : void *opaque)
675 : {
676 2464 : int8 operator = 0;
677 : ts_tokentype type;
678 2464 : int lenval = 0;
679 2464 : char *strval = NULL;
680 : OperatorElement opstack[STACKDEPTH];
681 2464 : int lenstack = 0;
682 2464 : int16 weight = 0;
683 : bool prefix;
684 :
685 : /* since this function recurses, it could be driven to stack overflow */
686 2464 : check_stack_depth();
687 :
688 9984 : while ((type = state->gettoken(state, &operator,
689 : &lenval, &strval,
690 9984 : &weight, &prefix)) != PT_END)
691 : {
692 8060 : switch (type)
693 : {
694 4206 : case PT_VAL:
695 4206 : pushval(opaque, state, strval, lenval, weight, prefix);
696 4206 : break;
697 2783 : case PT_OPR:
698 2783 : cleanOpStack(state, opstack, &lenstack, operator);
699 2783 : pushOpStack(opstack, &lenstack, operator, weight);
700 2783 : break;
701 531 : case PT_OPEN:
702 531 : makepol(state, pushval, opaque);
703 531 : break;
704 531 : case PT_CLOSE:
705 531 : cleanOpStack(state, opstack, &lenstack, OP_OR /* lowest */ );
706 540 : return;
707 9 : case PT_ERR:
708 : default:
709 : /* don't overwrite a soft error saved by gettoken function */
710 9 : if (!SOFT_ERROR_OCCURRED(state->escontext))
711 6 : errsave(state->escontext,
712 : (errcode(ERRCODE_SYNTAX_ERROR),
713 : errmsg("syntax error in tsquery: \"%s\"",
714 : state->buffer)));
715 9 : return;
716 : }
717 : /* detect soft error in pushval or recursion */
718 7520 : if (SOFT_ERROR_OCCURRED(state->escontext))
719 0 : return;
720 : }
721 :
722 1924 : cleanOpStack(state, opstack, &lenstack, OP_OR /* lowest */ );
723 : }
724 :
725 : static void
726 7652 : findoprnd_recurse(QueryItem *ptr, uint32 *pos, int nnodes, bool *needcleanup)
727 : {
728 : /* since this function recurses, it could be driven to stack overflow. */
729 7652 : check_stack_depth();
730 :
731 7652 : if (*pos >= nnodes)
732 0 : elog(ERROR, "malformed tsquery: operand not found");
733 :
734 7652 : if (ptr[*pos].type == QI_VAL)
735 : {
736 4200 : (*pos)++;
737 : }
738 3452 : else if (ptr[*pos].type == QI_VALSTOP)
739 : {
740 333 : *needcleanup = true; /* we'll have to remove stop words */
741 333 : (*pos)++;
742 : }
743 : else
744 : {
745 : Assert(ptr[*pos].type == QI_OPR);
746 :
747 3119 : if (ptr[*pos].qoperator.oper == OP_NOT)
748 : {
749 498 : ptr[*pos].qoperator.left = 1; /* fixed offset */
750 498 : (*pos)++;
751 :
752 : /* process the only argument */
753 498 : findoprnd_recurse(ptr, pos, nnodes, needcleanup);
754 : }
755 : else
756 : {
757 2621 : QueryOperator *curitem = &ptr[*pos].qoperator;
758 2621 : int tmp = *pos; /* save current position */
759 :
760 : Assert(curitem->oper == OP_AND ||
761 : curitem->oper == OP_OR ||
762 : curitem->oper == OP_PHRASE);
763 :
764 2621 : (*pos)++;
765 :
766 : /* process RIGHT argument */
767 2621 : findoprnd_recurse(ptr, pos, nnodes, needcleanup);
768 :
769 2621 : curitem->left = *pos - tmp; /* set LEFT arg's offset */
770 :
771 : /* process LEFT argument */
772 2621 : findoprnd_recurse(ptr, pos, nnodes, needcleanup);
773 : }
774 : }
775 7652 : }
776 :
777 :
778 : /*
779 : * Fill in the left-fields previously left unfilled.
780 : * The input QueryItems must be in polish (prefix) notation.
781 : * Also, set *needcleanup to true if there are any QI_VALSTOP nodes.
782 : */
783 : static void
784 1912 : findoprnd(QueryItem *ptr, int size, bool *needcleanup)
785 : {
786 : uint32 pos;
787 :
788 1912 : *needcleanup = false;
789 1912 : pos = 0;
790 1912 : findoprnd_recurse(ptr, &pos, size, needcleanup);
791 :
792 1912 : if (pos != size)
793 0 : elog(ERROR, "malformed tsquery: extra nodes");
794 1912 : }
795 :
796 :
797 : /*
798 : * Parse the tsquery stored in "buf".
799 : *
800 : * Each value (operand) in the query is passed to pushval. pushval can
801 : * transform the simple value to an arbitrarily complex expression using
802 : * pushValue and pushOperator. It must push a single value with pushValue,
803 : * a complete expression with all operands, or a stopword placeholder
804 : * with pushStop, otherwise the prefix notation representation will be broken,
805 : * having an operator with no operand.
806 : *
807 : * opaque is passed on to pushval as is, pushval can use it to store its
808 : * private state.
809 : *
810 : * The pushval function can record soft errors via escontext.
811 : * Callers must check SOFT_ERROR_OCCURRED to detect that.
812 : *
813 : * A bitmask of flags (see ts_utils.h) and an error context object
814 : * can be provided as well. If a soft error occurs, NULL is returned.
815 : */
816 : TSQuery
817 1933 : parse_tsquery(char *buf,
818 : PushFunction pushval,
819 : void *opaque,
820 : int flags,
821 : Node *escontext)
822 : {
823 : struct TSQueryParserStateData state;
824 : int i;
825 : TSQuery query;
826 : int commonlen;
827 : QueryItem *ptr;
828 : ListCell *cell;
829 : bool noisy;
830 : bool needcleanup;
831 1933 : int tsv_flags = P_TSV_OPR_IS_DELIM | P_TSV_IS_TSQUERY;
832 :
833 : /* plain should not be used with web */
834 : Assert((flags & (P_TSQ_PLAIN | P_TSQ_WEB)) != (P_TSQ_PLAIN | P_TSQ_WEB));
835 :
836 : /* select suitable tokenizer */
837 1933 : if (flags & P_TSQ_PLAIN)
838 54 : state.gettoken = gettoken_query_plain;
839 1879 : else if (flags & P_TSQ_WEB)
840 : {
841 207 : state.gettoken = gettoken_query_websearch;
842 207 : tsv_flags |= P_TSV_IS_WEB;
843 : }
844 : else
845 1672 : state.gettoken = gettoken_query_standard;
846 :
847 : /* emit nuisance NOTICEs only if not doing soft errors */
848 1933 : noisy = !(escontext && IsA(escontext, ErrorSaveContext));
849 :
850 : /* init state */
851 1933 : state.buffer = buf;
852 1933 : state.buf = buf;
853 1933 : state.count = 0;
854 1933 : state.state = WAITFIRSTOPERAND;
855 1933 : state.polstr = NIL;
856 1933 : state.escontext = escontext;
857 :
858 : /* init value parser's state */
859 1933 : state.valstate = init_tsvector_parser(state.buffer, tsv_flags, escontext);
860 :
861 : /* init list of operand */
862 1933 : state.sumlen = 0;
863 1933 : state.lenop = 64;
864 1933 : state.curop = state.op = (char *) palloc(state.lenop);
865 1933 : *(state.curop) = '\0';
866 :
867 : /* parse query & make polish notation (postfix, but in reverse order) */
868 1933 : makepol(&state, pushval, opaque);
869 :
870 1933 : close_tsvector_parser(state.valstate);
871 :
872 1933 : if (SOFT_ERROR_OCCURRED(escontext))
873 9 : return NULL;
874 :
875 1924 : if (state.polstr == NIL)
876 : {
877 12 : if (noisy)
878 12 : ereport(NOTICE,
879 : (errmsg("text-search query doesn't contain lexemes: \"%s\"",
880 : state.buffer)));
881 12 : query = (TSQuery) palloc(HDRSIZETQ);
882 12 : SET_VARSIZE(query, HDRSIZETQ);
883 12 : query->size = 0;
884 12 : return query;
885 : }
886 :
887 1912 : if (TSQUERY_TOO_BIG(list_length(state.polstr), state.sumlen))
888 0 : ereturn(escontext, NULL,
889 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
890 : errmsg("tsquery is too large")));
891 1912 : commonlen = COMPUTESIZE(list_length(state.polstr), state.sumlen);
892 :
893 : /* Pack the QueryItems in the final TSQuery struct to return to caller */
894 1912 : query = (TSQuery) palloc0(commonlen);
895 1912 : SET_VARSIZE(query, commonlen);
896 1912 : query->size = list_length(state.polstr);
897 1912 : ptr = GETQUERY(query);
898 :
899 : /* Copy QueryItems to TSQuery */
900 1912 : i = 0;
901 9564 : foreach(cell, state.polstr)
902 : {
903 7652 : QueryItem *item = (QueryItem *) lfirst(cell);
904 :
905 7652 : switch (item->type)
906 : {
907 4200 : case QI_VAL:
908 4200 : memcpy(&ptr[i], item, sizeof(QueryOperand));
909 4200 : break;
910 333 : case QI_VALSTOP:
911 333 : ptr[i].type = QI_VALSTOP;
912 333 : break;
913 3119 : case QI_OPR:
914 3119 : memcpy(&ptr[i], item, sizeof(QueryOperator));
915 3119 : break;
916 0 : default:
917 0 : elog(ERROR, "unrecognized QueryItem type: %d", item->type);
918 : }
919 7652 : i++;
920 : }
921 :
922 : /* Copy all the operand strings to TSQuery */
923 1912 : memcpy(GETOPERAND(query), state.op, state.sumlen);
924 1912 : pfree(state.op);
925 :
926 : /*
927 : * Set left operand pointers for every operator. While we're at it,
928 : * detect whether there are any QI_VALSTOP nodes.
929 : */
930 1912 : findoprnd(ptr, query->size, &needcleanup);
931 :
932 : /*
933 : * If there are QI_VALSTOP nodes, delete them and simplify the tree.
934 : */
935 1912 : if (needcleanup)
936 216 : query = cleanup_tsquery_stopwords(query, noisy);
937 :
938 1912 : return query;
939 : }
940 :
941 : static void
942 2650 : pushval_asis(void *opaque, TSQueryParserState state, char *strval, int lenval,
943 : int16 weight, bool prefix)
944 : {
945 2650 : pushValue(state, strval, lenval, weight, prefix);
946 2650 : }
947 :
948 : /*
949 : * in without morphology
950 : */
951 : Datum
952 1295 : tsqueryin(PG_FUNCTION_ARGS)
953 : {
954 1295 : char *in = PG_GETARG_CSTRING(0);
955 1295 : Node *escontext = fcinfo->context;
956 :
957 1295 : PG_RETURN_TSQUERY(parse_tsquery(in,
958 : pushval_asis,
959 : NULL,
960 : 0,
961 : escontext));
962 : }
963 :
964 : /*
965 : * out function
966 : */
967 : typedef struct
968 : {
969 : QueryItem *curpol;
970 : char *buf;
971 : char *cur;
972 : char *op;
973 : int buflen;
974 : } INFIX;
975 :
976 : /* Makes sure inf->buf is large enough for adding 'addsize' bytes */
977 : #define RESIZEBUF(inf, addsize) \
978 : while( ( (inf)->cur - (inf)->buf ) + (addsize) + 1 >= (inf)->buflen ) \
979 : { \
980 : int len = (inf)->cur - (inf)->buf; \
981 : (inf)->buflen *= 2; \
982 : (inf)->buf = (char*) repalloc( (void*)(inf)->buf, (inf)->buflen ); \
983 : (inf)->cur = (inf)->buf + len; \
984 : }
985 :
986 : /*
987 : * recursively traverse the tree and
988 : * print it in infix (human-readable) form
989 : */
990 : static void
991 3644 : infix(INFIX *in, int parentPriority, bool rightPhraseOp)
992 : {
993 : /* since this function recurses, it could be driven to stack overflow. */
994 3644 : check_stack_depth();
995 :
996 3644 : if (in->curpol->type == QI_VAL)
997 : {
998 2108 : QueryOperand *curpol = &in->curpol->qoperand;
999 2108 : char *op = in->op + curpol->distance;
1000 : int clen;
1001 :
1002 3441 : RESIZEBUF(in, curpol->length * (pg_database_encoding_max_length() + 1) + 2 + 6);
1003 2108 : *(in->cur) = '\'';
1004 2108 : in->cur++;
1005 8182 : while (*op)
1006 : {
1007 6074 : if (t_iseq(op, '\''))
1008 : {
1009 6 : *(in->cur) = '\'';
1010 6 : in->cur++;
1011 : }
1012 6068 : else if (t_iseq(op, '\\'))
1013 : {
1014 3 : *(in->cur) = '\\';
1015 3 : in->cur++;
1016 : }
1017 :
1018 6074 : clen = ts_copychar_cstr(in->cur, op);
1019 6074 : op += clen;
1020 6074 : in->cur += clen;
1021 : }
1022 2108 : *(in->cur) = '\'';
1023 2108 : in->cur++;
1024 2108 : if (curpol->weight || curpol->prefix)
1025 : {
1026 87 : *(in->cur) = ':';
1027 87 : in->cur++;
1028 87 : if (curpol->prefix)
1029 : {
1030 12 : *(in->cur) = '*';
1031 12 : in->cur++;
1032 : }
1033 87 : if (curpol->weight & (1 << 3))
1034 : {
1035 30 : *(in->cur) = 'A';
1036 30 : in->cur++;
1037 : }
1038 87 : if (curpol->weight & (1 << 2))
1039 : {
1040 48 : *(in->cur) = 'B';
1041 48 : in->cur++;
1042 : }
1043 87 : if (curpol->weight & (1 << 1))
1044 : {
1045 9 : *(in->cur) = 'C';
1046 9 : in->cur++;
1047 : }
1048 87 : if (curpol->weight & 1)
1049 : {
1050 3 : *(in->cur) = 'D';
1051 3 : in->cur++;
1052 : }
1053 : }
1054 2108 : *(in->cur) = '\0';
1055 2108 : in->curpol++;
1056 : }
1057 1536 : else if (in->curpol->qoperator.oper == OP_NOT)
1058 : {
1059 186 : int priority = QO_PRIORITY(in->curpol);
1060 :
1061 186 : if (priority < parentPriority)
1062 : {
1063 0 : RESIZEBUF(in, 2);
1064 0 : sprintf(in->cur, "( ");
1065 0 : in->cur = strchr(in->cur, '\0');
1066 : }
1067 186 : RESIZEBUF(in, 1);
1068 186 : *(in->cur) = '!';
1069 186 : in->cur++;
1070 186 : *(in->cur) = '\0';
1071 186 : in->curpol++;
1072 :
1073 186 : infix(in, priority, false);
1074 186 : if (priority < parentPriority)
1075 : {
1076 0 : RESIZEBUF(in, 2);
1077 0 : sprintf(in->cur, " )");
1078 0 : in->cur = strchr(in->cur, '\0');
1079 : }
1080 : }
1081 : else
1082 : {
1083 1350 : int8 op = in->curpol->qoperator.oper;
1084 1350 : int priority = QO_PRIORITY(in->curpol);
1085 1350 : int16 distance = in->curpol->qoperator.distance;
1086 : INFIX nrm;
1087 1350 : bool needParenthesis = false;
1088 :
1089 1350 : in->curpol++;
1090 1350 : if (priority < parentPriority ||
1091 : /* phrase operator depends on order */
1092 360 : (op == OP_PHRASE && rightPhraseOp))
1093 : {
1094 166 : needParenthesis = true;
1095 166 : RESIZEBUF(in, 2);
1096 166 : sprintf(in->cur, "( ");
1097 166 : in->cur = strchr(in->cur, '\0');
1098 : }
1099 :
1100 1350 : nrm.curpol = in->curpol;
1101 1350 : nrm.op = in->op;
1102 1350 : nrm.buflen = 16;
1103 1350 : nrm.cur = nrm.buf = palloc_array(char, nrm.buflen);
1104 :
1105 : /* get right operand */
1106 1350 : infix(&nrm, priority, (op == OP_PHRASE));
1107 :
1108 : /* get & print left operand */
1109 1350 : in->curpol = nrm.curpol;
1110 1350 : infix(in, priority, false);
1111 :
1112 : /* print operator & right operand */
1113 1840 : RESIZEBUF(in, 3 + (2 + 10 /* distance */ ) + (nrm.cur - nrm.buf));
1114 1350 : switch (op)
1115 : {
1116 366 : case OP_OR:
1117 366 : sprintf(in->cur, " | %s", nrm.buf);
1118 366 : break;
1119 618 : case OP_AND:
1120 618 : sprintf(in->cur, " & %s", nrm.buf);
1121 618 : break;
1122 366 : case OP_PHRASE:
1123 366 : if (distance != 1)
1124 87 : sprintf(in->cur, " <%d> %s", distance, nrm.buf);
1125 : else
1126 279 : sprintf(in->cur, " <-> %s", nrm.buf);
1127 366 : break;
1128 0 : default:
1129 : /* OP_NOT is handled in above if-branch */
1130 0 : elog(ERROR, "unrecognized operator type: %d", op);
1131 : }
1132 1350 : in->cur = strchr(in->cur, '\0');
1133 1350 : pfree(nrm.buf);
1134 :
1135 1350 : if (needParenthesis)
1136 : {
1137 166 : RESIZEBUF(in, 2);
1138 166 : sprintf(in->cur, " )");
1139 166 : in->cur = strchr(in->cur, '\0');
1140 : }
1141 : }
1142 3644 : }
1143 :
1144 : Datum
1145 773 : tsqueryout(PG_FUNCTION_ARGS)
1146 : {
1147 773 : TSQuery query = PG_GETARG_TSQUERY(0);
1148 : INFIX nrm;
1149 :
1150 773 : if (query->size == 0)
1151 : {
1152 15 : char *b = palloc(1);
1153 :
1154 15 : *b = '\0';
1155 15 : PG_RETURN_POINTER(b);
1156 : }
1157 758 : nrm.curpol = GETQUERY(query);
1158 758 : nrm.buflen = 32;
1159 758 : nrm.cur = nrm.buf = palloc_array(char, nrm.buflen);
1160 758 : *(nrm.cur) = '\0';
1161 758 : nrm.op = GETOPERAND(query);
1162 758 : infix(&nrm, -1 /* lowest priority */ , false);
1163 :
1164 758 : PG_FREE_IF_COPY(query, 0);
1165 758 : PG_RETURN_CSTRING(nrm.buf);
1166 : }
1167 :
1168 : /*
1169 : * Binary Input / Output functions. The binary format is as follows:
1170 : *
1171 : * uint32 number of operators/operands in the query
1172 : *
1173 : * Followed by the operators and operands, in prefix notation. For each
1174 : * operand:
1175 : *
1176 : * uint8 type, QI_VAL
1177 : * uint8 weight
1178 : * uint8 prefix
1179 : * operand text in client encoding, null-terminated
1180 : *
1181 : * For each operator:
1182 : *
1183 : * uint8 type, QI_OPR
1184 : * uint8 operator, one of OP_AND, OP_PHRASE OP_OR, OP_NOT.
1185 : * uint16 distance (only for OP_PHRASE)
1186 : */
1187 : Datum
1188 0 : tsquerysend(PG_FUNCTION_ARGS)
1189 : {
1190 0 : TSQuery query = PG_GETARG_TSQUERY(0);
1191 : StringInfoData buf;
1192 : int i;
1193 0 : QueryItem *item = GETQUERY(query);
1194 :
1195 0 : pq_begintypsend(&buf);
1196 :
1197 0 : pq_sendint32(&buf, query->size);
1198 0 : for (i = 0; i < query->size; i++)
1199 : {
1200 0 : pq_sendint8(&buf, item->type);
1201 :
1202 0 : switch (item->type)
1203 : {
1204 0 : case QI_VAL:
1205 0 : pq_sendint8(&buf, item->qoperand.weight);
1206 0 : pq_sendint8(&buf, item->qoperand.prefix);
1207 0 : pq_sendstring(&buf, GETOPERAND(query) + item->qoperand.distance);
1208 0 : break;
1209 0 : case QI_OPR:
1210 0 : pq_sendint8(&buf, item->qoperator.oper);
1211 0 : if (item->qoperator.oper == OP_PHRASE)
1212 0 : pq_sendint16(&buf, item->qoperator.distance);
1213 0 : break;
1214 0 : default:
1215 0 : elog(ERROR, "unrecognized tsquery node type: %d", item->type);
1216 : }
1217 0 : item++;
1218 : }
1219 :
1220 0 : PG_FREE_IF_COPY(query, 0);
1221 :
1222 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
1223 : }
1224 :
1225 : Datum
1226 0 : tsqueryrecv(PG_FUNCTION_ARGS)
1227 : {
1228 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
1229 : TSQuery query;
1230 : int i,
1231 : len;
1232 : QueryItem *item;
1233 : int datalen;
1234 : char *ptr;
1235 : uint32 size;
1236 : const char **operands;
1237 : bool needcleanup;
1238 :
1239 0 : size = pq_getmsgint(buf, sizeof(uint32));
1240 0 : if (size > (MaxAllocSize / sizeof(QueryItem)))
1241 0 : elog(ERROR, "invalid size of tsquery");
1242 :
1243 : /* Allocate space to temporarily hold operand strings */
1244 0 : operands = palloc(size * sizeof(char *));
1245 :
1246 : /* Allocate space for all the QueryItems. */
1247 0 : len = HDRSIZETQ + sizeof(QueryItem) * size;
1248 0 : query = (TSQuery) palloc0(len);
1249 0 : query->size = size;
1250 0 : item = GETQUERY(query);
1251 :
1252 0 : datalen = 0;
1253 0 : for (i = 0; i < size; i++)
1254 : {
1255 0 : item->type = (int8) pq_getmsgint(buf, sizeof(int8));
1256 :
1257 0 : if (item->type == QI_VAL)
1258 : {
1259 : size_t val_len; /* length after recoding to server
1260 : * encoding */
1261 : uint8 weight;
1262 : uint8 prefix;
1263 : const char *val;
1264 : pg_crc32 valcrc;
1265 :
1266 0 : weight = (uint8) pq_getmsgint(buf, sizeof(uint8));
1267 0 : prefix = (uint8) pq_getmsgint(buf, sizeof(uint8));
1268 0 : val = pq_getmsgstring(buf);
1269 0 : val_len = strlen(val);
1270 :
1271 : /* Sanity checks */
1272 :
1273 0 : if (weight > 0xF)
1274 0 : elog(ERROR, "invalid tsquery: invalid weight bitmap");
1275 :
1276 0 : if (val_len > MAXSTRLEN)
1277 0 : elog(ERROR, "invalid tsquery: operand too long");
1278 :
1279 0 : if (datalen > MAXSTRPOS)
1280 0 : elog(ERROR, "invalid tsquery: total operand length exceeded");
1281 :
1282 : /* Looks valid. */
1283 :
1284 0 : INIT_LEGACY_CRC32(valcrc);
1285 0 : COMP_LEGACY_CRC32(valcrc, val, val_len);
1286 0 : FIN_LEGACY_CRC32(valcrc);
1287 :
1288 0 : item->qoperand.weight = weight;
1289 0 : item->qoperand.prefix = (prefix) ? true : false;
1290 0 : item->qoperand.valcrc = (int32) valcrc;
1291 0 : item->qoperand.length = val_len;
1292 0 : item->qoperand.distance = datalen;
1293 :
1294 : /*
1295 : * Operand strings are copied to the final struct after this loop;
1296 : * here we just collect them to an array
1297 : */
1298 0 : operands[i] = val;
1299 :
1300 0 : datalen += val_len + 1; /* + 1 for the '\0' terminator */
1301 : }
1302 0 : else if (item->type == QI_OPR)
1303 : {
1304 : int8 oper;
1305 :
1306 0 : oper = (int8) pq_getmsgint(buf, sizeof(int8));
1307 0 : if (oper != OP_NOT && oper != OP_OR && oper != OP_AND && oper != OP_PHRASE)
1308 0 : elog(ERROR, "invalid tsquery: unrecognized operator type %d",
1309 : (int) oper);
1310 0 : if (i == size - 1)
1311 0 : elog(ERROR, "invalid pointer to right operand");
1312 :
1313 0 : item->qoperator.oper = oper;
1314 0 : if (oper == OP_PHRASE)
1315 0 : item->qoperator.distance = (int16) pq_getmsgint(buf, sizeof(int16));
1316 : }
1317 : else
1318 0 : elog(ERROR, "unrecognized tsquery node type: %d", item->type);
1319 :
1320 0 : item++;
1321 : }
1322 :
1323 : /* Enlarge buffer to make room for the operand values. */
1324 0 : query = (TSQuery) repalloc(query, len + datalen);
1325 0 : item = GETQUERY(query);
1326 0 : ptr = GETOPERAND(query);
1327 :
1328 : /*
1329 : * Fill in the left-pointers. Checks that the tree is well-formed as a
1330 : * side-effect.
1331 : */
1332 0 : findoprnd(item, size, &needcleanup);
1333 :
1334 : /* Can't have found any QI_VALSTOP nodes */
1335 : Assert(!needcleanup);
1336 :
1337 : /* Copy operands to output struct */
1338 0 : for (i = 0; i < size; i++)
1339 : {
1340 0 : if (item->type == QI_VAL)
1341 : {
1342 0 : memcpy(ptr, operands[i], item->qoperand.length + 1);
1343 0 : ptr += item->qoperand.length + 1;
1344 : }
1345 0 : item++;
1346 : }
1347 :
1348 0 : pfree(operands);
1349 :
1350 : Assert(ptr - GETOPERAND(query) == datalen);
1351 :
1352 0 : SET_VARSIZE(query, len + datalen);
1353 :
1354 0 : PG_RETURN_TSQUERY(query);
1355 : }
1356 :
1357 : /*
1358 : * debug function, used only for view query
1359 : * which will be executed in non-leaf pages in index
1360 : */
1361 : Datum
1362 0 : tsquerytree(PG_FUNCTION_ARGS)
1363 : {
1364 0 : TSQuery query = PG_GETARG_TSQUERY(0);
1365 : INFIX nrm;
1366 : text *res;
1367 : QueryItem *q;
1368 : int len;
1369 :
1370 0 : if (query->size == 0)
1371 : {
1372 0 : res = (text *) palloc(VARHDRSZ);
1373 0 : SET_VARSIZE(res, VARHDRSZ);
1374 0 : PG_RETURN_POINTER(res);
1375 : }
1376 :
1377 0 : q = clean_NOT(GETQUERY(query), &len);
1378 :
1379 0 : if (!q)
1380 : {
1381 0 : res = cstring_to_text("T");
1382 : }
1383 : else
1384 : {
1385 0 : nrm.curpol = q;
1386 0 : nrm.buflen = 32;
1387 0 : nrm.cur = nrm.buf = palloc_array(char, nrm.buflen);
1388 0 : *(nrm.cur) = '\0';
1389 0 : nrm.op = GETOPERAND(query);
1390 0 : infix(&nrm, -1, false);
1391 0 : res = cstring_to_text_with_len(nrm.buf, nrm.cur - nrm.buf);
1392 0 : pfree(q);
1393 : }
1394 :
1395 0 : PG_FREE_IF_COPY(query, 0);
1396 :
1397 0 : PG_RETURN_TEXT_P(res);
1398 : }
|