LCOV - code coverage report
Current view: top level - contrib/unaccent - unaccent.c (source / functions) Hit Total Coverage
Test: PostgreSQL 16beta1 Lines: 130 153 85.0 %
Date: 2023-06-01 12:12:28 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * unaccent.c
       4             :  *    Text search unaccent dictionary
       5             :  *
       6             :  * Copyright (c) 2009-2023, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *    contrib/unaccent/unaccent.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : 
      14             : #include "postgres.h"
      15             : 
      16             : #include "catalog/namespace.h"
      17             : #include "catalog/pg_ts_dict.h"
      18             : #include "commands/defrem.h"
      19             : #include "lib/stringinfo.h"
      20             : #include "tsearch/ts_cache.h"
      21             : #include "tsearch/ts_locale.h"
      22             : #include "tsearch/ts_public.h"
      23             : #include "utils/builtins.h"
      24             : #include "utils/lsyscache.h"
      25             : #include "utils/regproc.h"
      26             : #include "utils/syscache.h"
      27             : 
      28           2 : PG_MODULE_MAGIC;
      29             : 
      30             : /*
      31             :  * An unaccent dictionary uses a trie to find a string to replace.  Each node
      32             :  * of the trie is an array of 256 TrieChar structs; the N-th element of the
      33             :  * array corresponds to next byte value N.  That element can contain both a
      34             :  * replacement string (to be used if the source string ends with this byte)
      35             :  * and a link to another trie node (to be followed if there are more bytes).
      36             :  *
      37             :  * Note that the trie search logic pays no attention to multibyte character
      38             :  * boundaries.  This is OK as long as both the data entered into the trie and
      39             :  * the data we're trying to look up are validly encoded; no partial-character
      40             :  * matches will occur.
      41             :  */
      42             : typedef struct TrieChar
      43             : {
      44             :     struct TrieChar *nextChar;
      45             :     char       *replaceTo;
      46             :     int         replacelen;
      47             : } TrieChar;
      48             : 
      49             : /*
      50             :  * placeChar - put str into trie's structure, byte by byte.
      51             :  *
      52             :  * If node is NULL, we need to make a new node, which will be returned;
      53             :  * otherwise the return value is the same as node.
      54             :  */
      55             : static TrieChar *
      56       17788 : placeChar(TrieChar *node, const unsigned char *str, int lenstr,
      57             :           const char *replaceTo, int replacelen)
      58             : {
      59             :     TrieChar   *curnode;
      60             : 
      61       17788 :     if (!node)
      62         252 :         node = (TrieChar *) palloc0(sizeof(TrieChar) * 256);
      63             : 
      64             :     Assert(lenstr > 0);          /* else str[0] doesn't exist */
      65             : 
      66       17788 :     curnode = node + *str;
      67             : 
      68       17788 :     if (lenstr <= 1)
      69             :     {
      70        6600 :         if (curnode->replaceTo)
      71           0 :             ereport(WARNING,
      72             :                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      73             :                      errmsg("duplicate source strings, first one will be used")));
      74             :         else
      75             :         {
      76        6600 :             curnode->replacelen = replacelen;
      77        6600 :             curnode->replaceTo = (char *) palloc(replacelen);
      78        6600 :             memcpy(curnode->replaceTo, replaceTo, replacelen);
      79             :         }
      80             :     }
      81             :     else
      82             :     {
      83       11188 :         curnode->nextChar = placeChar(curnode->nextChar, str + 1, lenstr - 1,
      84             :                                       replaceTo, replacelen);
      85             :     }
      86             : 
      87       17788 :     return node;
      88             : }
      89             : 
      90             : /*
      91             :  * initTrie  - create trie from file.
      92             :  *
      93             :  * Function converts UTF8-encoded file into current encoding.
      94             :  */
      95             : static TrieChar *
      96           4 : initTrie(const char *filename)
      97             : {
      98           4 :     TrieChar   *volatile rootTrie = NULL;
      99           4 :     MemoryContext ccxt = CurrentMemoryContext;
     100             :     tsearch_readline_state trst;
     101             :     volatile bool skip;
     102             : 
     103           4 :     filename = get_tsearch_config_filename(filename, "rules");
     104           4 :     if (!tsearch_readline_begin(&trst, filename))
     105           0 :         ereport(ERROR,
     106             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
     107             :                  errmsg("could not open unaccent file \"%s\": %m",
     108             :                         filename)));
     109             : 
     110             :     do
     111             :     {
     112             :         /*
     113             :          * pg_do_encoding_conversion() (called by tsearch_readline()) will
     114             :          * emit exception if it finds untranslatable characters in current
     115             :          * locale. We just skip such lines, continuing with the next.
     116             :          */
     117           4 :         skip = true;
     118             : 
     119           4 :         PG_TRY();
     120             :         {
     121             :             char       *line;
     122             : 
     123        6604 :             while ((line = tsearch_readline(&trst)) != NULL)
     124             :             {
     125             :                 /*----------
     126             :                  * The format of each line must be "src" or "src trg", where
     127             :                  * src and trg are sequences of one or more non-whitespace
     128             :                  * characters, separated by whitespace.  Whitespace at start
     129             :                  * or end of line is ignored.  If trg is omitted, an empty
     130             :                  * string is used as the replacement.
     131             :                  *
     132             :                  * We use a simple state machine, with states
     133             :                  *  0   initial (before src)
     134             :                  *  1   in src
     135             :                  *  2   in whitespace after src
     136             :                  *  3   in trg
     137             :                  *  4   in whitespace after trg
     138             :                  *  -1  syntax error detected
     139             :                  *----------
     140             :                  */
     141             :                 int         state;
     142             :                 char       *ptr;
     143        6600 :                 char       *src = NULL;
     144        6600 :                 char       *trg = NULL;
     145             :                 int         ptrlen;
     146        6600 :                 int         srclen = 0;
     147        6600 :                 int         trglen = 0;
     148             : 
     149        6600 :                 state = 0;
     150       34268 :                 for (ptr = line; *ptr; ptr += ptrlen)
     151             :                 {
     152       27668 :                     ptrlen = pg_mblen(ptr);
     153             :                     /* ignore whitespace, but end src or trg */
     154       27668 :                     if (t_isspace(ptr))
     155             :                     {
     156       12856 :                         if (state == 1)
     157        6600 :                             state = 2;
     158        6256 :                         else if (state == 3)
     159        6176 :                             state = 4;
     160       12856 :                         continue;
     161             :                     }
     162       14812 :                     switch (state)
     163             :                     {
     164        6600 :                         case 0:
     165             :                             /* start of src */
     166        6600 :                             src = ptr;
     167        6600 :                             srclen = ptrlen;
     168        6600 :                             state = 1;
     169        6600 :                             break;
     170           0 :                         case 1:
     171             :                             /* continue src */
     172           0 :                             srclen += ptrlen;
     173           0 :                             break;
     174        6176 :                         case 2:
     175             :                             /* start of trg */
     176        6176 :                             trg = ptr;
     177        6176 :                             trglen = ptrlen;
     178        6176 :                             state = 3;
     179        6176 :                             break;
     180        2036 :                         case 3:
     181             :                             /* continue trg */
     182        2036 :                             trglen += ptrlen;
     183        2036 :                             break;
     184           0 :                         default:
     185             :                             /* bogus line format */
     186           0 :                             state = -1;
     187           0 :                             break;
     188             :                     }
     189             :                 }
     190             : 
     191        6600 :                 if (state == 1 || state == 2)
     192             :                 {
     193             :                     /* trg was omitted, so use "" */
     194         424 :                     trg = "";
     195         424 :                     trglen = 0;
     196             :                 }
     197             : 
     198        6600 :                 if (state > 0)
     199        6600 :                     rootTrie = placeChar(rootTrie,
     200             :                                          (unsigned char *) src, srclen,
     201             :                                          trg, trglen);
     202           0 :                 else if (state < 0)
     203           0 :                     ereport(WARNING,
     204             :                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
     205             :                              errmsg("invalid syntax: more than two strings in unaccent rule")));
     206             : 
     207        6600 :                 pfree(line);
     208             :             }
     209           4 :             skip = false;
     210             :         }
     211           0 :         PG_CATCH();
     212             :         {
     213             :             ErrorData  *errdata;
     214             :             MemoryContext ecxt;
     215             : 
     216           0 :             ecxt = MemoryContextSwitchTo(ccxt);
     217           0 :             errdata = CopyErrorData();
     218           0 :             if (errdata->sqlerrcode == ERRCODE_UNTRANSLATABLE_CHARACTER)
     219             :             {
     220           0 :                 FlushErrorState();
     221             :             }
     222             :             else
     223             :             {
     224           0 :                 MemoryContextSwitchTo(ecxt);
     225           0 :                 PG_RE_THROW();
     226             :             }
     227             :         }
     228           4 :         PG_END_TRY();
     229             :     }
     230           4 :     while (skip);
     231             : 
     232           4 :     tsearch_readline_end(&trst);
     233             : 
     234           4 :     return rootTrie;
     235             : }
     236             : 
     237             : /*
     238             :  * findReplaceTo - find longest possible match in trie
     239             :  *
     240             :  * On success, returns pointer to ending subnode, plus length of matched
     241             :  * source string in *p_matchlen.  On failure, returns NULL.
     242             :  */
     243             : static TrieChar *
     244         140 : findReplaceTo(TrieChar *node, const unsigned char *src, int srclen,
     245             :               int *p_matchlen)
     246             : {
     247         140 :     TrieChar   *result = NULL;
     248         140 :     int         matchlen = 0;
     249             : 
     250         140 :     *p_matchlen = 0;            /* prevent uninitialized-variable warnings */
     251             : 
     252         398 :     while (node && matchlen < srclen)
     253             :     {
     254         258 :         node = node + src[matchlen];
     255         258 :         matchlen++;
     256             : 
     257         258 :         if (node->replaceTo)
     258             :         {
     259          62 :             result = node;
     260          62 :             *p_matchlen = matchlen;
     261             :         }
     262             : 
     263         258 :         node = node->nextChar;
     264             :     }
     265             : 
     266         140 :     return result;
     267             : }
     268             : 
     269           4 : PG_FUNCTION_INFO_V1(unaccent_init);
     270             : Datum
     271           4 : unaccent_init(PG_FUNCTION_ARGS)
     272             : {
     273           4 :     List       *dictoptions = (List *) PG_GETARG_POINTER(0);
     274           4 :     TrieChar   *rootTrie = NULL;
     275           4 :     bool        fileloaded = false;
     276             :     ListCell   *l;
     277             : 
     278           8 :     foreach(l, dictoptions)
     279             :     {
     280           4 :         DefElem    *defel = (DefElem *) lfirst(l);
     281             : 
     282           4 :         if (strcmp(defel->defname, "rules") == 0)
     283             :         {
     284           4 :             if (fileloaded)
     285           0 :                 ereport(ERROR,
     286             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     287             :                          errmsg("multiple Rules parameters")));
     288           4 :             rootTrie = initTrie(defGetString(defel));
     289           4 :             fileloaded = true;
     290             :         }
     291             :         else
     292             :         {
     293           0 :             ereport(ERROR,
     294             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     295             :                      errmsg("unrecognized Unaccent parameter: \"%s\"",
     296             :                             defel->defname)));
     297             :         }
     298             :     }
     299             : 
     300           4 :     if (!fileloaded)
     301             :     {
     302           0 :         ereport(ERROR,
     303             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     304             :                  errmsg("missing Rules parameter")));
     305             :     }
     306             : 
     307           4 :     PG_RETURN_POINTER(rootTrie);
     308             : }
     309             : 
     310           4 : PG_FUNCTION_INFO_V1(unaccent_lexize);
     311             : Datum
     312          44 : unaccent_lexize(PG_FUNCTION_ARGS)
     313             : {
     314          44 :     TrieChar   *rootTrie = (TrieChar *) PG_GETARG_POINTER(0);
     315          44 :     char       *srcchar = (char *) PG_GETARG_POINTER(1);
     316          44 :     int32       len = PG_GETARG_INT32(2);
     317          44 :     char       *srcstart = srcchar;
     318             :     TSLexeme   *res;
     319             :     StringInfoData buf;
     320             : 
     321             :     /* we allocate storage for the buffer only if needed */
     322          44 :     buf.data = NULL;
     323             : 
     324         184 :     while (len > 0)
     325             :     {
     326             :         TrieChar   *node;
     327             :         int         matchlen;
     328             : 
     329         140 :         node = findReplaceTo(rootTrie, (unsigned char *) srcchar, len,
     330             :                              &matchlen);
     331         140 :         if (node && node->replaceTo)
     332             :         {
     333          62 :             if (buf.data == NULL)
     334             :             {
     335             :                 /* initialize buffer */
     336          38 :                 initStringInfo(&buf);
     337             :                 /* insert any data we already skipped over */
     338          38 :                 if (srcchar != srcstart)
     339           6 :                     appendBinaryStringInfo(&buf, srcstart, srcchar - srcstart);
     340             :             }
     341          62 :             appendBinaryStringInfo(&buf, node->replaceTo, node->replacelen);
     342             :         }
     343             :         else
     344             :         {
     345          78 :             matchlen = pg_mblen(srcchar);
     346          78 :             if (buf.data != NULL)
     347          36 :                 appendBinaryStringInfo(&buf, srcchar, matchlen);
     348             :         }
     349             : 
     350         140 :         srcchar += matchlen;
     351         140 :         len -= matchlen;
     352             :     }
     353             : 
     354             :     /* return a result only if we made at least one substitution */
     355          44 :     if (buf.data != NULL)
     356             :     {
     357          38 :         res = (TSLexeme *) palloc0(sizeof(TSLexeme) * 2);
     358          38 :         res->lexeme = buf.data;
     359          38 :         res->flags = TSL_FILTER;
     360             :     }
     361             :     else
     362           6 :         res = NULL;
     363             : 
     364          44 :     PG_RETURN_POINTER(res);
     365             : }
     366             : 
     367             : /*
     368             :  * Function-like wrapper for dictionary
     369             :  */
     370           8 : PG_FUNCTION_INFO_V1(unaccent_dict);
     371             : Datum
     372          30 : unaccent_dict(PG_FUNCTION_ARGS)
     373             : {
     374             :     text       *str;
     375             :     int         strArg;
     376             :     Oid         dictOid;
     377             :     TSDictionaryCacheEntry *dict;
     378             :     TSLexeme   *res;
     379             : 
     380          30 :     if (PG_NARGS() == 1)
     381             :     {
     382             :         /*
     383             :          * Use the "unaccent" dictionary that is in the same schema that this
     384             :          * function is in.
     385             :          */
     386          16 :         Oid         procnspid = get_func_namespace(fcinfo->flinfo->fn_oid);
     387          16 :         const char *dictname = "unaccent";
     388             : 
     389          16 :         dictOid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid,
     390             :                                   PointerGetDatum(dictname),
     391             :                                   ObjectIdGetDatum(procnspid));
     392          16 :         if (!OidIsValid(dictOid))
     393           0 :             ereport(ERROR,
     394             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     395             :                      errmsg("text search dictionary \"%s.%s\" does not exist",
     396             :                             get_namespace_name(procnspid), dictname)));
     397          16 :         strArg = 0;
     398             :     }
     399             :     else
     400             :     {
     401          14 :         dictOid = PG_GETARG_OID(0);
     402          14 :         strArg = 1;
     403             :     }
     404          30 :     str = PG_GETARG_TEXT_PP(strArg);
     405             : 
     406          30 :     dict = lookup_ts_dictionary_cache(dictOid);
     407             : 
     408          30 :     res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(dict->lexize),
     409             :                                                      PointerGetDatum(dict->dictData),
     410             :                                                      PointerGetDatum(VARDATA_ANY(str)),
     411             :                                                      Int32GetDatum(VARSIZE_ANY_EXHDR(str)),
     412             :                                                      PointerGetDatum(NULL)));
     413             : 
     414          30 :     PG_FREE_IF_COPY(str, strArg);
     415             : 
     416          30 :     if (res == NULL)
     417             :     {
     418           4 :         PG_RETURN_TEXT_P(PG_GETARG_TEXT_P_COPY(strArg));
     419             :     }
     420          26 :     else if (res->lexeme == NULL)
     421             :     {
     422           0 :         pfree(res);
     423           0 :         PG_RETURN_TEXT_P(PG_GETARG_TEXT_P_COPY(strArg));
     424             :     }
     425             :     else
     426             :     {
     427          26 :         text       *txt = cstring_to_text(res->lexeme);
     428             : 
     429          26 :         pfree(res->lexeme);
     430          26 :         pfree(res);
     431             : 
     432          26 :         PG_RETURN_TEXT_P(txt);
     433             :     }
     434             : }

Generated by: LCOV version 1.14