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