LCOV - code coverage report
Current view: top level - src/backend/tsearch - dict_synonym.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 78 90 86.7 %
Date: 2025-01-18 04:15:08 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * dict_synonym.c
       4             :  *      Synonym dictionary: replace word by its synonym
       5             :  *
       6             :  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
       7             :  *
       8             :  *
       9             :  * IDENTIFICATION
      10             :  *    src/backend/tsearch/dict_synonym.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : #include "postgres.h"
      15             : 
      16             : #include "catalog/pg_collation_d.h"
      17             : #include "commands/defrem.h"
      18             : #include "tsearch/ts_locale.h"
      19             : #include "tsearch/ts_public.h"
      20             : #include "utils/fmgrprotos.h"
      21             : #include "utils/formatting.h"
      22             : 
      23             : typedef struct
      24             : {
      25             :     char       *in;
      26             :     char       *out;
      27             :     int         outlen;
      28             :     uint16      flags;
      29             : } Syn;
      30             : 
      31             : typedef struct
      32             : {
      33             :     int         len;            /* length of syn array */
      34             :     Syn        *syn;
      35             :     bool        case_sensitive;
      36             : } DictSyn;
      37             : 
      38             : /*
      39             :  * Finds the next whitespace-delimited word within the 'in' string.
      40             :  * Returns a pointer to the first character of the word, and a pointer
      41             :  * to the next byte after the last character in the word (in *end).
      42             :  * Character '*' at the end of word will not be treated as word
      43             :  * character if flags is not null.
      44             :  */
      45             : static char *
      46         440 : findwrd(char *in, char **end, uint16 *flags)
      47             : {
      48             :     char       *start;
      49             :     char       *lastchar;
      50             : 
      51             :     /* Skip leading spaces */
      52         440 :     while (*in && isspace((unsigned char) *in))
      53           0 :         in += pg_mblen(in);
      54             : 
      55             :     /* Return NULL on empty lines */
      56         440 :     if (*in == '\0')
      57             :     {
      58           0 :         *end = NULL;
      59           0 :         return NULL;
      60             :     }
      61             : 
      62         440 :     lastchar = start = in;
      63             : 
      64             :     /* Find end of word */
      65        3212 :     while (*in && !isspace((unsigned char) *in))
      66             :     {
      67        2772 :         lastchar = in;
      68        2772 :         in += pg_mblen(in);
      69             :     }
      70             : 
      71         440 :     if (in - lastchar == 1 && t_iseq(lastchar, '*') && flags)
      72             :     {
      73          44 :         *flags = TSL_PREFIX;
      74          44 :         *end = lastchar;
      75             :     }
      76             :     else
      77             :     {
      78         396 :         if (flags)
      79         176 :             *flags = 0;
      80         396 :         *end = in;
      81             :     }
      82             : 
      83         440 :     return start;
      84             : }
      85             : 
      86             : static int
      87        1304 : compareSyn(const void *a, const void *b)
      88             : {
      89        1304 :     return strcmp(((const Syn *) a)->in, ((const Syn *) b)->in);
      90             : }
      91             : 
      92             : 
      93             : Datum
      94          50 : dsynonym_init(PG_FUNCTION_ARGS)
      95             : {
      96          50 :     List       *dictoptions = (List *) PG_GETARG_POINTER(0);
      97             :     DictSyn    *d;
      98             :     ListCell   *l;
      99          50 :     char       *filename = NULL;
     100          50 :     bool        case_sensitive = false;
     101             :     tsearch_readline_state trst;
     102             :     char       *starti,
     103             :                *starto,
     104          50 :                *end = NULL;
     105          50 :     int         cur = 0;
     106          50 :     char       *line = NULL;
     107          50 :     uint16      flags = 0;
     108             : 
     109         132 :     foreach(l, dictoptions)
     110             :     {
     111          88 :         DefElem    *defel = (DefElem *) lfirst(l);
     112             : 
     113          88 :         if (strcmp(defel->defname, "synonyms") == 0)
     114          50 :             filename = defGetString(defel);
     115          38 :         else if (strcmp(defel->defname, "casesensitive") == 0)
     116          38 :             case_sensitive = defGetBoolean(defel);
     117             :         else
     118           0 :             ereport(ERROR,
     119             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     120             :                      errmsg("unrecognized synonym parameter: \"%s\"",
     121             :                             defel->defname)));
     122             :     }
     123             : 
     124          44 :     if (!filename)
     125           0 :         ereport(ERROR,
     126             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     127             :                  errmsg("missing Synonyms parameter")));
     128             : 
     129          44 :     filename = get_tsearch_config_filename(filename, "syn");
     130             : 
     131          44 :     if (!tsearch_readline_begin(&trst, filename))
     132           0 :         ereport(ERROR,
     133             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
     134             :                  errmsg("could not open synonym file \"%s\": %m",
     135             :                         filename)));
     136             : 
     137          44 :     d = (DictSyn *) palloc0(sizeof(DictSyn));
     138             : 
     139         264 :     while ((line = tsearch_readline(&trst)) != NULL)
     140             :     {
     141         220 :         starti = findwrd(line, &end, NULL);
     142         220 :         if (!starti)
     143             :         {
     144             :             /* Empty line */
     145           0 :             goto skipline;
     146             :         }
     147         220 :         if (*end == '\0')
     148             :         {
     149             :             /* A line with only one word. Ignore silently. */
     150           0 :             goto skipline;
     151             :         }
     152         220 :         *end = '\0';
     153             : 
     154         220 :         starto = findwrd(end + 1, &end, &flags);
     155         220 :         if (!starto)
     156             :         {
     157             :             /* A line with only one word (+whitespace). Ignore silently. */
     158           0 :             goto skipline;
     159             :         }
     160         220 :         *end = '\0';
     161             : 
     162             :         /*
     163             :          * starti now points to the first word, and starto to the second word
     164             :          * on the line, with a \0 terminator at the end of both words.
     165             :          */
     166             : 
     167         220 :         if (cur >= d->len)
     168             :         {
     169          44 :             if (d->len == 0)
     170             :             {
     171          44 :                 d->len = 64;
     172          44 :                 d->syn = (Syn *) palloc(sizeof(Syn) * d->len);
     173             :             }
     174             :             else
     175             :             {
     176           0 :                 d->len *= 2;
     177           0 :                 d->syn = (Syn *) repalloc(d->syn, sizeof(Syn) * d->len);
     178             :             }
     179             :         }
     180             : 
     181         220 :         if (case_sensitive)
     182             :         {
     183          60 :             d->syn[cur].in = pstrdup(starti);
     184          60 :             d->syn[cur].out = pstrdup(starto);
     185             :         }
     186             :         else
     187             :         {
     188         160 :             d->syn[cur].in = str_tolower(starti, strlen(starti), DEFAULT_COLLATION_OID);
     189         160 :             d->syn[cur].out = str_tolower(starto, strlen(starto), DEFAULT_COLLATION_OID);
     190             :         }
     191             : 
     192         220 :         d->syn[cur].outlen = strlen(starto);
     193         220 :         d->syn[cur].flags = flags;
     194             : 
     195         220 :         cur++;
     196             : 
     197         220 : skipline:
     198         220 :         pfree(line);
     199             :     }
     200             : 
     201          44 :     tsearch_readline_end(&trst);
     202             : 
     203          44 :     d->len = cur;
     204          44 :     qsort(d->syn, d->len, sizeof(Syn), compareSyn);
     205             : 
     206          44 :     d->case_sensitive = case_sensitive;
     207             : 
     208          44 :     PG_RETURN_POINTER(d);
     209             : }
     210             : 
     211             : Datum
     212         366 : dsynonym_lexize(PG_FUNCTION_ARGS)
     213             : {
     214         366 :     DictSyn    *d = (DictSyn *) PG_GETARG_POINTER(0);
     215         366 :     char       *in = (char *) PG_GETARG_POINTER(1);
     216         366 :     int32       len = PG_GETARG_INT32(2);
     217             :     Syn         key,
     218             :                *found;
     219             :     TSLexeme   *res;
     220             : 
     221             :     /* note: d->len test protects against Solaris bsearch-of-no-items bug */
     222         366 :     if (len <= 0 || d->len <= 0)
     223           0 :         PG_RETURN_POINTER(NULL);
     224             : 
     225         366 :     if (d->case_sensitive)
     226           6 :         key.in = pnstrdup(in, len);
     227             :     else
     228         360 :         key.in = str_tolower(in, len, DEFAULT_COLLATION_OID);
     229             : 
     230         366 :     key.out = NULL;
     231             : 
     232         366 :     found = (Syn *) bsearch(&key, d->syn, d->len, sizeof(Syn), compareSyn);
     233         366 :     pfree(key.in);
     234             : 
     235         366 :     if (!found)
     236         300 :         PG_RETURN_POINTER(NULL);
     237             : 
     238          66 :     res = palloc0(sizeof(TSLexeme) * 2);
     239          66 :     res[0].lexeme = pnstrdup(found->out, found->outlen);
     240          66 :     res[0].flags = found->flags;
     241             : 
     242          66 :     PG_RETURN_POINTER(res);
     243             : }

Generated by: LCOV version 1.14