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