LCOV - code coverage report
Current view: top level - contrib/ltree - ltxtquery_io.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 210 291 72.2 %
Date: 2024-04-16 11:11:24 Functions: 13 15 86.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * txtquery io
       3             :  * Teodor Sigaev <teodor@stack.net>
       4             :  * contrib/ltree/ltxtquery_io.c
       5             :  */
       6             : #include "postgres.h"
       7             : 
       8             : #include <ctype.h>
       9             : 
      10             : #include "crc32.h"
      11             : #include "libpq/pqformat.h"
      12             : #include "ltree.h"
      13             : #include "miscadmin.h"
      14             : #include "nodes/miscnodes.h"
      15             : #include "varatt.h"
      16             : 
      17             : 
      18             : /* parser's states */
      19             : #define WAITOPERAND 1
      20             : #define INOPERAND 2
      21             : #define WAITOPERATOR    3
      22             : 
      23             : /*
      24             :  * node of query tree, also used
      25             :  * for storing polish notation in parser
      26             :  */
      27             : typedef struct NODE
      28             : {
      29             :     int32       type;
      30             :     int32       val;
      31             :     int16       distance;
      32             :     int16       length;
      33             :     uint16      flag;
      34             :     struct NODE *next;
      35             : } NODE;
      36             : 
      37             : typedef struct
      38             : {
      39             :     char       *buf;
      40             :     int32       state;
      41             :     int32       count;
      42             :     struct Node *escontext;
      43             :     /* reverse polish notation in list (for temporary usage) */
      44             :     NODE       *str;
      45             :     /* number in str */
      46             :     int32       num;
      47             : 
      48             :     /* user-friendly operand */
      49             :     int32       lenop;
      50             :     int32       sumlen;
      51             :     char       *op;
      52             :     char       *curop;
      53             : } QPRS_STATE;
      54             : 
      55             : /*
      56             :  * get token from query string
      57             :  *
      58             :  * caller needs to check if a soft-error was set if the result is ERR.
      59             :  */
      60             : static int32
      61         730 : gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint16 *flag)
      62             : {
      63             :     int         charlen;
      64             : 
      65             :     for (;;)
      66             :     {
      67         730 :         charlen = pg_mblen(state->buf);
      68             : 
      69         730 :         switch (state->state)
      70             :         {
      71         166 :             case WAITOPERAND:
      72         166 :                 if (t_iseq(state->buf, '!'))
      73             :                 {
      74          12 :                     (state->buf)++;
      75          12 :                     *val = (int32) '!';
      76          12 :                     return OPR;
      77             :                 }
      78         154 :                 else if (t_iseq(state->buf, '('))
      79             :                 {
      80           0 :                     state->count++;
      81           0 :                     (state->buf)++;
      82           0 :                     return OPEN;
      83             :                 }
      84         154 :                 else if (ISLABEL(state->buf))
      85             :                 {
      86         100 :                     state->state = INOPERAND;
      87         100 :                     *strval = state->buf;
      88         100 :                     *lenval = charlen;
      89         100 :                     *flag = 0;
      90             :                 }
      91          54 :                 else if (!t_isspace(state->buf))
      92           4 :                     ereturn(state->escontext, ERR,
      93             :                             (errcode(ERRCODE_SYNTAX_ERROR),
      94             :                              errmsg("operand syntax error")));
      95         150 :                 break;
      96         414 :             case INOPERAND:
      97         414 :                 if (ISLABEL(state->buf))
      98             :                 {
      99         254 :                     if (*flag)
     100           0 :                         ereturn(state->escontext, ERR,
     101             :                                 (errcode(ERRCODE_SYNTAX_ERROR),
     102             :                                  errmsg("modifiers syntax error")));
     103         254 :                     *lenval += charlen;
     104             :                 }
     105         160 :                 else if (t_iseq(state->buf, '%'))
     106           8 :                     *flag |= LVAR_SUBLEXEME;
     107         152 :                 else if (t_iseq(state->buf, '@'))
     108          24 :                     *flag |= LVAR_INCASE;
     109         128 :                 else if (t_iseq(state->buf, '*'))
     110          28 :                     *flag |= LVAR_ANYEND;
     111             :                 else
     112             :                 {
     113         100 :                     state->state = WAITOPERATOR;
     114         100 :                     return VAL;
     115             :                 }
     116         314 :                 break;
     117         150 :             case WAITOPERATOR:
     118         150 :                 if (t_iseq(state->buf, '&') || t_iseq(state->buf, '|'))
     119             :                 {
     120          50 :                     state->state = WAITOPERAND;
     121          50 :                     *val = (int32) *(state->buf);
     122          50 :                     (state->buf)++;
     123          50 :                     return OPR;
     124             :                 }
     125         100 :                 else if (t_iseq(state->buf, ')'))
     126             :                 {
     127           0 :                     (state->buf)++;
     128           0 :                     state->count--;
     129           0 :                     return (state->count < 0) ? ERR : CLOSE;
     130             :                 }
     131         100 :                 else if (*(state->buf) == '\0')
     132             :                 {
     133          50 :                     return (state->count) ? ERR : END;
     134             :                 }
     135          50 :                 else if (!t_iseq(state->buf, ' '))
     136             :                 {
     137           0 :                     return ERR;
     138             :                 }
     139          50 :                 break;
     140           0 :             default:
     141           0 :                 return ERR;
     142             :                 break;
     143             :         }
     144             : 
     145         514 :         state->buf += charlen;
     146             :     }
     147             : 
     148             :     /* should not get here */
     149             : }
     150             : 
     151             : /*
     152             :  * push new one in polish notation reverse view
     153             :  */
     154             : static bool
     155         162 : pushquery(QPRS_STATE *state, int32 type, int32 val, int32 distance, int32 lenval, uint16 flag)
     156             : {
     157         162 :     NODE       *tmp = (NODE *) palloc(sizeof(NODE));
     158             : 
     159         162 :     tmp->type = type;
     160         162 :     tmp->val = val;
     161         162 :     tmp->flag = flag;
     162         162 :     if (distance > 0xffff)
     163           0 :         ereturn(state->escontext, false,
     164             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     165             :                  errmsg("value is too big")));
     166         162 :     if (lenval > 0xff)
     167           0 :         ereturn(state->escontext, false,
     168             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     169             :                  errmsg("operand is too long")));
     170         162 :     tmp->distance = distance;
     171         162 :     tmp->length = lenval;
     172         162 :     tmp->next = state->str;
     173         162 :     state->str = tmp;
     174         162 :     state->num++;
     175         162 :     return true;
     176             : }
     177             : 
     178             : /*
     179             :  * This function is used for query text parsing
     180             :  */
     181             : static bool
     182         100 : pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, uint16 flag)
     183             : {
     184         100 :     if (lenval > 0xffff)
     185           0 :         ereturn(state->escontext, false,
     186             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     187             :                  errmsg("word is too long")));
     188             : 
     189         100 :     if (!pushquery(state, type, ltree_crc32_sz(strval, lenval),
     190         100 :                    state->curop - state->op, lenval, flag))
     191           0 :         return false;
     192             : 
     193         100 :     while (state->curop - state->op + lenval + 1 >= state->lenop)
     194             :     {
     195           0 :         int32       tmp = state->curop - state->op;
     196             : 
     197           0 :         state->lenop *= 2;
     198           0 :         state->op = (char *) repalloc(state->op, state->lenop);
     199           0 :         state->curop = state->op + tmp;
     200             :     }
     201         100 :     memcpy(state->curop, strval, lenval);
     202         100 :     state->curop += lenval;
     203         100 :     *(state->curop) = '\0';
     204         100 :     state->curop++;
     205         100 :     state->sumlen += lenval + 1;
     206         100 :     return true;
     207             : }
     208             : 
     209             : #define STACKDEPTH      32
     210             : /*
     211             :  * make polish notation of query
     212             :  */
     213             : static int32
     214          54 : makepol(QPRS_STATE *state)
     215             : {
     216          54 :     int32       val = 0,
     217             :                 type;
     218          54 :     int32       lenval = 0;
     219          54 :     char       *strval = NULL;
     220             :     int32       stack[STACKDEPTH];
     221          54 :     int32       lenstack = 0;
     222          54 :     uint16      flag = 0;
     223             : 
     224             :     /* since this function recurses, it could be driven to stack overflow */
     225          54 :     check_stack_depth();
     226             : 
     227         216 :     while ((type = gettoken_query(state, &val, &lenval, &strval, &flag)) != END)
     228             :     {
     229         166 :         switch (type)
     230             :         {
     231         100 :             case VAL:
     232         100 :                 if (!pushval_asis(state, VAL, strval, lenval, flag))
     233           0 :                     return ERR;
     234         158 :                 while (lenstack && (stack[lenstack - 1] == (int32) '&' ||
     235          16 :                                     stack[lenstack - 1] == (int32) '!'))
     236             :                 {
     237          58 :                     lenstack--;
     238          58 :                     if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0))
     239           0 :                         return ERR;
     240             :                 }
     241         100 :                 break;
     242          62 :             case OPR:
     243          62 :                 if (lenstack && val == (int32) '|')
     244             :                 {
     245           0 :                     if (!pushquery(state, OPR, val, 0, 0, 0))
     246           0 :                         return ERR;
     247             :                 }
     248             :                 else
     249             :                 {
     250          62 :                     if (lenstack == STACKDEPTH)
     251             :                         /* internal error */
     252           0 :                         elog(ERROR, "stack too short");
     253          62 :                     stack[lenstack] = val;
     254          62 :                     lenstack++;
     255             :                 }
     256          62 :                 break;
     257           0 :             case OPEN:
     258           0 :                 if (makepol(state) == ERR)
     259           0 :                     return ERR;
     260           0 :                 while (lenstack && (stack[lenstack - 1] == (int32) '&' ||
     261           0 :                                     stack[lenstack - 1] == (int32) '!'))
     262             :                 {
     263           0 :                     lenstack--;
     264           0 :                     if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0))
     265           0 :                         return ERR;
     266             :                 }
     267           0 :                 break;
     268           0 :             case CLOSE:
     269           0 :                 while (lenstack)
     270             :                 {
     271           0 :                     lenstack--;
     272           0 :                     if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0))
     273           0 :                         return ERR;
     274             :                 };
     275           0 :                 return END;
     276             :                 break;
     277           4 :             case ERR:
     278           4 :                 if (SOFT_ERROR_OCCURRED(state->escontext))
     279           4 :                     return ERR;
     280             :                 /* fall through */
     281             :             default:
     282           0 :                 ereturn(state->escontext, ERR,
     283             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     284             :                          errmsg("syntax error")));
     285             : 
     286             :         }
     287             :     }
     288          54 :     while (lenstack)
     289             :     {
     290           4 :         lenstack--;
     291           4 :         if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0))
     292           0 :             return ERR;
     293             :     };
     294          50 :     return END;
     295             : }
     296             : 
     297             : static void
     298         162 : findoprnd(ITEM *ptr, int32 *pos)
     299             : {
     300             :     /* since this function recurses, it could be driven to stack overflow. */
     301         162 :     check_stack_depth();
     302             : 
     303         162 :     if (ptr[*pos].type == VAL || ptr[*pos].type == VALTRUE)
     304             :     {
     305         100 :         ptr[*pos].left = 0;
     306         100 :         (*pos)++;
     307             :     }
     308          62 :     else if (ptr[*pos].val == (int32) '!')
     309             :     {
     310          12 :         ptr[*pos].left = 1;
     311          12 :         (*pos)++;
     312          12 :         findoprnd(ptr, pos);
     313             :     }
     314             :     else
     315             :     {
     316          50 :         ITEM       *curitem = &ptr[*pos];
     317          50 :         int32       tmp = *pos;
     318             : 
     319          50 :         (*pos)++;
     320          50 :         findoprnd(ptr, pos);
     321          50 :         curitem->left = *pos - tmp;
     322          50 :         findoprnd(ptr, pos);
     323             :     }
     324         162 : }
     325             : 
     326             : 
     327             : /*
     328             :  * input
     329             :  */
     330             : static ltxtquery *
     331          54 : queryin(char *buf, struct Node *escontext)
     332             : {
     333             :     QPRS_STATE  state;
     334             :     int32       i;
     335             :     ltxtquery  *query;
     336             :     int32       commonlen;
     337             :     ITEM       *ptr;
     338             :     NODE       *tmp;
     339          54 :     int32       pos = 0;
     340             : 
     341             : #ifdef BS_DEBUG
     342             :     char        pbuf[16384],
     343             :                *cur;
     344             : #endif
     345             : 
     346             :     /* init state */
     347          54 :     state.buf = buf;
     348          54 :     state.state = WAITOPERAND;
     349          54 :     state.count = 0;
     350          54 :     state.num = 0;
     351          54 :     state.str = NULL;
     352          54 :     state.escontext = escontext;
     353             : 
     354             :     /* init list of operand */
     355          54 :     state.sumlen = 0;
     356          54 :     state.lenop = 64;
     357          54 :     state.curop = state.op = (char *) palloc(state.lenop);
     358          54 :     *(state.curop) = '\0';
     359             : 
     360             :     /* parse query & make polish notation (postfix, but in reverse order) */
     361          54 :     if (makepol(&state) == ERR)
     362           4 :         return NULL;
     363          50 :     if (!state.num)
     364           0 :         ereturn(escontext, NULL,
     365             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     366             :                  errmsg("syntax error"),
     367             :                  errdetail("Empty query.")));
     368             : 
     369          50 :     if (LTXTQUERY_TOO_BIG(state.num, state.sumlen))
     370           0 :         ereturn(escontext, NULL,
     371             :                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     372             :                  errmsg("ltxtquery is too large")));
     373          50 :     commonlen = COMPUTESIZE(state.num, state.sumlen);
     374             : 
     375          50 :     query = (ltxtquery *) palloc0(commonlen);
     376          50 :     SET_VARSIZE(query, commonlen);
     377          50 :     query->size = state.num;
     378          50 :     ptr = GETQUERY(query);
     379             : 
     380             :     /* set item in polish notation */
     381         212 :     for (i = 0; i < state.num; i++)
     382             :     {
     383         162 :         ptr[i].type = state.str->type;
     384         162 :         ptr[i].val = state.str->val;
     385         162 :         ptr[i].distance = state.str->distance;
     386         162 :         ptr[i].length = state.str->length;
     387         162 :         ptr[i].flag = state.str->flag;
     388         162 :         tmp = state.str->next;
     389         162 :         pfree(state.str);
     390         162 :         state.str = tmp;
     391             :     }
     392             : 
     393             :     /* set user-friendly operand view */
     394          50 :     memcpy(GETOPERAND(query), state.op, state.sumlen);
     395          50 :     pfree(state.op);
     396             : 
     397             :     /* set left operand's position for every operator */
     398          50 :     pos = 0;
     399          50 :     findoprnd(ptr, &pos);
     400             : 
     401          50 :     return query;
     402             : }
     403             : 
     404             : /*
     405             :  * in without morphology
     406             :  */
     407           6 : PG_FUNCTION_INFO_V1(ltxtq_in);
     408             : Datum
     409          54 : ltxtq_in(PG_FUNCTION_ARGS)
     410             : {
     411             :     ltxtquery  *res;
     412             : 
     413          54 :     if ((res = queryin((char *) PG_GETARG_POINTER(0), fcinfo->context)) == NULL)
     414           4 :         PG_RETURN_NULL();
     415          50 :     PG_RETURN_POINTER(res);
     416             : }
     417             : 
     418             : /*
     419             :  * ltxtquery type recv function
     420             :  *
     421             :  * The type is sent as text in binary mode, so this is almost the same
     422             :  * as the input function, but it's prefixed with a version number so we
     423             :  * can change the binary format sent in future if necessary. For now,
     424             :  * only version 1 is supported.
     425             :  */
     426           4 : PG_FUNCTION_INFO_V1(ltxtq_recv);
     427             : Datum
     428           0 : ltxtq_recv(PG_FUNCTION_ARGS)
     429             : {
     430           0 :     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
     431           0 :     int         version = pq_getmsgint(buf, 1);
     432             :     char       *str;
     433             :     int         nbytes;
     434             :     ltxtquery  *res;
     435             : 
     436           0 :     if (version != 1)
     437           0 :         elog(ERROR, "unsupported ltxtquery version number %d", version);
     438             : 
     439           0 :     str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
     440           0 :     res = queryin(str, NULL);
     441           0 :     pfree(str);
     442             : 
     443           0 :     PG_RETURN_POINTER(res);
     444             : }
     445             : 
     446             : /*
     447             :  * out function
     448             :  */
     449             : typedef struct
     450             : {
     451             :     ITEM       *curpol;
     452             :     char       *buf;
     453             :     char       *cur;
     454             :     char       *op;
     455             :     int32       buflen;
     456             : } INFIX;
     457             : 
     458             : #define RESIZEBUF(inf,addsize) \
     459             : while( ( (inf)->cur - (inf)->buf ) + (addsize) + 1 >= (inf)->buflen ) \
     460             : { \
     461             :     int32 len = (inf)->cur - (inf)->buf; \
     462             :     (inf)->buflen *= 2; \
     463             :     (inf)->buf = (char*) repalloc( (void*)(inf)->buf, (inf)->buflen ); \
     464             :     (inf)->cur = (inf)->buf + len; \
     465             : }
     466             : 
     467             : /*
     468             :  * recursive walk on tree and print it in
     469             :  * infix (human-readable) view
     470             :  */
     471             : static void
     472          20 : infix(INFIX *in, bool first)
     473             : {
     474             :     /* since this function recurses, it could be driven to stack overflow. */
     475          20 :     check_stack_depth();
     476             : 
     477          20 :     if (in->curpol->type == VAL)
     478             :     {
     479          12 :         char       *op = in->op + in->curpol->distance;
     480             : 
     481          16 :         RESIZEBUF(in, in->curpol->length * 2 + 5);
     482          64 :         while (*op)
     483             :         {
     484          52 :             *(in->cur) = *op;
     485          52 :             op++;
     486          52 :             in->cur++;
     487             :         }
     488          12 :         if (in->curpol->flag & LVAR_SUBLEXEME)
     489             :         {
     490           4 :             *(in->cur) = '%';
     491           4 :             in->cur++;
     492             :         }
     493          12 :         if (in->curpol->flag & LVAR_INCASE)
     494             :         {
     495           2 :             *(in->cur) = '@';
     496           2 :             in->cur++;
     497             :         }
     498          12 :         if (in->curpol->flag & LVAR_ANYEND)
     499             :         {
     500           6 :             *(in->cur) = '*';
     501           6 :             in->cur++;
     502             :         }
     503          12 :         *(in->cur) = '\0';
     504          12 :         in->curpol++;
     505             :     }
     506           8 :     else if (in->curpol->val == (int32) '!')
     507             :     {
     508           2 :         bool        isopr = false;
     509             : 
     510           2 :         RESIZEBUF(in, 1);
     511           2 :         *(in->cur) = '!';
     512           2 :         in->cur++;
     513           2 :         *(in->cur) = '\0';
     514           2 :         in->curpol++;
     515           2 :         if (in->curpol->type == OPR)
     516             :         {
     517           0 :             isopr = true;
     518           0 :             RESIZEBUF(in, 2);
     519           0 :             sprintf(in->cur, "( ");
     520           0 :             in->cur = strchr(in->cur, '\0');
     521             :         }
     522           2 :         infix(in, isopr);
     523           2 :         if (isopr)
     524             :         {
     525           0 :             RESIZEBUF(in, 2);
     526           0 :             sprintf(in->cur, " )");
     527           0 :             in->cur = strchr(in->cur, '\0');
     528             :         }
     529             :     }
     530             :     else
     531             :     {
     532           6 :         int32       op = in->curpol->val;
     533             :         INFIX       nrm;
     534             : 
     535           6 :         in->curpol++;
     536           6 :         if (op == (int32) '|' && !first)
     537             :         {
     538           0 :             RESIZEBUF(in, 2);
     539           0 :             sprintf(in->cur, "( ");
     540           0 :             in->cur = strchr(in->cur, '\0');
     541             :         }
     542             : 
     543           6 :         nrm.curpol = in->curpol;
     544           6 :         nrm.op = in->op;
     545           6 :         nrm.buflen = 16;
     546           6 :         nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen);
     547             : 
     548             :         /* get right operand */
     549           6 :         infix(&nrm, false);
     550             : 
     551             :         /* get & print left operand */
     552           6 :         in->curpol = nrm.curpol;
     553           6 :         infix(in, false);
     554             : 
     555             :         /* print operator & right operand */
     556           6 :         RESIZEBUF(in, 3 + (nrm.cur - nrm.buf));
     557           6 :         sprintf(in->cur, " %c %s", op, nrm.buf);
     558           6 :         in->cur = strchr(in->cur, '\0');
     559           6 :         pfree(nrm.buf);
     560             : 
     561           6 :         if (op == (int32) '|' && !first)
     562             :         {
     563           0 :             RESIZEBUF(in, 2);
     564           0 :             sprintf(in->cur, " )");
     565           0 :             in->cur = strchr(in->cur, '\0');
     566             :         }
     567             :     }
     568          20 : }
     569             : 
     570           6 : PG_FUNCTION_INFO_V1(ltxtq_out);
     571             : Datum
     572           6 : ltxtq_out(PG_FUNCTION_ARGS)
     573             : {
     574           6 :     ltxtquery  *query = PG_GETARG_LTXTQUERY_P(0);
     575             :     INFIX       nrm;
     576             : 
     577           6 :     if (query->size == 0)
     578           0 :         ereport(ERROR,
     579             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     580             :                  errmsg("syntax error"),
     581             :                  errdetail("Empty query.")));
     582             : 
     583           6 :     nrm.curpol = GETQUERY(query);
     584           6 :     nrm.buflen = 32;
     585           6 :     nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen);
     586           6 :     *(nrm.cur) = '\0';
     587           6 :     nrm.op = GETOPERAND(query);
     588           6 :     infix(&nrm, true);
     589             : 
     590           6 :     PG_RETURN_POINTER(nrm.buf);
     591             : }
     592             : 
     593             : /*
     594             :  * ltxtquery type send function
     595             :  *
     596             :  * The type is sent as text in binary mode, so this is almost the same
     597             :  * as the output function, but it's prefixed with a version number so we
     598             :  * can change the binary format sent in future if necessary. For now,
     599             :  * only version 1 is supported.
     600             :  */
     601           4 : PG_FUNCTION_INFO_V1(ltxtq_send);
     602             : Datum
     603           0 : ltxtq_send(PG_FUNCTION_ARGS)
     604             : {
     605           0 :     ltxtquery  *query = PG_GETARG_LTXTQUERY_P(0);
     606             :     StringInfoData buf;
     607           0 :     int         version = 1;
     608             :     INFIX       nrm;
     609             : 
     610           0 :     if (query->size == 0)
     611           0 :         ereport(ERROR,
     612             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     613             :                  errmsg("syntax error"),
     614             :                  errdetail("Empty query.")));
     615             : 
     616           0 :     nrm.curpol = GETQUERY(query);
     617           0 :     nrm.buflen = 32;
     618           0 :     nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen);
     619           0 :     *(nrm.cur) = '\0';
     620           0 :     nrm.op = GETOPERAND(query);
     621           0 :     infix(&nrm, true);
     622             : 
     623           0 :     pq_begintypsend(&buf);
     624           0 :     pq_sendint8(&buf, version);
     625           0 :     pq_sendtext(&buf, nrm.buf, strlen(nrm.buf));
     626           0 :     pfree(nrm.buf);
     627             : 
     628           0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
     629             : }

Generated by: LCOV version 1.14