LCOV - differential code coverage report
Current view: top level - contrib/fuzzystrmatch - fuzzystrmatch.c (source / functions) Coverage Total Hit UNC UIC UBC GIC GNC CBC DUB DCB
Current: 380a8b2ea024c33a35e7abc8628e7c4f52f9f9f9 vs db5ed03217b9c238703df8b4b286115d6e940488 Lines: 45.2 % 281 127 154 12 115 10
Current Date: 2026-05-29 21:51:00 -0400 Functions: 90.5 % 21 19 2 5 14
Baseline: lcov-20260530-034037-baseline Branches: 27.0 % 233 63 6 35 129 3 18 42 119 33
Baseline Date: 2026-05-29 14:39:03 -0700 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(30,360] days: 100.0 % 12 12 12
(360..) days: 42.8 % 269 115 154 115
Function coverage date bins:
(30,360] days: 100.0 % 1 1 1
(360..) days: 90.0 % 20 18 2 4 14
Branch coverage date bins:
(30,360] days: 75.0 % 24 18 6 18
(360..) days: 21.5 % 209 45 35 129 3 42

 Age         Owner                    Branch data    TLA  Line data    Source code
                                  1                 :                : /*
                                  2                 :                :  * fuzzystrmatch.c
                                  3                 :                :  *
                                  4                 :                :  * Functions for "fuzzy" comparison of strings
                                  5                 :                :  *
                                  6                 :                :  * Joe Conway <mail@joeconway.com>
                                  7                 :                :  *
                                  8                 :                :  * contrib/fuzzystrmatch/fuzzystrmatch.c
                                  9                 :                :  * Copyright (c) 2001-2026, PostgreSQL Global Development Group
                                 10                 :                :  * ALL RIGHTS RESERVED;
                                 11                 :                :  *
                                 12                 :                :  * metaphone()
                                 13                 :                :  * -----------
                                 14                 :                :  * Modified for PostgreSQL by Joe Conway.
                                 15                 :                :  * Based on CPAN's "Text-Metaphone-1.96" by Michael G Schwern <schwern@pobox.com>
                                 16                 :                :  * Code slightly modified for use as PostgreSQL function (palloc, elog, etc).
                                 17                 :                :  * Metaphone was originally created by Lawrence Philips and presented in article
                                 18                 :                :  * in "Computer Language" December 1990 issue.
                                 19                 :                :  *
                                 20                 :                :  * Permission to use, copy, modify, and distribute this software and its
                                 21                 :                :  * documentation for any purpose, without fee, and without a written agreement
                                 22                 :                :  * is hereby granted, provided that the above copyright notice and this
                                 23                 :                :  * paragraph and the following two paragraphs appear in all copies.
                                 24                 :                :  *
                                 25                 :                :  * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
                                 26                 :                :  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
                                 27                 :                :  * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
                                 28                 :                :  * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
                                 29                 :                :  * POSSIBILITY OF SUCH DAMAGE.
                                 30                 :                :  *
                                 31                 :                :  * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
                                 32                 :                :  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
                                 33                 :                :  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
                                 34                 :                :  * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
                                 35                 :                :  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
                                 36                 :                :  *
                                 37                 :                :  */
                                 38                 :                : 
                                 39                 :                : #include "postgres.h"
                                 40                 :                : 
                                 41                 :                : #include <ctype.h>
                                 42                 :                : 
                                 43                 :                : #include "utils/builtins.h"
                                 44                 :                : #include "utils/varlena.h"
                                 45                 :                : #include "varatt.h"
                                 46                 :                : 
  430 tgl@sss.pgh.pa.us          47                 :CBC           2 : PG_MODULE_MAGIC_EXT(
                                 48                 :                :                     .name = "fuzzystrmatch",
                                 49                 :                :                     .version = PG_VERSION
                                 50                 :                : );
                                 51                 :                : 
                                 52                 :                : /*
                                 53                 :                :  * Soundex
                                 54                 :                :  */
                                 55                 :                : static void _soundex(const char *instr, char *outstr);
                                 56                 :                : 
                                 57                 :                : #define SOUNDEX_LEN 4
                                 58                 :                : 
                                 59                 :                : /*                                  ABCDEFGHIJKLMNOPQRSTUVWXYZ */
                                 60                 :                : static const char *const soundex_table = "01230120022455012623010202";
                                 61                 :                : 
                                 62                 :                : static char
 6262                            63                 :            127 : soundex_code(char letter)
                                 64                 :                : {
  138 jdavis@postgresql.or       65                 :GNC         127 :     letter = pg_ascii_toupper((unsigned char) letter);
                                 66                 :                :     /* Defend against non-ASCII letters */
 6262 tgl@sss.pgh.pa.us          67   [ +  +  +  - ]:CBC         127 :     if (letter >= 'A' && letter <= 'Z')
                                 68                 :            126 :         return soundex_table[letter - 'A'];
                                 69                 :              1 :     return letter;
                                 70                 :                : }
                                 71                 :                : 
                                 72                 :                : /*
                                 73                 :                :  * Metaphone
                                 74                 :                :  */
                                 75                 :                : #define MAX_METAPHONE_STRLEN        255
                                 76                 :                : 
                                 77                 :                : /*
                                 78                 :                :  * Original code by Michael G Schwern starts here.
                                 79                 :                :  * Code slightly modified for use as PostgreSQL function.
                                 80                 :                :  */
                                 81                 :                : 
                                 82                 :                : 
                                 83                 :                : /**************************************************************************
                                 84                 :                :     metaphone -- Breaks english phrases down into their phonemes.
                                 85                 :                : 
                                 86                 :                :     Input
                                 87                 :                :         word            --  An english word to be phonized
                                 88                 :                :         max_phonemes    --  How many phonemes to calculate.  If 0, then it
                                 89                 :                :                             will phonize the entire phrase.
                                 90                 :                :         phoned_word     --  The final phonized word.  (We'll allocate the
                                 91                 :                :                             memory.)
                                 92                 :                :     Output
                                 93                 :                :         error   --  A simple error flag, returns true or false
                                 94                 :                : 
                                 95                 :                :     NOTES:  ALL non-alpha characters are ignored, this includes whitespace,
                                 96                 :                :     although non-alpha characters will break up phonemes.
                                 97                 :                : ****************************************************************************/
                                 98                 :                : 
                                 99                 :                : 
                                100                 :                : /*
                                101                 :                :  * I add modifications to the traditional metaphone algorithm that you
                                102                 :                :  * might find in books.  Define this if you want metaphone to behave
                                103                 :                :  * traditionally
                                104                 :                :  */
                                105                 :                : #undef USE_TRADITIONAL_METAPHONE
                                106                 :                : 
                                107                 :                : /* Special encodings */
                                108                 :                : #define  SH     'X'
                                109                 :                : #define  TH     '0'
                                110                 :                : 
                                111                 :                : static char Lookahead(char *word, int how_far);
                                112                 :                : static void _metaphone(char *word, int max_phonemes, char **phoned_word);
                                113                 :                : 
                                114                 :                : /* Metachar.h ... little bits about characters for metaphone */
                                115                 :                : 
                                116                 :                : 
                                117                 :                : /*-- Character encoding array & accessing macros --*/
                                118                 :                : /* Stolen directly out of the book... */
                                119                 :                : static const char _codes[26] = {
                                120                 :                :     1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2, 2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0
                                121                 :                : /*  a  b c  d e f g  h i j k l m n o p q r s t u v w x y z */
                                122                 :                : };
                                123                 :                : 
                                124                 :                : static int
                                125                 :              1 : getcode(char c)
                                126                 :                : {
  138 jdavis@postgresql.or      127                 :GNC           1 :     c = pg_ascii_toupper((unsigned char) c);
                                128                 :                :     /* Defend against non-ASCII letters */
                                129   [ +  -  +  - ]:              1 :     if (c >= 'A' && c <= 'Z')
                                130                 :              1 :         return _codes[c - 'A'];
                                131                 :                : 
 6262 tgl@sss.pgh.pa.us         132                 :UBC           0 :     return 0;
                                133                 :                : }
                                134                 :                : 
                                135                 :                : static bool
  138 jdavis@postgresql.or      136                 :GNC          66 : ascii_isalpha(char c)
                                137                 :                : {
                                138   [ +  +  +  +  :            113 :     return (c >= 'A' && c <= 'Z') ||
                                              +  + ]
                                139         [ +  - ]:             47 :         (c >= 'a' && c <= 'z');
                                140                 :                : }
                                141                 :                : 
                                142                 :                : #define isvowel(c)  (getcode(c) & 1)    /* AEIOU */
                                143                 :                : 
                                144                 :                : /* These letters are passed through unchanged */
                                145                 :                : #define NOCHANGE(c) (getcode(c) & 2)    /* FJMNR */
                                146                 :                : 
                                147                 :                : /* These form diphthongs when preceding H */
                                148                 :                : #define AFFECTH(c)  (getcode(c) & 4)    /* CGPST */
                                149                 :                : 
                                150                 :                : /* These make C and G soft */
                                151                 :                : #define MAKESOFT(c) (getcode(c) & 8)    /* EIY */
                                152                 :                : 
                                153                 :                : /* These prevent GH from becoming F */
                                154                 :                : #define NOGHTOF(c)  (getcode(c) & 16)   /* BDH */
                                155                 :                : 
 6631 tgl@sss.pgh.pa.us         156                 :CBC           2 : PG_FUNCTION_INFO_V1(levenshtein_with_costs);
                                157                 :                : Datum
                                158                 :              1 : levenshtein_with_costs(PG_FUNCTION_ARGS)
                                159                 :                : {
 5784 rhaas@postgresql.org      160                 :              1 :     text       *src = PG_GETARG_TEXT_PP(0);
                                161                 :              1 :     text       *dst = PG_GETARG_TEXT_PP(1);
 6197 bruce@momjian.us          162                 :              1 :     int         ins_c = PG_GETARG_INT32(2);
                                163                 :              1 :     int         del_c = PG_GETARG_INT32(3);
                                164                 :              1 :     int         sub_c = PG_GETARG_INT32(4);
                                165                 :                :     const char *s_data;
                                166                 :                :     const char *t_data;
                                167                 :                :     int         s_bytes,
                                168                 :                :                 t_bytes;
                                169                 :                : 
                                170                 :                :     /* Extract a pointer to the actual character data */
 4216 rhaas@postgresql.org      171         [ -  + ]:              1 :     s_data = VARDATA_ANY(src);
                                172         [ -  + ]:              1 :     t_data = VARDATA_ANY(dst);
                                173                 :                :     /* Determine length of each string in bytes */
                                174   [ -  +  -  -  :              1 :     s_bytes = VARSIZE_ANY_EXHDR(src);
                                     -  -  -  -  -  
                                                 + ]
                                175   [ -  +  -  -  :              1 :     t_bytes = VARSIZE_ANY_EXHDR(dst);
                                     -  -  -  -  -  
                                                 + ]
                                176                 :                : 
 3781 tgl@sss.pgh.pa.us         177                 :              1 :     PG_RETURN_INT32(varstr_levenshtein(s_data, s_bytes, t_data, t_bytes,
                                178                 :                :                                        ins_c, del_c, sub_c, false));
                                179                 :                : }
                                180                 :                : 
                                181                 :                : 
 6631                           182                 :              2 : PG_FUNCTION_INFO_V1(levenshtein);
                                183                 :                : Datum
                                184                 :              1 : levenshtein(PG_FUNCTION_ARGS)
                                185                 :                : {
 5784 rhaas@postgresql.org      186                 :              1 :     text       *src = PG_GETARG_TEXT_PP(0);
                                187                 :              1 :     text       *dst = PG_GETARG_TEXT_PP(1);
                                188                 :                :     const char *s_data;
                                189                 :                :     const char *t_data;
                                190                 :                :     int         s_bytes,
                                191                 :                :                 t_bytes;
                                192                 :                : 
                                193                 :                :     /* Extract a pointer to the actual character data */
 4216                           194         [ -  + ]:              1 :     s_data = VARDATA_ANY(src);
                                195         [ -  + ]:              1 :     t_data = VARDATA_ANY(dst);
                                196                 :                :     /* Determine length of each string in bytes */
                                197   [ -  +  -  -  :              1 :     s_bytes = VARSIZE_ANY_EXHDR(src);
                                     -  -  -  -  -  
                                                 + ]
                                198   [ -  +  -  -  :              1 :     t_bytes = VARSIZE_ANY_EXHDR(dst);
                                     -  -  -  -  -  
                                                 + ]
                                199                 :                : 
 3781 tgl@sss.pgh.pa.us         200                 :              1 :     PG_RETURN_INT32(varstr_levenshtein(s_data, s_bytes, t_data, t_bytes,
                                201                 :                :                                        1, 1, 1, false));
                                202                 :                : }
                                203                 :                : 
                                204                 :                : 
 5702 rhaas@postgresql.org      205                 :              1 : PG_FUNCTION_INFO_V1(levenshtein_less_equal_with_costs);
                                206                 :                : Datum
 5702 rhaas@postgresql.org      207                 :UBC           0 : levenshtein_less_equal_with_costs(PG_FUNCTION_ARGS)
                                208                 :                : {
                                209                 :              0 :     text       *src = PG_GETARG_TEXT_PP(0);
                                210                 :              0 :     text       *dst = PG_GETARG_TEXT_PP(1);
                                211                 :              0 :     int         ins_c = PG_GETARG_INT32(2);
                                212                 :              0 :     int         del_c = PG_GETARG_INT32(3);
                                213                 :              0 :     int         sub_c = PG_GETARG_INT32(4);
                                214                 :              0 :     int         max_d = PG_GETARG_INT32(5);
                                215                 :                :     const char *s_data;
                                216                 :                :     const char *t_data;
                                217                 :                :     int         s_bytes,
                                218                 :                :                 t_bytes;
                                219                 :                : 
                                220                 :                :     /* Extract a pointer to the actual character data */
 4216                           221         [ #  # ]:              0 :     s_data = VARDATA_ANY(src);
                                222         [ #  # ]:              0 :     t_data = VARDATA_ANY(dst);
                                223                 :                :     /* Determine length of each string in bytes */
                                224   [ #  #  #  #  :              0 :     s_bytes = VARSIZE_ANY_EXHDR(src);
                                     #  #  #  #  #  
                                                 # ]
                                225   [ #  #  #  #  :              0 :     t_bytes = VARSIZE_ANY_EXHDR(dst);
                                     #  #  #  #  #  
                                                 # ]
                                226                 :                : 
 3781 tgl@sss.pgh.pa.us         227                 :              0 :     PG_RETURN_INT32(varstr_levenshtein_less_equal(s_data, s_bytes,
                                228                 :                :                                                   t_data, t_bytes,
                                229                 :                :                                                   ins_c, del_c, sub_c,
                                230                 :                :                                                   max_d, false));
                                231                 :                : }
                                232                 :                : 
                                233                 :                : 
 5702 rhaas@postgresql.org      234                 :CBC           2 : PG_FUNCTION_INFO_V1(levenshtein_less_equal);
                                235                 :                : Datum
                                236                 :              2 : levenshtein_less_equal(PG_FUNCTION_ARGS)
                                237                 :                : {
                                238                 :              2 :     text       *src = PG_GETARG_TEXT_PP(0);
                                239                 :              2 :     text       *dst = PG_GETARG_TEXT_PP(1);
                                240                 :              2 :     int         max_d = PG_GETARG_INT32(2);
                                241                 :                :     const char *s_data;
                                242                 :                :     const char *t_data;
                                243                 :                :     int         s_bytes,
                                244                 :                :                 t_bytes;
                                245                 :                : 
                                246                 :                :     /* Extract a pointer to the actual character data */
 4216                           247         [ -  + ]:              2 :     s_data = VARDATA_ANY(src);
                                248         [ -  + ]:              2 :     t_data = VARDATA_ANY(dst);
                                249                 :                :     /* Determine length of each string in bytes */
                                250   [ -  +  -  -  :              2 :     s_bytes = VARSIZE_ANY_EXHDR(src);
                                     -  -  -  -  -  
                                                 + ]
                                251   [ -  +  -  -  :              2 :     t_bytes = VARSIZE_ANY_EXHDR(dst);
                                     -  -  -  -  -  
                                                 + ]
                                252                 :                : 
 3781 tgl@sss.pgh.pa.us         253                 :              2 :     PG_RETURN_INT32(varstr_levenshtein_less_equal(s_data, s_bytes,
                                254                 :                :                                                   t_data, t_bytes,
                                255                 :                :                                                   1, 1, 1,
                                256                 :                :                                                   max_d, false));
                                257                 :                : }
                                258                 :                : 
                                259                 :                : 
                                260                 :                : /*
                                261                 :                :  * Calculates the metaphone of an input string.
                                262                 :                :  * Returns number of characters requested
                                263                 :                :  * (suggested value is 4)
                                264                 :                :  */
 9062 bruce@momjian.us          265                 :              2 : PG_FUNCTION_INFO_V1(metaphone);
                                266                 :                : Datum
                                267                 :              1 : metaphone(PG_FUNCTION_ARGS)
                                268                 :                : {
 6640 tgl@sss.pgh.pa.us         269                 :              1 :     char       *str_i = TextDatumGetCString(PG_GETARG_DATUM(0));
                                270                 :              1 :     size_t      str_i_len = strlen(str_i);
                                271                 :                :     int         reqlen;
                                272                 :                :     char       *metaph;
                                273                 :                : 
                                274                 :                :     /* return an empty string if we receive one */
 8003 mail@joeconway.com        275         [ -  + ]:              1 :     if (!(str_i_len > 0))
 6640 tgl@sss.pgh.pa.us         276                 :UBC           0 :         PG_RETURN_TEXT_P(cstring_to_text(""));
                                277                 :                : 
 9062 bruce@momjian.us          278         [ -  + ]:CBC           1 :     if (str_i_len > MAX_METAPHONE_STRLEN)
 8346 tgl@sss.pgh.pa.us         279         [ #  # ]:UBC           0 :         ereport(ERROR,
                                280                 :                :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                281                 :                :                  errmsg("argument exceeds the maximum length of %d bytes",
                                282                 :                :                         MAX_METAPHONE_STRLEN)));
                                283                 :                : 
 9062 bruce@momjian.us          284                 :CBC           1 :     reqlen = PG_GETARG_INT32(1);
                                285         [ -  + ]:              1 :     if (reqlen > MAX_METAPHONE_STRLEN)
 8346 tgl@sss.pgh.pa.us         286         [ #  # ]:UBC           0 :         ereport(ERROR,
                                287                 :                :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                288                 :                :                  errmsg("output exceeds the maximum length of %d bytes",
                                289                 :                :                         MAX_METAPHONE_STRLEN)));
                                290                 :                : 
 9062 bruce@momjian.us          291         [ -  + ]:CBC           1 :     if (!(reqlen > 0))
 8346 tgl@sss.pgh.pa.us         292         [ #  # ]:UBC           0 :         ereport(ERROR,
                                293                 :                :                 (errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
                                294                 :                :                  errmsg("output cannot be empty string")));
                                295                 :                : 
 3208 peter_e@gmx.net           296                 :CBC           1 :     _metaphone(str_i, reqlen, &metaph);
                                297                 :              1 :     PG_RETURN_TEXT_P(cstring_to_text(metaph));
                                298                 :                : }
                                299                 :                : 
                                300                 :                : 
                                301                 :                : /*
                                302                 :                :  * Original code by Michael G Schwern starts here.
                                303                 :                :  * Code slightly modified for use as PostgreSQL
                                304                 :                :  * function (palloc, etc).
                                305                 :                :  */
                                306                 :                : 
                                307                 :                : /*
                                308                 :                :  * I suppose I could have been using a character pointer instead of
                                309                 :                :  * accessing the array directly...
                                310                 :                :  */
                                311                 :                : 
                                312                 :                : /* Look at the next letter in the word */
                                313                 :                : #define Next_Letter (pg_ascii_toupper((unsigned char) word[w_idx+1]))
                                314                 :                : /* Look at the current letter in the word */
                                315                 :                : #define Curr_Letter (pg_ascii_toupper((unsigned char) word[w_idx]))
                                316                 :                : /* Go N letters back. */
                                317                 :                : #define Look_Back_Letter(n) \
                                318                 :                :     (w_idx >= (n) ? pg_ascii_toupper((unsigned char) word[w_idx-(n)]) : '\0')
                                319                 :                : /* Previous letter.  I dunno, should this return null on failure? */
                                320                 :                : #define Prev_Letter (Look_Back_Letter(1))
                                321                 :                : /* Look two letters down.  It makes sure you don't walk off the string. */
                                322                 :                : #define After_Next_Letter \
                                323                 :                :     (Next_Letter != '\0' ? pg_ascii_toupper((unsigned char) word[w_idx+2]) : '\0')
                                324                 :                : #define Look_Ahead_Letter(n) pg_ascii_toupper((unsigned char) Lookahead(word+w_idx, n))
                                325                 :                : 
                                326                 :                : 
                                327                 :                : /* Allows us to safely look ahead an arbitrary # of letters */
                                328                 :                : /* I probably could have just used strlen... */
                                329                 :                : static char
 8983 bruce@momjian.us          330                 :UBC           0 : Lookahead(char *word, int how_far)
                                331                 :                : {
                                332                 :              0 :     char        letter_ahead = '\0';    /* null by default */
                                333                 :                :     int         idx;
                                334                 :                : 
                                335   [ #  #  #  # ]:              0 :     for (idx = 0; word[idx] != '\0' && idx < how_far; idx++);
                                336                 :                :     /* Edge forward in the string... */
                                337                 :                : 
 7532                           338                 :              0 :     letter_ahead = word[idx];   /* idx will be either == to how_far or at the
                                339                 :                :                                  * end of the string */
 9062                           340                 :              0 :     return letter_ahead;
                                341                 :                : }
                                342                 :                : 
                                343                 :                : 
                                344                 :                : /* phonize one letter */
                                345                 :                : #define Phonize(c)  do {(*phoned_word)[p_idx++] = c;} while (0)
                                346                 :                : /* Slap a null character on the end of the phoned word */
                                347                 :                : #define End_Phoned_Word do {(*phoned_word)[p_idx] = '\0';} while (0)
                                348                 :                : /* How long is the phoned word? */
                                349                 :                : #define Phone_Len   (p_idx)
                                350                 :                : 
                                351                 :                : /* Note is a letter is a 'break' in the word */
                                352                 :                : #define Isbreak(c)  (!ascii_isalpha((unsigned char) (c)))
                                353                 :                : 
                                354                 :                : 
                                355                 :                : static void
 6197 bruce@momjian.us          356                 :CBC           1 : _metaphone(char *word,          /* IN */
                                357                 :                :            int max_phonemes,
                                358                 :                :            char **phoned_word)  /* OUT */
                                359                 :                : {
 8983                           360                 :              1 :     int         w_idx = 0;      /* point in the phonization we're at. */
                                361                 :              1 :     int         p_idx = 0;      /* end of the phoned phrase */
                                362                 :                : 
                                363                 :                :     /*-- Parameter checks --*/
                                364                 :                : 
                                365                 :                :     /*
                                366                 :                :      * Shouldn't be necessary, but left these here anyway jec Aug 3, 2001
                                367                 :                :      */
                                368                 :                : 
                                369                 :                :     /* Negative phoneme length is meaningless */
 9062                           370         [ -  + ]:              1 :     if (!(max_phonemes > 0))
                                371                 :                :         /* internal error */
 9062 bruce@momjian.us          372         [ #  # ]:UBC           0 :         elog(ERROR, "metaphone: Requested output length must be > 0");
                                373                 :                : 
                                374                 :                :     /* Empty/null string is meaningless */
 9062 bruce@momjian.us          375   [ +  -  -  + ]:CBC           1 :     if ((word == NULL) || !(strlen(word) > 0))
                                376                 :                :         /* internal error */
 9062 bruce@momjian.us          377         [ #  # ]:UBC           0 :         elog(ERROR, "metaphone: Input string length must be > 0");
                                378                 :                : 
                                379                 :                :     /*-- Allocate memory for our phoned_phrase --*/
 8983 bruce@momjian.us          380         [ -  + ]:CBC           1 :     if (max_phonemes == 0)
                                381                 :                :     {                           /* Assume largest possible */
 3265 tgl@sss.pgh.pa.us         382                 :UBC           0 :         *phoned_word = palloc(sizeof(char) * strlen(word) + 1);
                                383                 :                :     }
                                384                 :                :     else
                                385                 :                :     {
 9062 bruce@momjian.us          386                 :CBC           1 :         *phoned_word = palloc(sizeof(char) * max_phonemes + 1);
                                387                 :                :     }
                                388                 :                : 
                                389                 :                :     /*-- The first phoneme has to be processed specially. --*/
                                390                 :                :     /* Find our first letter */
  138 jdavis@postgresql.or      391         [ -  + ]:GNC           1 :     for (; !ascii_isalpha((unsigned char) (Curr_Letter)); w_idx++)
                                392                 :                :     {
                                393                 :                :         /* On the off chance we were given nothing but crap... */
 8983 bruce@momjian.us          394         [ #  # ]:UBC           0 :         if (Curr_Letter == '\0')
                                395                 :                :         {
 8979                           396                 :              0 :             End_Phoned_Word;
 3208 peter_e@gmx.net           397                 :              0 :             return;
                                398                 :                :         }
                                399                 :                :     }
                                400                 :                : 
 8983 bruce@momjian.us          401   [ -  +  -  -  :CBC           1 :     switch (Curr_Letter)
                                              -  - ]
                                402                 :                :     {
                                403                 :                :             /* AE becomes E */
 9062 bruce@momjian.us          404                 :UBC           0 :         case 'A':
 8983                           405         [ #  # ]:              0 :             if (Next_Letter == 'E')
                                406                 :                :             {
 9062                           407                 :              0 :                 Phonize('E');
 8983                           408                 :              0 :                 w_idx += 2;
                                409                 :                :             }
                                410                 :                :             /* Remember, preserve vowels at the beginning */
                                411                 :                :             else
                                412                 :                :             {
 9062                           413                 :              0 :                 Phonize('A');
                                414                 :              0 :                 w_idx++;
                                415                 :                :             }
                                416                 :              0 :             break;
                                417                 :                :             /* [GKP]N becomes N */
 9062 bruce@momjian.us          418                 :CBC           1 :         case 'G':
                                419                 :                :         case 'K':
                                420                 :                :         case 'P':
 8983                           421         [ -  + ]:              1 :             if (Next_Letter == 'N')
                                422                 :                :             {
 9062 bruce@momjian.us          423                 :UBC           0 :                 Phonize('N');
 8983                           424                 :              0 :                 w_idx += 2;
                                425                 :                :             }
 9062 bruce@momjian.us          426                 :CBC           1 :             break;
                                427                 :                : 
                                428                 :                :             /*
                                429                 :                :              * WH becomes H, WR becomes R W if followed by a vowel
                                430                 :                :              */
 9062 bruce@momjian.us          431                 :UBC           0 :         case 'W':
 8983                           432         [ #  # ]:              0 :             if (Next_Letter == 'H' ||
                                      [ #  #  #  # ]
                                433         [ #  # ]:              0 :                 Next_Letter == 'R')
                                434                 :                :             {
                                435                 :              0 :                 Phonize(Next_Letter);
                                436                 :              0 :                 w_idx += 2;
                                437                 :                :             }
                                438         [ #  # ]:              0 :             else if (isvowel(Next_Letter))
                                439                 :                :             {
                                440                 :              0 :                 Phonize('W');
                                441                 :              0 :                 w_idx += 2;
                                442                 :                :             }
                                443                 :                :             /* else ignore */
 9062                           444                 :              0 :             break;
                                445                 :                :             /* X becomes S */
                                446                 :              0 :         case 'X':
                                447                 :              0 :             Phonize('S');
                                448                 :              0 :             w_idx++;
                                449                 :              0 :             break;
                                450                 :                :             /* Vowels are kept */
                                451                 :                : 
                                452                 :                :             /*
                                453                 :                :              * We did A already case 'A': case 'a':
                                454                 :                :              */
                                455                 :              0 :         case 'E':
                                456                 :                :         case 'I':
                                457                 :                :         case 'O':
                                458                 :                :         case 'U':
                                459                 :              0 :             Phonize(Curr_Letter);
                                460                 :              0 :             w_idx++;
                                461                 :              0 :             break;
                                462                 :              0 :         default:
                                463                 :                :             /* do nothing */
                                464                 :              0 :             break;
                                465                 :                :     }
                                466                 :                : 
                                467                 :                : 
                                468                 :                : 
                                469                 :                :     /* On to the metaphoning */
 8983 bruce@momjian.us          470   [ +  +  -  + ]:CBC           6 :     for (; Curr_Letter != '\0' &&
                                471         [ +  - ]:              5 :          (max_phonemes == 0 || Phone_Len < max_phonemes);
                                472                 :              5 :          w_idx++)
                                473                 :                :     {
                                474                 :                :         /*
                                475                 :                :          * How many letters to skip because an earlier encoding handled
                                476                 :                :          * multiple letters
                                477                 :                :          */
                                478                 :              5 :         unsigned short int skip_letter = 0;
                                479                 :                : 
                                480                 :                : 
                                481                 :                :         /*
                                482                 :                :          * THOUGHT:  It would be nice if, rather than having things like...
                                483                 :                :          * well, SCI.  For SCI you encode the S, then have to remember to skip
                                484                 :                :          * the C.  So the phonome SCI invades both S and C.  It would be
                                485                 :                :          * better, IMHO, to skip the C from the S part of the encoding. Hell,
                                486                 :                :          * I'm trying it.
                                487                 :                :          */
                                488                 :                : 
                                489                 :                :         /* Ignore non-alphas */
  138 jdavis@postgresql.or      490         [ -  + ]:GNC           5 :         if (!ascii_isalpha((unsigned char) (Curr_Letter)))
 9062 bruce@momjian.us          491                 :UBC           0 :             continue;
                                492                 :                : 
                                493                 :                :         /* Drop duplicates, except CC */
 8983 bruce@momjian.us          494   [ +  +  -  + ]:CBC           5 :         if (Curr_Letter == Prev_Letter &&
                                      [ +  +  -  +  
                                              -  - ]
 8983 bruce@momjian.us          495         [ #  # ]:UBC           0 :             Curr_Letter != 'C')
 9062                           496                 :              0 :             continue;
                                497                 :                : 
 8983 bruce@momjian.us          498                 :CBC           5 :         switch (Curr_Letter)
                                      [ +  -  -  +  
                                     -  -  -  -  -  
                                     -  -  -  -  -  
                                           -  +  + ]
                                499                 :                :         {
                                500                 :                :                 /* B -> B unless in MB */
 9062                           501                 :              1 :             case 'B':
 8983                           502   [ +  -  -  + ]:              1 :                 if (Prev_Letter != 'M')
 9062 bruce@momjian.us          503                 :UBC           0 :                     Phonize('B');
 9062 bruce@momjian.us          504                 :CBC           1 :                 break;
                                505                 :                : 
                                506                 :                :                 /*
                                507                 :                :                  * 'sh' if -CIA- or -CH, but not SCH, except SCHW. (SCHW is
                                508                 :                :                  * handled in S) S if -CI-, -CE- or -CY- dropped if -SCI-,
                                509                 :                :                  * SCE-, -SCY- (handed in S) else K
                                510                 :                :                  */
 9062 bruce@momjian.us          511                 :UBC           0 :             case 'C':
 8983                           512         [ #  # ]:              0 :                 if (MAKESOFT(Next_Letter))
                                513                 :                :                 {               /* C[IEY] */
                                514   [ #  #  #  # ]:              0 :                     if (After_Next_Letter == 'A' &&
                                      [ #  #  #  #  
                                              #  # ]
                                515         [ #  # ]:              0 :                         Next_Letter == 'I')
                                516                 :                :                     {           /* CIA */
 9062                           517                 :              0 :                         Phonize(SH);
                                518                 :                :                     }
                                519                 :                :                     /* SC[IEY] */
 8983                           520   [ #  #  #  # ]:              0 :                     else if (Prev_Letter == 'S')
                                521                 :                :                     {
                                522                 :                :                         /* Dropped */
                                523                 :                :                     }
                                524                 :                :                     else
                                525                 :              0 :                         Phonize('S');
                                526                 :                :                 }
                                527         [ #  # ]:              0 :                 else if (Next_Letter == 'H')
                                528                 :                :                 {
                                529                 :                : #ifndef USE_TRADITIONAL_METAPHONE
                                530   [ #  #  #  #  :              0 :                     if (After_Next_Letter == 'R' ||
                                              #  # ]
                                531         [ #  # ]:              0 :                         Prev_Letter == 'S')
                                532                 :                :                     {           /* Christ, School */
 9062                           533                 :              0 :                         Phonize('K');
                                534                 :                :                     }
                                535                 :                :                     else
                                536                 :              0 :                         Phonize(SH);
                                537                 :                : #else
                                538                 :                :                     Phonize(SH);
                                539                 :                : #endif
                                540                 :              0 :                     skip_letter++;
                                541                 :                :                 }
                                542                 :                :                 else
                                543                 :              0 :                     Phonize('K');
                                544                 :              0 :                 break;
                                545                 :                : 
                                546                 :                :                 /*
                                547                 :                :                  * J if in -DGE-, -DGI- or -DGY- else T
                                548                 :                :                  */
                                549                 :              0 :             case 'D':
 8983                           550         [ #  # ]:              0 :                 if (Next_Letter == 'G' &&
                                551   [ #  #  #  # ]:              0 :                     MAKESOFT(After_Next_Letter))
                                552                 :                :                 {
 9062                           553                 :              0 :                     Phonize('J');
                                554                 :              0 :                     skip_letter++;
                                555                 :                :                 }
                                556                 :                :                 else
                                557                 :              0 :                     Phonize('T');
                                558                 :              0 :                 break;
                                559                 :                : 
                                560                 :                :                 /*
                                561                 :                :                  * F if in -GH and not B--GH, D--GH, -H--GH, -H---GH else
                                562                 :                :                  * dropped if -GNED, -GN, else dropped if -DGE-, -DGI- or
                                563                 :                :                  * -DGY- (handled in D) else J if in -GE-, -GI, -GY and not GG
                                564                 :                :                  * else K
                                565                 :                :                  */
 9062 bruce@momjian.us          566                 :CBC           1 :             case 'G':
 8983                           567         [ -  + ]:              1 :                 if (Next_Letter == 'H')
                                568                 :                :                 {
 8983 bruce@momjian.us          569   [ #  #  #  #  :UBC           0 :                     if (!(NOGHTOF(Look_Back_Letter(3)) ||
                                              #  # ]
                                570         [ #  # ]:              0 :                           Look_Back_Letter(4) == 'H'))
                                571                 :                :                     {
 9062                           572                 :              0 :                         Phonize('F');
                                573                 :              0 :                         skip_letter++;
                                574                 :                :                     }
                                575                 :                :                     else
                                576                 :                :                     {
                                577                 :                :                         /* silent */
                                578                 :                :                     }
                                579                 :                :                 }
 8983 bruce@momjian.us          580         [ -  + ]:CBC           1 :                 else if (Next_Letter == 'N')
                                581                 :                :                 {
 8983 bruce@momjian.us          582   [ #  #  #  # ]:UBC           0 :                     if (Isbreak(After_Next_Letter) ||
                                      [ #  #  #  #  
                                              #  # ]
                                583   [ #  #  #  # ]:              0 :                         (After_Next_Letter == 'E' &&
                                584         [ #  # ]:              0 :                          Look_Ahead_Letter(3) == 'D'))
                                585                 :                :                     {
                                586                 :                :                         /* dropped */
                                587                 :                :                     }
                                588                 :                :                     else
 9062                           589                 :              0 :                         Phonize('K');
                                590                 :                :                 }
 8983 bruce@momjian.us          591   [ -  +  -  - ]:CBC           1 :                 else if (MAKESOFT(Next_Letter) &&
 8983 bruce@momjian.us          592         [ #  # ]:UBC           0 :                          Prev_Letter != 'G')
 9062                           593                 :              0 :                     Phonize('J');
                                594                 :                :                 else
 9062 bruce@momjian.us          595                 :CBC           1 :                     Phonize('K');
                                596                 :              1 :                 break;
                                597                 :                :                 /* H if before a vowel and not after C,G,P,S,T */
 9062 bruce@momjian.us          598                 :UBC           0 :             case 'H':
 8983                           599         [ #  # ]:              0 :                 if (isvowel(Next_Letter) &&
                                600   [ #  #  #  # ]:              0 :                     !AFFECTH(Prev_Letter))
 9062                           601                 :              0 :                     Phonize('H');
                                602                 :              0 :                 break;
                                603                 :                : 
                                604                 :                :                 /*
                                605                 :                :                  * dropped if after C else K
                                606                 :                :                  */
                                607                 :              0 :             case 'K':
 8983                           608   [ #  #  #  # ]:              0 :                 if (Prev_Letter != 'C')
 9062                           609                 :              0 :                     Phonize('K');
                                610                 :              0 :                 break;
                                611                 :                : 
                                612                 :                :                 /*
                                613                 :                :                  * F if before H else P
                                614                 :                :                  */
                                615                 :              0 :             case 'P':
 8983                           616         [ #  # ]:              0 :                 if (Next_Letter == 'H')
 9062                           617                 :              0 :                     Phonize('F');
                                618                 :                :                 else
                                619                 :              0 :                     Phonize('P');
                                620                 :              0 :                 break;
                                621                 :                : 
                                622                 :                :                 /*
                                623                 :                :                  * K
                                624                 :                :                  */
                                625                 :              0 :             case 'Q':
                                626                 :              0 :                 Phonize('K');
                                627                 :              0 :                 break;
                                628                 :                : 
                                629                 :                :                 /*
                                630                 :                :                  * 'sh' in -SH-, -SIO- or -SIA- or -SCHW- else S
                                631                 :                :                  */
                                632                 :              0 :             case 'S':
 8983                           633         [ #  # ]:              0 :                 if (Next_Letter == 'I' &&
                                      [ #  #  #  # ]
                                634   [ #  #  #  # ]:              0 :                     (After_Next_Letter == 'O' ||
                                635         [ #  # ]:              0 :                      After_Next_Letter == 'A'))
                                      [ #  #  #  # ]
 9062                           636                 :              0 :                     Phonize(SH);
 8983                           637         [ #  # ]:              0 :                 else if (Next_Letter == 'H')
                                638                 :                :                 {
 9062                           639                 :              0 :                     Phonize(SH);
                                640                 :              0 :                     skip_letter++;
                                641                 :                :                 }
                                642                 :                : #ifndef USE_TRADITIONAL_METAPHONE
 8983                           643         [ #  # ]:              0 :                 else if (Next_Letter == 'C' &&
                                      [ #  #  #  # ]
                                644         [ #  # ]:              0 :                          Look_Ahead_Letter(2) == 'H' &&
                                645         [ #  # ]:              0 :                          Look_Ahead_Letter(3) == 'W')
                                646                 :                :                 {
 9062                           647                 :              0 :                     Phonize(SH);
                                648                 :              0 :                     skip_letter += 2;
                                649                 :                :                 }
                                650                 :                : #endif
                                651                 :                :                 else
                                652                 :              0 :                     Phonize('S');
                                653                 :              0 :                 break;
                                654                 :                : 
                                655                 :                :                 /*
                                656                 :                :                  * 'sh' in -TIA- or -TIO- else 'th' before H else T
                                657                 :                :                  */
                                658                 :              0 :             case 'T':
 8983                           659         [ #  # ]:              0 :                 if (Next_Letter == 'I' &&
                                      [ #  #  #  # ]
                                660   [ #  #  #  # ]:              0 :                     (After_Next_Letter == 'O' ||
                                661         [ #  # ]:              0 :                      After_Next_Letter == 'A'))
                                      [ #  #  #  # ]
 9062                           662                 :              0 :                     Phonize(SH);
 8983                           663         [ #  # ]:              0 :                 else if (Next_Letter == 'H')
                                664                 :                :                 {
 9062                           665                 :              0 :                     Phonize(TH);
                                666                 :              0 :                     skip_letter++;
                                667                 :                :                 }
                                668                 :                :                 else
                                669                 :              0 :                     Phonize('T');
                                670                 :              0 :                 break;
                                671                 :                :                 /* F */
                                672                 :              0 :             case 'V':
                                673                 :              0 :                 Phonize('F');
                                674                 :              0 :                 break;
                                675                 :                :                 /* W before a vowel, else dropped */
                                676                 :              0 :             case 'W':
 8983                           677         [ #  # ]:              0 :                 if (isvowel(Next_Letter))
 9062                           678                 :              0 :                     Phonize('W');
                                679                 :              0 :                 break;
                                680                 :                :                 /* KS */
                                681                 :              0 :             case 'X':
                                682                 :              0 :                 Phonize('K');
 8376                           683   [ #  #  #  # ]:              0 :                 if (max_phonemes == 0 || Phone_Len < max_phonemes)
                                684                 :              0 :                     Phonize('S');
 9062                           685                 :              0 :                 break;
                                686                 :                :                 /* Y if followed by a vowel */
                                687                 :              0 :             case 'Y':
 8983                           688         [ #  # ]:              0 :                 if (isvowel(Next_Letter))
 9062                           689                 :              0 :                     Phonize('Y');
                                690                 :              0 :                 break;
                                691                 :                :                 /* S */
                                692                 :              0 :             case 'Z':
                                693                 :              0 :                 Phonize('S');
                                694                 :              0 :                 break;
                                695                 :                :                 /* No transformation */
 9062 bruce@momjian.us          696                 :CBC           1 :             case 'F':
                                697                 :                :             case 'J':
                                698                 :                :             case 'L':
                                699                 :                :             case 'M':
                                700                 :                :             case 'N':
                                701                 :                :             case 'R':
                                702                 :              1 :                 Phonize(Curr_Letter);
                                703                 :              1 :                 break;
                                704                 :              2 :             default:
                                705                 :                :                 /* nothing */
                                706                 :              2 :                 break;
                                707                 :                :         }                       /* END SWITCH */
                                708                 :                : 
                                709                 :              5 :         w_idx += skip_letter;
                                710                 :                :     }                           /* END FOR */
                                711                 :                : 
                                712                 :              1 :     End_Phoned_Word;
                                713                 :                : }                               /* END metaphone */
                                714                 :                : 
                                715                 :                : 
                                716                 :                : /*
                                717                 :                :  * SQL function: soundex(text) returns text
                                718                 :                :  */
                                719                 :              3 : PG_FUNCTION_INFO_V1(soundex);
                                720                 :                : 
                                721                 :                : Datum
                                722                 :              8 : soundex(PG_FUNCTION_ARGS)
                                723                 :                : {
                                724                 :                :     char        outstr[SOUNDEX_LEN + 1];
                                725                 :                :     char       *arg;
                                726                 :                : 
 3366 noah@leadboat.com         727                 :              8 :     arg = text_to_cstring(PG_GETARG_TEXT_PP(0));
                                728                 :                : 
 9062 bruce@momjian.us          729                 :              8 :     _soundex(arg, outstr);
                                730                 :                : 
 6640 tgl@sss.pgh.pa.us         731                 :              8 :     PG_RETURN_TEXT_P(cstring_to_text(outstr));
                                732                 :                : }
                                733                 :                : 
                                734                 :                : static void
 9062 bruce@momjian.us          735                 :             16 : _soundex(const char *instr, char *outstr)
                                736                 :                : {
                                737                 :                :     int         count;
                                738                 :                : 
 1310 peter@eisentraut.org      739         [ -  + ]:             16 :     Assert(instr);
                                740         [ -  + ]:             16 :     Assert(outstr);
                                741                 :                : 
                                742                 :                :     /* Skip leading non-alphabetic characters */
  138 jdavis@postgresql.or      743   [ +  +  -  + ]:GNC          16 :     while (*instr && !ascii_isalpha((unsigned char) *instr))
 9062 bruce@momjian.us          744                 :UBC           0 :         ++instr;
                                745                 :                : 
                                746                 :                :     /* If no string left, return all-zeroes buffer */
 1110 tgl@sss.pgh.pa.us         747         [ +  + ]:CBC          16 :     if (!*instr)
                                748                 :                :     {
                                749                 :              3 :         memset(outstr, '\0', SOUNDEX_LEN + 1);
 9062 bruce@momjian.us          750                 :              3 :         return;
                                751                 :                :     }
                                752                 :                : 
                                753                 :                :     /* Take the first letter as is */
  138 jdavis@postgresql.or      754                 :GNC          13 :     *outstr++ = (char) pg_ascii_toupper((unsigned char) *instr++);
                                755                 :                : 
 9062 bruce@momjian.us          756                 :CBC          13 :     count = 1;
                                757   [ +  +  +  + ]:             60 :     while (*instr && count < SOUNDEX_LEN)
                                758                 :                :     {
  138 jdavis@postgresql.or      759   [ +  +  +  + ]:GNC          93 :         if (ascii_isalpha((unsigned char) *instr) &&
 9062 bruce@momjian.us          760                 :CBC          46 :             soundex_code(*instr) != soundex_code(*(instr - 1)))
                                761                 :                :         {
 1110 tgl@sss.pgh.pa.us         762                 :             35 :             *outstr = soundex_code(*instr);
 9062 bruce@momjian.us          763         [ +  + ]:             35 :             if (*outstr != '0')
                                764                 :                :             {
                                765                 :             23 :                 ++outstr;
                                766                 :             23 :                 ++count;
                                767                 :                :             }
                                768                 :                :         }
                                769                 :             47 :         ++instr;
                                770                 :                :     }
                                771                 :                : 
                                772                 :                :     /* Fill with 0's */
                                773         [ +  + ]:             29 :     while (count < SOUNDEX_LEN)
                                774                 :                :     {
                                775                 :             16 :         *outstr = '0';
                                776                 :             16 :         ++outstr;
                                777                 :             16 :         ++count;
                                778                 :                :     }
                                779                 :                : 
                                780                 :                :     /* And null-terminate */
 1110 tgl@sss.pgh.pa.us         781                 :             13 :     *outstr = '\0';
                                782                 :                : }
                                783                 :                : 
 7794 neilc@samurai.com         784                 :              2 : PG_FUNCTION_INFO_V1(difference);
                                785                 :                : 
                                786                 :                : Datum
                                787                 :              4 : difference(PG_FUNCTION_ARGS)
                                788                 :                : {
                                789                 :                :     char        sndx1[SOUNDEX_LEN + 1],
                                790                 :                :                 sndx2[SOUNDEX_LEN + 1];
                                791                 :                :     int         i,
                                792                 :                :                 result;
                                793                 :                : 
 3366 noah@leadboat.com         794                 :              4 :     _soundex(text_to_cstring(PG_GETARG_TEXT_PP(0)), sndx1);
                                795                 :              4 :     _soundex(text_to_cstring(PG_GETARG_TEXT_PP(1)), sndx2);
                                796                 :                : 
 7794 neilc@samurai.com         797                 :              4 :     result = 0;
 7532 bruce@momjian.us          798         [ +  + ]:             20 :     for (i = 0; i < SOUNDEX_LEN; i++)
                                799                 :                :     {
 7794 neilc@samurai.com         800         [ +  + ]:             16 :         if (sndx1[i] == sndx2[i])
                                801                 :             10 :             result++;
                                802                 :                :     }
                                803                 :                : 
                                804                 :              4 :     PG_RETURN_INT32(result);
                                805                 :                : }
        

Generated by: LCOV version 2.5.0-beta