Hello,

Please find attached a patch for the daitch_mokotoff module.

This implements the Daitch-Mokotoff Soundex System, as described in
https://www.avotaynu.com/soundex.htm

The module is used in production at Finance Norway.

In order to verify correctness, I have compared generated soundex codes
with corresponding results from the implementation by Stephen P. Morse
at https://stevemorse.org/census/soundex.html

Where soundex codes differ, the daitch_mokotoff module has been found
to be correct. The Morse implementation uses a few unofficial rules,
and also has an error in the handling of adjacent identical code
digits. Please see daitch_mokotoff.c for further references and
comments.

For reference, detailed instructions for soundex code comparison are
attached.


Best regards

Dag Lem

diff --git a/contrib/Makefile b/contrib/Makefile
index 87bf87ab90..5e1111a729 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -14,6 +14,7 @@ SUBDIRS = \
 		btree_gist	\
 		citext		\
 		cube		\
+		daitch_mokotoff	\
 		dblink		\
 		dict_int	\
 		dict_xsyn	\
diff --git a/contrib/daitch_mokotoff/Makefile b/contrib/daitch_mokotoff/Makefile
new file mode 100644
index 0000000000..baec5e31d4
--- /dev/null
+++ b/contrib/daitch_mokotoff/Makefile
@@ -0,0 +1,25 @@
+# contrib/daitch_mokotoff/Makefile
+
+MODULE_big = daitch_mokotoff
+OBJS = \
+	$(WIN32RES) \
+	daitch_mokotoff.o
+
+EXTENSION = daitch_mokotoff
+DATA = daitch_mokotoff--1.0.sql
+PGFILEDESC = "daitch_mokotoff - Daitch-Mokotoff Soundex System"
+
+HEADERS = daitch_mokotoff.h
+
+REGRESS = daitch_mokotoff
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/daitch_mokotoff
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/daitch_mokotoff/daitch_mokotoff--1.0.sql b/contrib/daitch_mokotoff/daitch_mokotoff--1.0.sql
new file mode 100644
index 0000000000..0b5a643175
--- /dev/null
+++ b/contrib/daitch_mokotoff/daitch_mokotoff--1.0.sql
@@ -0,0 +1,8 @@
+/* contrib/daitch_mokotoff/daitch_mokotoff--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION daitch_mokotoff" to load this file. \quit
+
+CREATE FUNCTION daitch_mokotoff(text) RETURNS text
+AS 'MODULE_PATHNAME', 'daitch_mokotoff'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
diff --git a/contrib/daitch_mokotoff/daitch_mokotoff.c b/contrib/daitch_mokotoff/daitch_mokotoff.c
new file mode 100644
index 0000000000..9e66aee434
--- /dev/null
+++ b/contrib/daitch_mokotoff/daitch_mokotoff.c
@@ -0,0 +1,551 @@
+/*
+ * Daitch-Mokotoff Soundex
+ *
+ * Copyright (c) 2021 Finance Norway
+ * Author: Dag Lem <d...@nimrod.no>
+ *
+ * This implementation of the Daitch-Mokotoff Soundex System aims at high
+ * performance.
+ *
+ * - The processing of each phoneme is initiated by an O(1) table lookup.
+ * - For phonemes containing more than one character, a coding tree is traversed
+ *   to process the complete phoneme.
+ * - The (alternate) soundex codes are produced digit by digit in-place in
+ *   another tree structure.
+ *
+ *  References:
+ *
+ * https://www.avotaynu.com/soundex.htm
+ * https://www.jewishgen.org/InfoFiles/Soundex.html
+ * https://familypedia.fandom.com/wiki/Daitch-Mokotoff_Soundex
+ * https://stevemorse.org/phoneticinfo.htm (dmlat.php, dmsoundex.php)
+ * https://github.com/apache/commons-codec/ (dmrules.txt, DaitchMokotoffSoundex.java)
+ * https://metacpan.org/pod/Text::Phonetic (DaitchMokotoff.pm)
+ *
+ * A few notes on other implementations:
+ *
+ * - "J" is considered a vowel in dmlat.php
+ * - The official rules for "RS" are commented out in dmlat.php
+ * - Adjacent identical code digits are not collapsed correctly in dmsoundex.php
+ *   when double digit codes are involved. E.g. "BESST" yields 744300 instead of
+ *   743000 as for "BEST".
+ * - "Y" is not considered a vowel in DaitchMokotoffSoundex.java
+ * - Both dmlat.php and dmrules.txt have the same unofficial rules for "UE".
+ * - Coding of MN/NM + M/N differs between dmsoundex.php and DaitchMokotoffSoundex.java
+ * - Neither dmsoundex.php nor DaitchMokotoffSoundex.java yields all valid codes for e.g.
+ *   "CJC" (550000 540000 545000 450000 400000 440000).
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without a written agreement
+ * is hereby granted, provided that the above copyright notice and this
+ * paragraph and the following two paragraphs appear in all copies.
+ *
+ * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+*/
+
+#include "daitch_mokotoff.h"
+
+#ifndef DM_MAIN
+
+#include "postgres.h"
+#include "utils/builtins.h"
+#include "mb/pg_wchar.h"
+
+#else							/* DM_MAIN */
+
+#include <stdio.h>
+
+#endif							/* DM_MAIN */
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/* Internal C implementation */
+static char *_daitch_mokotoff(char *word, char *soundex, size_t n);
+
+
+#ifndef DM_MAIN
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(daitch_mokotoff);
+Datum
+daitch_mokotoff(PG_FUNCTION_ARGS)
+{
+	text	   *arg = PG_GETARG_TEXT_PP(0);
+	char	   *string,
+			   *tmp_soundex;
+	text	   *soundex;
+
+	/*
+	 * The maximum theoretical soundex size is several KB, however in practice
+	 * anything but contrived synthetic inputs will yield a soundex size of
+	 * less than 100 bytes. We thus allocate and free a temporary work buffer,
+	 * and return only the actual soundex result.
+	 */
+	string = pg_server_to_any(text_to_cstring(arg), VARSIZE_ANY_EXHDR(arg), PG_UTF8);
+	tmp_soundex = palloc(DM_MAX_SOUNDEX_CHARS);
+
+	if (!(tmp_soundex && _daitch_mokotoff(string, tmp_soundex, DM_MAX_SOUNDEX_CHARS)))
+	{
+		pfree(tmp_soundex);
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("unable to allocate temporary memory for soundex")));
+	}
+
+	soundex = cstring_to_text(pg_any_to_server(tmp_soundex, strlen(tmp_soundex), PG_UTF8));
+	pfree(tmp_soundex);
+
+	PG_RETURN_TEXT_P(soundex);
+}
+
+#endif							/* DM_MAIN */
+
+
+typedef dm_node dm_nodes[DM_MAX_NODES];
+typedef dm_node * dm_leaves[DM_MAX_LEAVES];
+
+
+static const dm_node start_node = {
+	.soundex_length = 0,
+	.soundex = "000000 ",		/* Six digits + joining space */
+	.is_leaf = 0,
+	.last_update = 0,
+	.code_digit = '\0',
+	.prev_code_digits = {'\0', '\0'},
+	.next_code_digits = {'\0', '\0'},
+	.next_nodes = {NULL}
+};
+
+
+static void
+initialize_node(dm_node * node, int last_update)
+{
+	if (node->last_update < last_update)
+	{
+		node->prev_code_digits[0] = node->next_code_digits[0];
+		node->prev_code_digits[1] = node->next_code_digits[1];
+		node->next_code_digits[0] = '\0';
+		node->next_code_digits[1] = '\0';
+		node->is_leaf = 0;
+		node->last_update = last_update;
+	}
+}
+
+
+static void
+add_next_code_digit(dm_node * node, char code_digit)
+{
+	if (!node->next_code_digits[0])
+	{
+		node->next_code_digits[0] = code_digit;
+	}
+	else if (node->next_code_digits[0] != code_digit)
+	{
+		node->next_code_digits[1] = code_digit;
+	}
+}
+
+
+static void
+set_leaf(dm_leaves leaves_next, int *num_leaves_next, dm_node * node)
+{
+	if (!node->is_leaf)
+	{
+		node->is_leaf = 1;
+		leaves_next[(*num_leaves_next)++] = node;
+	}
+}
+
+
+static dm_node * find_or_create_node(dm_nodes nodes, int *num_nodes, dm_node * node, char code_digit)
+{
+	dm_node   **next_nodes;
+	dm_node    *next_node;
+
+	for (next_nodes = node->next_nodes; (next_node = *next_nodes); next_nodes++)
+	{
+		if (next_node->code_digit == code_digit)
+		{
+			return next_node;
+		}
+	}
+
+	next_node = &nodes[(*num_nodes)++];
+	*next_nodes = next_node;
+
+	*next_node = start_node;
+	memcpy(next_node->soundex, node->soundex, sizeof(next_node->soundex));
+	next_node->soundex_length = node->soundex_length;
+	next_node->soundex[next_node->soundex_length++] = code_digit;
+	next_node->code_digit = code_digit;
+
+	return next_node;
+}
+
+
+static int
+update_node(dm_nodes nodes, dm_node * node, int *num_nodes, dm_leaves leaves_next, int *num_leaves_next, int char_number, char next_code_digit, char next_code_digit_2)
+{
+	int			i;
+	int			num_dirty_nodes = 0;
+	dm_node    *dirty_nodes[2];
+
+	initialize_node(node, char_number);
+
+	if (node->soundex_length == DM_MAX_CODE_DIGITS)
+	{
+		/* Keep completed soundex code. */
+		set_leaf(leaves_next, num_leaves_next, node);
+		return 0;
+	}
+
+	if (next_code_digit == 'X' ||
+		node->prev_code_digits[0] == next_code_digit ||
+		node->prev_code_digits[1] == next_code_digit)
+	{
+		/* The code digit is the same as one of the previous (i.e. not added). */
+		dirty_nodes[num_dirty_nodes++] = node;
+	}
+
+	if (next_code_digit != 'X' &&
+		(node->prev_code_digits[0] != next_code_digit ||
+		 node->prev_code_digits[1]))
+	{
+		/* The code digit is different from one of the previous (i.e. added). */
+		node = find_or_create_node(nodes, num_nodes, node, next_code_digit);
+		initialize_node(node, char_number);
+		dirty_nodes[num_dirty_nodes++] = node;
+	}
+
+	for (i = 0; i < num_dirty_nodes; i++)
+	{
+		/* Add code digit leading to the current node. */
+		add_next_code_digit(dirty_nodes[i], next_code_digit);
+
+		if (next_code_digit_2)
+		{
+			update_node(nodes, dirty_nodes[i], num_nodes, leaves_next, num_leaves_next, char_number, next_code_digit_2, '\0');
+		}
+		else
+		{
+			set_leaf(leaves_next, num_leaves_next, dirty_nodes[i]);
+		}
+	}
+
+	return 1;
+}
+
+
+static int
+update_leaves(dm_nodes nodes, int *num_nodes, dm_leaves leaves[2], int *ix_leaves, int *num_leaves, int char_number, dm_codes codes)
+{
+	int			i,
+				j;
+	char	   *code;
+	int			num_leaves_next = 0;
+	int			ix_leaves_next = (*ix_leaves + 1) % 2;
+	int			finished = 1;
+
+	for (i = 0; i < *num_leaves; i++)
+	{
+		dm_node    *node = leaves[*ix_leaves][i];
+
+		/* One or two alternate code sequences. */
+		for (j = 0; j < 2 && (code = codes[j]) && code[0]; j++)
+		{
+			/* One or two sequential code digits. */
+			if (update_node(nodes, node, num_nodes, leaves[ix_leaves_next], &num_leaves_next, char_number, code[0], code[1]))
+			{
+				finished = 0;
+			}
+		}
+	}
+
+	*ix_leaves = ix_leaves_next;
+	*num_leaves = num_leaves_next;
+
+	return finished;
+}
+
+
+static const char tr_accents_iso8859_1[] =
+/*
+"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
+*/
+"AAAAAAECEEEEIIIIDNOOOOO*OUUUUYDsaaaaaaeceeeeiiiidnooooo/ouuuuydy";
+
+static char
+unaccent_iso8859_1(unsigned char c)
+{
+	return c >= 192 ? tr_accents_iso8859_1[c - 192] : c;
+}
+
+
+/* Convert an UTF-8 character to ISO-8859-1.
+ * Unconvertable characters are returned as '?'.
+ * NB! Beware of the domain specific conversion of Ą, Ę, and Ţ/Ț.
+ */
+static char
+utf8_to_iso8859_1(char *str, int *len)
+{
+	const char	unknown = '?';
+	unsigned char c;
+	unsigned int code_point;
+
+	*len = 1;
+	c = (unsigned char) str[0];
+	if (c < 0x80)
+	{
+		/* ASCII code point. */
+		return c;
+	}
+	else if (c < 0xE0)
+	{
+		/* Two-byte character. */
+		if (!str[1])
+		{
+			/* The UTF-8 character is cut short (invalid code point). */
+			return unknown;
+		}
+		*len = 2;
+		code_point = ((c & 0x1F) << 6) | (str[1] & 0x3F);
+		if (code_point < 0x100)
+		{
+			/* ISO-8859-1 code point. */
+			return code_point;
+		}
+		else if (code_point == 0x0104 || code_point == 0x0105)
+		{
+			/* Ą/ą */
+			return '[';
+		}
+		else if (code_point == 0x0118 || code_point == 0x0119)
+		{
+			/* Ę/ę */
+			return '\\';
+		}
+		else if (code_point == 0x0162 || code_point == 0x0163 ||
+				 code_point == 0x021A || code_point == 0x021B)
+		{
+			/* Ţ/ţ or Ț/ț */
+			return ']';
+		}
+		else
+		{
+			return unknown;
+		}
+	}
+	else if (c < 0xF0)
+	{
+		/* Three-byte character. */
+		if (!str[2])
+		{
+			/* The UTF-8 character is cut short (invalid code point). */
+			*len = 2;
+		}
+		else
+		{
+			*len = 3;
+		}
+		return unknown;
+	}
+	else
+	{
+		/* Four-byte character. */
+		if (!str[3])
+		{
+			/* The UTF-8 character is cut short (invalid code point). */
+			*len = 3;
+		}
+		else
+		{
+			*len = 4;
+		}
+		return unknown;
+	}
+}
+
+
+static char
+read_char(char *str, int *len)
+{
+	return toupper(unaccent_iso8859_1(utf8_to_iso8859_1(str, len)));
+}
+
+
+static char
+read_valid_char(char *str, int *len)
+{
+	int			c;
+	int			i,
+				ilen;
+
+	for (i = 0, ilen = 0; (c = read_char(&str[i], &ilen)) && (c < 'A' || c > ']'); i += ilen)
+	{
+	}
+
+	*len = i + ilen;
+	return c;
+}
+
+
+static char *
+_daitch_mokotoff(char *word, char *soundex, size_t n)
+{
+	int			c,
+				cmp;
+	int			i,
+				ilen,
+				i_ok,
+				j,
+				jlen,
+				k;
+	int			first_letter = 1;
+	int			ix_leaves = 0;
+	int			num_nodes = 0,
+				num_leaves = 0;
+	dm_letter  *letter,
+			   *letters;
+	dm_codes   *codes_ok,
+				codes;
+
+	dm_node    *nodes = malloc(sizeof(dm_nodes));
+	dm_leaves  *leaves = malloc(2 * sizeof(dm_leaves));
+
+	if (!nodes || !leaves)
+	{
+		/* Out of memory - clean up and return. */
+		free(leaves);
+		free(nodes);
+		*soundex = '\0';
+		return NULL;
+	}
+
+	/* Starting point. */
+	nodes[num_nodes++] = start_node;
+	leaves[ix_leaves][num_leaves++] = &nodes[0];
+
+	for (i = 0; (c = read_valid_char(&word[i], &ilen)); i += ilen)
+	{
+		/* First letter in sequence. */
+		letter = &letter_[c - 'A'];
+		codes_ok = letter->codes;
+		i_ok = i;
+
+		/* Subsequent letters. */
+		for (j = i + ilen; (letters = letter->letters) && (c = read_valid_char(&word[j], &jlen)); j += jlen)
+		{
+			for (k = 0; (cmp = letters[k].letter); k++)
+			{
+				if (cmp == c)
+				{
+					/* Coding for letter found. */
+					break;
+				}
+			}
+			if (!cmp)
+			{
+				/* The sequence of letters has no coding. */
+				break;
+			}
+
+			letter = &letters[k];
+			if (letter->codes)
+			{
+				codes_ok = letter->codes;
+				i_ok = j;
+				ilen = jlen;
+			}
+		}
+
+		/* Determine which code to use. */
+		if (first_letter)
+		{
+			/* This is the first letter. */
+			j = 0;
+			first_letter = 0;
+		}
+		else if ((c = read_valid_char(&word[i_ok + ilen], &jlen)) && strchr(DM_VOWELS, c))
+		{
+			/* The next letter is a vowel. */
+			j = 1;
+		}
+		else
+		{
+			/* All other cases. */
+			j = 2;
+		}
+		memcpy(codes, codes_ok[j], sizeof(codes));
+
+		/* Update leaves. */
+		if (update_leaves(nodes, &num_nodes, leaves, &ix_leaves, &num_leaves, i, codes))
+		{
+			/* All soundex codes are completed to six digits. */
+			break;
+		}
+
+		/* Prepare for next letter sequence. */
+		i = i_ok;
+	}
+
+	/* Concatenate all generated soundex codes. */
+	for (i = 0, j = 0; i < num_leaves && j + DM_MAX_CODE_DIGITS + 1 <= n; i++, j += DM_MAX_CODE_DIGITS + 1)
+	{
+		memcpy(&soundex[j], leaves[ix_leaves][i]->soundex, DM_MAX_CODE_DIGITS + 1);
+	}
+
+	/* Terminate string. */
+	soundex[j - (j != 0)] = '\0';
+
+	free(leaves);
+	free(nodes);
+
+	return soundex;
+}
+
+
+#ifdef DM_MAIN
+
+/* For testing */
+
+int
+main(int argc, char **argv)
+{
+	char	   *soundex;
+
+	if (argc != 2)
+	{
+		fprintf(stderr, "Usage: %s string\n", argv[0]);
+		return -1;
+	}
+
+	soundex = malloc(DM_MAX_SOUNDEX_CHARS);
+
+	if (!_daitch_mokotoff(argv[1], soundex, DM_MAX_SOUNDEX_CHARS))
+	{
+		free(soundex);
+		fprintf(stderr, "Unable to allocate memory for soundex trees\n");
+		return -1;
+	}
+
+	printf("%s\n", soundex);
+	free(soundex);
+
+	return 0;
+}
+
+#endif
diff --git a/contrib/daitch_mokotoff/daitch_mokotoff.control b/contrib/daitch_mokotoff/daitch_mokotoff.control
new file mode 100644
index 0000000000..c5aed8e46e
--- /dev/null
+++ b/contrib/daitch_mokotoff/daitch_mokotoff.control
@@ -0,0 +1,5 @@
+# daitch_mokotoff extension
+comment = 'Daitch-Mokotoff Soundex System'
+default_version = '1.0'
+module_pathname = '$libdir/daitch_mokotoff'
+relocatable = true
diff --git a/contrib/daitch_mokotoff/daitch_mokotoff.h b/contrib/daitch_mokotoff/daitch_mokotoff.h
new file mode 100644
index 0000000000..8fcb98f1cf
--- /dev/null
+++ b/contrib/daitch_mokotoff/daitch_mokotoff.h
@@ -0,0 +1,1110 @@
+/*
+ * Types and lookup tables for Daitch-Mokotoff Soundex
+ *
+ * Copyright (c) 2021 Finance Norway
+ * Author: Dag Lem <d...@nimrod.no>
+ *
+ * This file is generated by daitch_mokotoff_header.pl
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without a written agreement
+ * is hereby granted, provided that the above copyright notice and this
+ * paragraph and the following two paragraphs appear in all copies.
+ *
+ * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include <stdlib.h>
+
+#define DM_MAX_CODE_DIGITS 6
+#define DM_MAX_ALTERNATE_CODES 5
+#define DM_MAX_NODES 1564
+#define DM_MAX_LEAVES 1250
+#define DM_MAX_SOUNDEX_CHARS (DM_MAX_NODES*(DM_MAX_CODE_DIGITS + 1))
+#define DM_VOWELS "AEIOUY"
+
+typedef char dm_code[2 + 1];	/* One or two sequential code digits + NUL */
+typedef dm_code dm_codes[2];	/* One or two alternate code sequences */
+
+struct dm_letter
+{
+	char		letter;
+	struct dm_letter *letters;
+	dm_codes   *codes;
+};
+
+struct dm_node
+{
+	int			soundex_length;
+	char		soundex[DM_MAX_CODE_DIGITS + 1];
+	int			is_leaf;
+	int			last_update;
+	char		code_digit;
+
+	/*
+	 * One or two alternate code digits leading to this node - repeated code
+	 * digits and 'X' lead back to the same node.
+	 */
+	char		prev_code_digits[2];
+	char		next_code_digits[2];
+	struct dm_node *next_nodes[DM_MAX_ALTERNATE_CODES + 1];
+};
+
+typedef struct dm_letter dm_letter;
+typedef struct dm_node dm_node;
+
+static dm_codes codes_0_1_X[] =
+{
+	{
+		"0"
+	},
+	{
+		"1"
+	},
+	{
+		"X"
+	}
+};
+static dm_codes codes_0_7_X[] =
+{
+	{
+		"0"
+	},
+	{
+		"7"
+	},
+	{
+		"X"
+	}
+};
+static dm_codes codes_0_X_X[] =
+{
+	{
+		"0"
+	},
+	{
+		"X"
+	},
+	{
+		"X"
+	}
+};
+static dm_codes codes_1_1_X[] =
+{
+	{
+		"1"
+	},
+	{
+		"1"
+	},
+	{
+		"X"
+	}
+};
+static dm_codes codes_1_X_X[] =
+{
+	{
+		"1"
+	},
+	{
+		"X"
+	},
+	{
+		"X"
+	}
+};
+static dm_codes codes_1or4_Xor4_Xor4[] =
+{
+	{
+		"1", "4"
+	},
+	{
+		"X", "4"
+	},
+	{
+		"X", "4"
+	}
+};
+static dm_codes codes_2_43_43[] =
+{
+	{
+		"2"
+	},
+	{
+		"43"
+	},
+	{
+		"43"
+	}
+};
+static dm_codes codes_2_4_4[] =
+{
+	{
+		"2"
+	},
+	{
+		"4"
+	},
+	{
+		"4"
+	}
+};
+static dm_codes codes_3_3_3[] =
+{
+	{
+		"3"
+	},
+	{
+		"3"
+	},
+	{
+		"3"
+	}
+};
+static dm_codes codes_3or4_3or4_3or4[] =
+{
+	{
+		"3", "4"
+	},
+	{
+		"3", "4"
+	},
+	{
+		"3", "4"
+	}
+};
+static dm_codes codes_4_4_4[] =
+{
+	{
+		"4"
+	},
+	{
+		"4"
+	},
+	{
+		"4"
+	}
+};
+static dm_codes codes_5_54_54[] =
+{
+	{
+		"5"
+	},
+	{
+		"54"
+	},
+	{
+		"54"
+	}
+};
+static dm_codes codes_5_5_5[] =
+{
+	{
+		"5"
+	},
+	{
+		"5"
+	},
+	{
+		"5"
+	}
+};
+static dm_codes codes_5_5_X[] =
+{
+	{
+		"5"
+	},
+	{
+		"5"
+	},
+	{
+		"X"
+	}
+};
+static dm_codes codes_5or45_5or45_5or45[] =
+{
+	{
+		"5", "45"
+	},
+	{
+		"5", "45"
+	},
+	{
+		"5", "45"
+	}
+};
+static dm_codes codes_5or4_5or4_5or4[] =
+{
+	{
+		"5", "4"
+	},
+	{
+		"5", "4"
+	},
+	{
+		"5", "4"
+	}
+};
+static dm_codes codes_66_66_66[] =
+{
+	{
+		"66"
+	},
+	{
+		"66"
+	},
+	{
+		"66"
+	}
+};
+static dm_codes codes_6_6_6[] =
+{
+	{
+		"6"
+	},
+	{
+		"6"
+	},
+	{
+		"6"
+	}
+};
+static dm_codes codes_7_7_7[] =
+{
+	{
+		"7"
+	},
+	{
+		"7"
+	},
+	{
+		"7"
+	}
+};
+static dm_codes codes_8_8_8[] =
+{
+	{
+		"8"
+	},
+	{
+		"8"
+	},
+	{
+		"8"
+	}
+};
+static dm_codes codes_94or4_94or4_94or4[] =
+{
+	{
+		"94", "4"
+	},
+	{
+		"94", "4"
+	},
+	{
+		"94", "4"
+	}
+};
+static dm_codes codes_9_9_9[] =
+{
+	{
+		"9"
+	},
+	{
+		"9"
+	},
+	{
+		"9"
+	}
+};
+static dm_codes codes_X_X_6orX[] =
+{
+	{
+		"X"
+	},
+	{
+		"X"
+	},
+	{
+		"6", "X"
+	}
+};
+
+static dm_letter letter_A[] =
+{
+	{
+		'I', NULL, codes_0_1_X
+	},
+	{
+		'J', NULL, codes_0_1_X
+	},
+	{
+		'U', NULL, codes_0_7_X
+	},
+	{
+		'Y', NULL, codes_0_1_X
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_CH[] =
+{
+	{
+		'S', NULL, codes_5_54_54
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_CS[] =
+{
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_CZ[] =
+{
+	{
+		'S', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_C[] =
+{
+	{
+		'H', letter_CH, codes_5or4_5or4_5or4
+	},
+	{
+		'K', NULL, codes_5or45_5or45_5or45
+	},
+	{
+		'S', letter_CS, codes_4_4_4
+	},
+	{
+		'Z', letter_CZ, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_DR[] =
+{
+	{
+		'S', NULL, codes_4_4_4
+	},
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_DS[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_DZ[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'S', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_D[] =
+{
+	{
+		'R', letter_DR, NULL
+	},
+	{
+		'S', letter_DS, codes_4_4_4
+	},
+	{
+		'T', NULL, codes_3_3_3
+	},
+	{
+		'Z', letter_DZ, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_E[] =
+{
+	{
+		'I', NULL, codes_0_1_X
+	},
+	{
+		'J', NULL, codes_0_1_X
+	},
+	{
+		'U', NULL, codes_1_1_X
+	},
+	{
+		'Y', NULL, codes_0_1_X
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_F[] =
+{
+	{
+		'B', NULL, codes_7_7_7
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_I[] =
+{
+	{
+		'A', NULL, codes_1_X_X
+	},
+	{
+		'E', NULL, codes_1_X_X
+	},
+	{
+		'O', NULL, codes_1_X_X
+	},
+	{
+		'U', NULL, codes_1_X_X
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_K[] =
+{
+	{
+		'H', NULL, codes_5_5_5
+	},
+	{
+		'S', NULL, codes_5_54_54
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_M[] =
+{
+	{
+		'N', NULL, codes_66_66_66
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_N[] =
+{
+	{
+		'M', NULL, codes_66_66_66
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_O[] =
+{
+	{
+		'I', NULL, codes_0_1_X
+	},
+	{
+		'J', NULL, codes_0_1_X
+	},
+	{
+		'Y', NULL, codes_0_1_X
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_P[] =
+{
+	{
+		'F', NULL, codes_7_7_7
+	},
+	{
+		'H', NULL, codes_7_7_7
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_R[] =
+{
+	{
+		'S', NULL, codes_94or4_94or4_94or4
+	},
+	{
+		'Z', NULL, codes_94or4_94or4_94or4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SCHTC[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SCHTSC[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SCHTS[] =
+{
+	{
+		'C', letter_SCHTSC, NULL
+	},
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SCHT[] =
+{
+	{
+		'C', letter_SCHTC, NULL
+	},
+	{
+		'S', letter_SCHTS, NULL
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SCH[] =
+{
+	{
+		'D', NULL, codes_2_43_43
+	},
+	{
+		'T', letter_SCHT, codes_2_43_43
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SC[] =
+{
+	{
+		'H', letter_SCH, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SHC[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SHTC[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SHTS[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SHT[] =
+{
+	{
+		'C', letter_SHTC, NULL
+	},
+	{
+		'S', letter_SHTS, NULL
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SH[] =
+{
+	{
+		'C', letter_SHC, NULL
+	},
+	{
+		'D', NULL, codes_2_43_43
+	},
+	{
+		'T', letter_SHT, codes_2_43_43
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_STC[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_STR[] =
+{
+	{
+		'S', NULL, codes_2_4_4
+	},
+	{
+		'Z', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_STSC[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_STS[] =
+{
+	{
+		'C', letter_STSC, NULL
+	},
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ST[] =
+{
+	{
+		'C', letter_STC, NULL
+	},
+	{
+		'R', letter_STR, NULL
+	},
+	{
+		'S', letter_STS, NULL
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SZC[] =
+{
+	{
+		'S', NULL, codes_2_4_4
+	},
+	{
+		'Z', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_SZ[] =
+{
+	{
+		'C', letter_SZC, NULL
+	},
+	{
+		'D', NULL, codes_2_43_43
+	},
+	{
+		'T', NULL, codes_2_43_43
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_S[] =
+{
+	{
+		'C', letter_SC, codes_2_4_4
+	},
+	{
+		'D', NULL, codes_2_43_43
+	},
+	{
+		'H', letter_SH, codes_4_4_4
+	},
+	{
+		'T', letter_ST, codes_2_43_43
+	},
+	{
+		'Z', letter_SZ, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TC[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TR[] =
+{
+	{
+		'S', NULL, codes_4_4_4
+	},
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TSC[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TS[] =
+{
+	{
+		'C', letter_TSC, NULL
+	},
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TTC[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TTSC[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TTS[] =
+{
+	{
+		'C', letter_TTSC, NULL
+	},
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TT[] =
+{
+	{
+		'C', letter_TTC, NULL
+	},
+	{
+		'S', letter_TTS, codes_4_4_4
+	},
+	{
+		'Z', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_TZ[] =
+{
+	{
+		'S', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_T[] =
+{
+	{
+		'C', letter_TC, codes_4_4_4
+	},
+	{
+		'H', NULL, codes_3_3_3
+	},
+	{
+		'R', letter_TR, NULL
+	},
+	{
+		'S', letter_TS, codes_4_4_4
+	},
+	{
+		'T', letter_TT, NULL
+	},
+	{
+		'Z', letter_TZ, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_U[] =
+{
+	{
+		'E', NULL, codes_0_X_X
+	},
+	{
+		'I', NULL, codes_0_1_X
+	},
+	{
+		'J', NULL, codes_0_1_X
+	},
+	{
+		'Y', NULL, codes_0_1_X
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZDZ[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZD[] =
+{
+	{
+		'Z', letter_ZDZ, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZHDZ[] =
+{
+	{
+		'H', NULL, codes_2_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZHD[] =
+{
+	{
+		'Z', letter_ZHDZ, NULL
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZH[] =
+{
+	{
+		'D', letter_ZHD, codes_2_43_43
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZSC[] =
+{
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_ZS[] =
+{
+	{
+		'C', letter_ZSC, NULL
+	},
+	{
+		'H', NULL, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_Z[] =
+{
+	{
+		'D', letter_ZD, codes_2_43_43
+	},
+	{
+		'H', letter_ZH, codes_4_4_4
+	},
+	{
+		'S', letter_ZS, codes_4_4_4
+	},
+	{
+		'\0'
+	}
+};
+static dm_letter letter_[] =
+{
+	{
+		'A', letter_A, codes_0_X_X
+	},
+	{
+		'B', NULL, codes_7_7_7
+	},
+	{
+		'C', letter_C, codes_5or4_5or4_5or4
+	},
+	{
+		'D', letter_D, codes_3_3_3
+	},
+	{
+		'E', letter_E, codes_0_X_X
+	},
+	{
+		'F', letter_F, codes_7_7_7
+	},
+	{
+		'G', NULL, codes_5_5_5
+	},
+	{
+		'H', NULL, codes_5_5_X
+	},
+	{
+		'I', letter_I, codes_0_X_X
+	},
+	{
+		'J', NULL, codes_1or4_Xor4_Xor4
+	},
+	{
+		'K', letter_K, codes_5_5_5
+	},
+	{
+		'L', NULL, codes_8_8_8
+	},
+	{
+		'M', letter_M, codes_6_6_6
+	},
+	{
+		'N', letter_N, codes_6_6_6
+	},
+	{
+		'O', letter_O, codes_0_X_X
+	},
+	{
+		'P', letter_P, codes_7_7_7
+	},
+	{
+		'Q', NULL, codes_5_5_5
+	},
+	{
+		'R', letter_R, codes_9_9_9
+	},
+	{
+		'S', letter_S, codes_4_4_4
+	},
+	{
+		'T', letter_T, codes_3_3_3
+	},
+	{
+		'U', letter_U, codes_0_X_X
+	},
+	{
+		'V', NULL, codes_7_7_7
+	},
+	{
+		'W', NULL, codes_7_7_7
+	},
+	{
+		'X', NULL, codes_5_54_54
+	},
+	{
+		'Y', NULL, codes_1_X_X
+	},
+	{
+		'Z', letter_Z, codes_4_4_4
+	},
+	{
+		'a', NULL, codes_X_X_6orX
+	},
+	{
+		'e', NULL, codes_X_X_6orX
+	},
+	{
+		't', NULL, codes_3or4_3or4_3or4
+	},
+	{
+		'\0'
+	}
+};
diff --git a/contrib/daitch_mokotoff/daitch_mokotoff_header.pl b/contrib/daitch_mokotoff/daitch_mokotoff_header.pl
new file mode 100755
index 0000000000..26e67fe5df
--- /dev/null
+++ b/contrib/daitch_mokotoff/daitch_mokotoff_header.pl
@@ -0,0 +1,274 @@
+#!/bin/perl
+#
+# Generation of types and lookup tables for Daitch-Mokotoff soundex.
+#
+# Copyright (c) 2021 Finance Norway
+# Author: Dag Lem <d...@nimrod.no>
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose, without fee, and without a written agreement
+# is hereby granted, provided that the above copyright notice and this
+# paragraph and the following two paragraphs appear in all copies.
+#
+# IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+# LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+# DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+# ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+#
+
+use strict;
+use warnings;
+use utf8;
+use open IO => ':utf8', ':std';
+use Data::Dumper;
+
+# Parse code table and generate tree for letter transitions.
+my %codes;
+my %alternates;
+my $table = [{}, [["","",""]]];
+while (<DATA>) {
+	chomp;
+	my ($letters, $codes) = split(/\s+/);
+	my @codes = map { [ split(/\|/) ] } split(/,/, $codes);
+
+	# Find alternate code transitions for calculation of storage.
+	# The first character can never yield more than two alternate codes, and is not considered here.
+	for my $c (@codes[1..2]) {
+		if (@$c > 1) {
+			for my $i (0..1) {
+				my ($a, $b) = (substr($c->[$i], -1, 1), substr($c->[($i + 1)%2], 0, 1));
+				$alternates{$a}{$b} = 1 if $a ne $b;
+			}
+		}
+	}
+	my $key = "codes_" . join("_", map { join("or", @$_) } @codes);
+	my $val = join(",\n", map { "\t{\n\t\t" . join(", ", map { "\"$_\"" } @$_) . "\n\t}" } @codes);
+	$codes{$key} = $val;
+
+	for my $letter (split(/,/, $letters)) {
+		my $ref = $table->[0];
+		# Link each character to the next in the letter combination.
+		my @c = split(//, $letter);
+		my $last_c = pop(@c);
+		for my $c (@c) {
+			$ref->{$c} //= [ {}, undef ];
+			$ref->{$c}[0] //= {};
+			$ref = $ref->{$c}[0];
+		}
+		# The sound code for the letter combination is stored at the last character.
+		$ref->{$last_c}[1] = $key;
+	}
+}
+close(DATA);
+
+# Find the maximum number of alternate codes in one position.
+my $alt_x = $alternates{"X"};
+while (my ($k, $v) = each %alternates) {
+	if (defined delete $v->{"X"}) {
+		for my $x (keys %$alt_x) {
+			$v->{$x} = 1;
+		}
+	}
+}
+my $max_alt = (reverse sort (2, map { scalar keys %$_ } values %alternates))[0];
+
+# The maximum number of nodes and leaves in the soundex tree.
+# These are safe estimates, but in practice somewhat higher than the actual maximums.
+# Note that the first character can never yield more than two alternate codes,
+# hence the calculations are performed as sums of two subtrees.
+my $digits = 6;
+# Number of nodes (sum of geometric progression).
+my $max_nodes = 2 + 2*(1 - $max_alt**($digits - 1))/(1 - $max_alt);
+# Number of leaves (exponential of base number).
+my $max_leaves = 2*$max_alt**($digits - 2);
+
+print <<EOF;
+/*
+ * Types and lookup tables for Daitch-Mokotoff Soundex
+ *
+ * Copyright (c) 2021 Finance Norway
+ * Author: Dag Lem <dag\@nimrod.no>
+ *
+ * This file is generated by daitch_mokotoff_header.pl
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without a written agreement
+ * is hereby granted, provided that the above copyright notice and this
+ * paragraph and the following two paragraphs appear in all copies.
+ *
+ * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include <stdlib.h>
+
+#define DM_MAX_CODE_DIGITS $digits
+#define DM_MAX_ALTERNATE_CODES $max_alt
+#define DM_MAX_NODES $max_nodes
+#define DM_MAX_LEAVES $max_leaves
+#define DM_MAX_SOUNDEX_CHARS (DM_MAX_NODES*(DM_MAX_CODE_DIGITS + 1))
+#define DM_VOWELS "AEIOUY"
+
+typedef char dm_code[2 + 1];	/* One or two sequential code digits + NUL */
+typedef dm_code dm_codes[2];	/* One or two alternate code sequences */
+
+struct dm_letter
+{
+	char		letter;
+	struct dm_letter *letters;
+	dm_codes   *codes;
+};
+
+struct dm_node
+{
+	int			soundex_length;
+	char		soundex[DM_MAX_CODE_DIGITS + 1];
+	int			is_leaf;
+	int			last_update;
+	char		code_digit;
+
+	/*
+	 * One or two alternate code digits leading to this node - repeated code
+	 * digits and 'X' lead back to the same node.
+	 */
+	char		prev_code_digits[2];
+	char		next_code_digits[2];
+	struct dm_node *next_nodes[DM_MAX_ALTERNATE_CODES + 1];
+};
+
+typedef struct dm_letter dm_letter;
+typedef struct dm_node dm_node;
+
+EOF
+
+for my $key (sort keys %codes) {
+	print "static dm_codes $key\[\] =\n{\n" . $codes{$key} . "\n};\n";
+}
+
+print "\n";
+
+sub hash2code {
+	my ($ref, $letter) = @_;
+
+	my @letters = ();
+
+	my $h = $ref->[0];
+	for my $key (sort keys %$h) {
+		$ref = $h->{$key};
+		my $children = "NULL";
+		if (defined $ref->[0]) {
+			$children = "letter_$letter$key";
+			hash2code($ref, "$letter$key");
+		}
+		my $codes = $ref->[1] // "NULL";
+		push(@letters, "\t{\n\t\t'$key', $children, $codes\n\t}");
+	}
+
+	print "static dm_letter letter_$letter\[\] =\n{\n";
+	for (@letters) {
+		print "$_,\n";
+	}
+	print "\t{\n\t\t'\\0'\n\t}\n";
+	print "};\n";
+}
+
+hash2code($table, '');
+
+
+# Table adapted from https://www.jewishgen.org/InfoFiles/Soundex.html
+#
+# X = NC (not coded)
+#
+# Note that the following letters are coded with substitute letters
+#
+# Ą => a (use '[' for table lookup)
+# Ę => e (use '\\' for table lookup)
+# Ţ => t (use ']' for table lookup)
+#
+
+__DATA__
+AI,AJ,AY				0,1,X
+AU						0,7,X
+a						X,X,6|X
+A						0,X,X
+B						7,7,7
+CHS						5,54,54
+CH						5|4,5|4,5|4
+CK						5|45,5|45,5|45
+CZ,CS,CSZ,CZS			4,4,4
+C						5|4,5|4,5|4
+DRZ,DRS					4,4,4
+DS,DSH,DSZ				4,4,4
+DZ,DZH,DZS				4,4,4
+D,DT					3,3,3
+EI,EJ,EY				0,1,X
+EU						1,1,X
+e						X,X,6|X
+E						0,X,X
+FB						7,7,7
+F						7,7,7
+G						5,5,5
+H						5,5,X
+IA,IE,IO,IU				1,X,X
+I						0,X,X
+J						1|4,X|4,X|4
+KS						5,54,54
+KH						5,5,5
+K						5,5,5
+L						8,8,8
+MN						66,66,66
+M						6,6,6
+NM						66,66,66
+N						6,6,6
+OI,OJ,OY				0,1,X
+O						0,X,X
+P,PF,PH					7,7,7
+Q						5,5,5
+RZ,RS					94|4,94|4,94|4
+R						9,9,9
+SCHTSCH,SCHTSH,SCHTCH	2,4,4
+SCH						4,4,4
+SHTCH,SHCH,SHTSH		2,4,4
+SHT,SCHT,SCHD			2,43,43
+SH						4,4,4
+STCH,STSCH,SC			2,4,4
+STRZ,STRS,STSH			2,4,4
+ST						2,43,43
+SZCZ,SZCS				2,4,4
+SZT,SHD,SZD,SD			2,43,43
+SZ						4,4,4
+S						4,4,4
+TCH,TTCH,TTSCH			4,4,4
+TH						3,3,3
+TRZ,TRS					4,4,4
+TSCH,TSH				4,4,4
+TS,TTS,TTSZ,TC			4,4,4
+TZ,TTZ,TZS,TSZ			4,4,4
+t						3|4,3|4,3|4
+T						3,3,3
+UI,UJ,UY				0,1,X
+U,UE					0,X,X
+V						7,7,7
+W						7,7,7
+X						5,54,54
+Y						1,X,X
+ZDZ,ZDZH,ZHDZH			2,4,4
+ZD,ZHD					2,43,43
+ZH,ZS,ZSCH,ZSH			4,4,4
+Z						4,4,4
diff --git a/contrib/daitch_mokotoff/expected/daitch_mokotoff.out b/contrib/daitch_mokotoff/expected/daitch_mokotoff.out
new file mode 100644
index 0000000000..b7db809746
--- /dev/null
+++ b/contrib/daitch_mokotoff/expected/daitch_mokotoff.out
@@ -0,0 +1,451 @@
+CREATE EXTENSION daitch_mokotoff;
+SELECT daitch_mokotoff('GOLDEN');
+ daitch_mokotoff 
+-----------------
+ 583600
+(1 row)
+
+SELECT daitch_mokotoff('Alpert');
+ daitch_mokotoff 
+-----------------
+ 087930
+(1 row)
+
+SELECT daitch_mokotoff('Breuer');
+ daitch_mokotoff 
+-----------------
+ 791900
+(1 row)
+
+SELECT daitch_mokotoff('Freud');
+ daitch_mokotoff 
+-----------------
+ 793000
+(1 row)
+
+SELECT daitch_mokotoff('Haber');
+ daitch_mokotoff 
+-----------------
+ 579000
+(1 row)
+
+SELECT daitch_mokotoff('Manheim');
+ daitch_mokotoff 
+-----------------
+ 665600
+(1 row)
+
+SELECT daitch_mokotoff('Mintz');
+ daitch_mokotoff 
+-----------------
+ 664000
+(1 row)
+
+SELECT daitch_mokotoff('Topf');
+ daitch_mokotoff 
+-----------------
+ 370000
+(1 row)
+
+SELECT daitch_mokotoff('Kleinman');
+ daitch_mokotoff 
+-----------------
+ 586660
+(1 row)
+
+SELECT daitch_mokotoff('Ben Aron');
+ daitch_mokotoff 
+-----------------
+ 769600
+(1 row)
+
+SELECT daitch_mokotoff('AUERBACH');
+ daitch_mokotoff 
+-----------------
+ 097500 097400
+(1 row)
+
+SELECT daitch_mokotoff('OHRBACH');
+ daitch_mokotoff 
+-----------------
+ 097500 097400
+(1 row)
+
+SELECT daitch_mokotoff('LIPSHITZ');
+ daitch_mokotoff 
+-----------------
+ 874400
+(1 row)
+
+SELECT daitch_mokotoff('LIPPSZYC');
+ daitch_mokotoff 
+-----------------
+ 874500 874400
+(1 row)
+
+SELECT daitch_mokotoff('LEWINSKY');
+ daitch_mokotoff 
+-----------------
+ 876450
+(1 row)
+
+SELECT daitch_mokotoff('LEVINSKI');
+ daitch_mokotoff 
+-----------------
+ 876450
+(1 row)
+
+SELECT daitch_mokotoff('SZLAMAWICZ');
+ daitch_mokotoff 
+-----------------
+ 486740
+(1 row)
+
+SELECT daitch_mokotoff('SHLAMOVITZ');
+ daitch_mokotoff 
+-----------------
+ 486740
+(1 row)
+
+SELECT daitch_mokotoff('Ceniow');
+ daitch_mokotoff 
+-----------------
+ 567000 467000
+(1 row)
+
+SELECT daitch_mokotoff('Tsenyuv');
+ daitch_mokotoff 
+-----------------
+ 467000
+(1 row)
+
+SELECT daitch_mokotoff('Holubica');
+ daitch_mokotoff 
+-----------------
+ 587500 587400
+(1 row)
+
+SELECT daitch_mokotoff('Golubitsa');
+ daitch_mokotoff 
+-----------------
+ 587400
+(1 row)
+
+SELECT daitch_mokotoff('Przemysl');
+ daitch_mokotoff 
+-----------------
+ 794648 746480
+(1 row)
+
+SELECT daitch_mokotoff('Pshemeshil');
+ daitch_mokotoff 
+-----------------
+ 746480
+(1 row)
+
+SELECT daitch_mokotoff('Rosochowaciec');
+                     daitch_mokotoff                     
+---------------------------------------------------------
+ 945755 945754 945745 945744 944755 944754 944745 944744
+(1 row)
+
+SELECT daitch_mokotoff('Rosokhovatsets');
+ daitch_mokotoff 
+-----------------
+ 945744
+(1 row)
+
+SELECT daitch_mokotoff('Peters');
+ daitch_mokotoff 
+-----------------
+ 739400 734000
+(1 row)
+
+SELECT daitch_mokotoff('Peterson');
+ daitch_mokotoff 
+-----------------
+ 739460 734600
+(1 row)
+
+SELECT daitch_mokotoff('Moskowitz');
+ daitch_mokotoff 
+-----------------
+ 645740
+(1 row)
+
+SELECT daitch_mokotoff('Moskovitz');
+ daitch_mokotoff 
+-----------------
+ 645740
+(1 row)
+
+SELECT daitch_mokotoff('Auerbach');
+ daitch_mokotoff 
+-----------------
+ 097500 097400
+(1 row)
+
+SELECT daitch_mokotoff('Uhrbach');
+ daitch_mokotoff 
+-----------------
+ 097500 097400
+(1 row)
+
+SELECT daitch_mokotoff('Jackson');
+       daitch_mokotoff       
+-----------------------------
+ 154600 145460 454600 445460
+(1 row)
+
+SELECT daitch_mokotoff('Jackson-Jackson');
+                            daitch_mokotoff                            
+-----------------------------------------------------------------------
+ 154654 154645 154644 145465 145464 454654 454645 454644 445465 445464
+(1 row)
+
+SELECT daitch_mokotoff('Müller');
+ daitch_mokotoff 
+-----------------
+ 689000
+(1 row)
+
+SELECT daitch_mokotoff('schmidt');
+ daitch_mokotoff 
+-----------------
+ 463000
+(1 row)
+
+SELECT daitch_mokotoff('schneider');
+ daitch_mokotoff 
+-----------------
+ 463900
+(1 row)
+
+SELECT daitch_mokotoff('fischer');
+ daitch_mokotoff 
+-----------------
+ 749000
+(1 row)
+
+SELECT daitch_mokotoff('weber');
+ daitch_mokotoff 
+-----------------
+ 779000
+(1 row)
+
+SELECT daitch_mokotoff('meyer');
+ daitch_mokotoff 
+-----------------
+ 619000
+(1 row)
+
+SELECT daitch_mokotoff('wagner');
+ daitch_mokotoff 
+-----------------
+ 756900
+(1 row)
+
+SELECT daitch_mokotoff('schulz');
+ daitch_mokotoff 
+-----------------
+ 484000
+(1 row)
+
+SELECT daitch_mokotoff('becker');
+ daitch_mokotoff 
+-----------------
+ 759000 745900
+(1 row)
+
+SELECT daitch_mokotoff('hoffmann');
+ daitch_mokotoff 
+-----------------
+ 576600
+(1 row)
+
+SELECT daitch_mokotoff('schäfer');
+ daitch_mokotoff 
+-----------------
+ 479000
+(1 row)
+
+SELECT daitch_mokotoff('Straßburg');
+ daitch_mokotoff 
+-----------------
+ 294795
+(1 row)
+
+SELECT daitch_mokotoff('Strasburg');
+ daitch_mokotoff 
+-----------------
+ 294795
+(1 row)
+
+SELECT daitch_mokotoff('Éregon');
+ daitch_mokotoff 
+-----------------
+ 095600
+(1 row)
+
+SELECT daitch_mokotoff('Eregon');
+ daitch_mokotoff 
+-----------------
+ 095600
+(1 row)
+
+SELECT daitch_mokotoff('AKSSOL');
+ daitch_mokotoff 
+-----------------
+ 054800
+(1 row)
+
+SELECT daitch_mokotoff('GERSCHFELD');
+       daitch_mokotoff       
+-----------------------------
+ 594578 594783 545783 547830
+(1 row)
+
+SELECT daitch_mokotoff('OBrien');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('''OBrien');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('O''Brien');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('OB''rien');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('OBr''ien');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('OBri''en');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('OBrie''n');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('OBrien''');
+ daitch_mokotoff 
+-----------------
+ 079600
+(1 row)
+
+SELECT daitch_mokotoff('KINGSMITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('-KINGSMITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('K-INGSMITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KI-NGSMITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KIN-GSMITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KING-SMITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KINGS-MITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KINGSM-ITH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KINGSMI-TH');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KINGSMIT-H');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff('KINGSMITH-');
+ daitch_mokotoff 
+-----------------
+ 565463
+(1 row)
+
+SELECT daitch_mokotoff(E' \t\n\r Washington \t\n\r ');
+ daitch_mokotoff 
+-----------------
+ 746536
+(1 row)
+
+SELECT daitch_mokotoff('Washington');
+ daitch_mokotoff 
+-----------------
+ 746536
+(1 row)
+
+SELECT daitch_mokotoff('ţamas');
+ daitch_mokotoff 
+-----------------
+ 364000 464000
+(1 row)
+
+SELECT daitch_mokotoff('țamas');
+ daitch_mokotoff 
+-----------------
+ 364000 464000
+(1 row)
+
+SELECT daitch_mokotoff('CJC');
+              daitch_mokotoff              
+-------------------------------------------
+ 550000 540000 545000 450000 400000 440000
+(1 row)
+
diff --git a/contrib/daitch_mokotoff/sql/daitch_mokotoff.sql b/contrib/daitch_mokotoff/sql/daitch_mokotoff.sql
new file mode 100644
index 0000000000..eafc24ee87
--- /dev/null
+++ b/contrib/daitch_mokotoff/sql/daitch_mokotoff.sql
@@ -0,0 +1,121 @@
+CREATE EXTENSION daitch_mokotoff;
+
+
+-- https://www.jewishgen.org/InfoFiles/Soundex.html
+SELECT daitch_mokotoff('GOLDEN');
+SELECT daitch_mokotoff('Alpert');
+SELECT daitch_mokotoff('Breuer');
+SELECT daitch_mokotoff('Freud');
+SELECT daitch_mokotoff('Haber');
+SELECT daitch_mokotoff('Manheim');
+SELECT daitch_mokotoff('Mintz');
+SELECT daitch_mokotoff('Topf');
+SELECT daitch_mokotoff('Kleinman');
+SELECT daitch_mokotoff('Ben Aron');
+
+SELECT daitch_mokotoff('AUERBACH');
+SELECT daitch_mokotoff('OHRBACH');
+SELECT daitch_mokotoff('LIPSHITZ');
+SELECT daitch_mokotoff('LIPPSZYC');
+SELECT daitch_mokotoff('LEWINSKY');
+SELECT daitch_mokotoff('LEVINSKI');
+SELECT daitch_mokotoff('SZLAMAWICZ');
+SELECT daitch_mokotoff('SHLAMOVITZ');
+
+
+-- https://www.avotaynu.com/soundex.htm
+SELECT daitch_mokotoff('Ceniow');
+SELECT daitch_mokotoff('Tsenyuv');
+SELECT daitch_mokotoff('Holubica');
+SELECT daitch_mokotoff('Golubitsa');
+SELECT daitch_mokotoff('Przemysl');
+SELECT daitch_mokotoff('Pshemeshil');
+SELECT daitch_mokotoff('Rosochowaciec');
+SELECT daitch_mokotoff('Rosokhovatsets');
+
+
+-- https://familypedia.fandom.com/wiki/Daitch-Mokotoff_Soundex
+SELECT daitch_mokotoff('Peters');
+SELECT daitch_mokotoff('Peterson');
+SELECT daitch_mokotoff('Moskowitz');
+SELECT daitch_mokotoff('Moskovitz');
+SELECT daitch_mokotoff('Auerbach');
+SELECT daitch_mokotoff('Uhrbach');
+SELECT daitch_mokotoff('Jackson');
+SELECT daitch_mokotoff('Jackson-Jackson');
+
+
+-- Perl Text::Phonetic::DaitchMokotoff 006_daitchmokotoff.t
+-- Tests covered above are omitted.
+SELECT daitch_mokotoff('Müller');
+SELECT daitch_mokotoff('schmidt');
+SELECT daitch_mokotoff('schneider');
+SELECT daitch_mokotoff('fischer');
+SELECT daitch_mokotoff('weber');
+SELECT daitch_mokotoff('meyer');
+SELECT daitch_mokotoff('wagner');
+SELECT daitch_mokotoff('schulz');
+SELECT daitch_mokotoff('becker');
+SELECT daitch_mokotoff('hoffmann');
+SELECT daitch_mokotoff('schäfer');
+
+
+-- Apache Commons DaitchMokotoffSoundexTest.java
+
+-- testAccentedCharacterFolding
+SELECT daitch_mokotoff('Straßburg');
+SELECT daitch_mokotoff('Strasburg');
+
+SELECT daitch_mokotoff('Éregon');
+SELECT daitch_mokotoff('Eregon');
+
+-- testAdjacentCodes
+SELECT daitch_mokotoff('AKSSOL');
+SELECT daitch_mokotoff('GERSCHFELD');
+
+-- testEncodeBasic
+-- Tests covered above are omitted.
+
+-- testEncodeIgnoreApostrophes
+SELECT daitch_mokotoff('OBrien');
+SELECT daitch_mokotoff('''OBrien');
+SELECT daitch_mokotoff('O''Brien');
+SELECT daitch_mokotoff('OB''rien');
+SELECT daitch_mokotoff('OBr''ien');
+SELECT daitch_mokotoff('OBri''en');
+SELECT daitch_mokotoff('OBrie''n');
+SELECT daitch_mokotoff('OBrien''');
+
+-- testEncodeIgnoreHyphens
+SELECT daitch_mokotoff('KINGSMITH');
+SELECT daitch_mokotoff('-KINGSMITH');
+SELECT daitch_mokotoff('K-INGSMITH');
+SELECT daitch_mokotoff('KI-NGSMITH');
+SELECT daitch_mokotoff('KIN-GSMITH');
+SELECT daitch_mokotoff('KING-SMITH');
+SELECT daitch_mokotoff('KINGS-MITH');
+SELECT daitch_mokotoff('KINGSM-ITH');
+SELECT daitch_mokotoff('KINGSMI-TH');
+SELECT daitch_mokotoff('KINGSMIT-H');
+SELECT daitch_mokotoff('KINGSMITH-');
+
+-- testEncodeIgnoreTrimmable
+SELECT daitch_mokotoff(E' \t\n\r Washington \t\n\r ');
+SELECT daitch_mokotoff('Washington');
+
+-- testSoundexBasic
+-- Tests covered above are omitted.
+
+-- testSoundexBasic2
+-- Tests covered above are omitted.
+
+-- testSoundexBasic3
+-- Tests covered above are omitted.
+
+-- testSpecialRomanianCharacters
+SELECT daitch_mokotoff('ţamas'); -- t-cedila
+SELECT daitch_mokotoff('țamas'); -- t-comma
+
+
+-- Contrived case which is not handled correctly by other implementations.
+SELECT daitch_mokotoff('CJC');
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index d3ca4b6932..0788db060d 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -105,6 +105,7 @@ CREATE EXTENSION <replaceable>module_name</replaceable>;
  &citext;
  &cube;
  &dblink;
+ &daitch-mokotoff;
  &dict-int;
  &dict-xsyn;
  &earthdistance;
diff --git a/doc/src/sgml/daitch-mokotoff.sgml b/doc/src/sgml/daitch-mokotoff.sgml
new file mode 100644
index 0000000000..6dbc817eca
--- /dev/null
+++ b/doc/src/sgml/daitch-mokotoff.sgml
@@ -0,0 +1,92 @@
+<!-- doc/src/sgml/daitch-mokotoff.sgml -->
+
+<sect1 id="daitch-mokotoff" xreflabel="daitch_mokotoff">
+ <title>daitch_mokotoff</title>
+
+ <indexterm zone="daitch-mokotoff">
+  <primary>daitch_mokotoff</primary>
+ </indexterm>
+
+ <para>
+  The <filename>daitch_mokotoff</filename> module provides an implementation
+  of the Daitch-Mokotoff Soundex System.
+ </para>
+
+ <para>
+  Compared to the American Soundex System implemented in
+  the <filename>fuzzystrmatch</filename> module, the major improvements of the
+  Daitch-Mokotoff Soundex System are:
+
+  <itemizedlist spacing="compact" mark="bullet">
+   <listitem>
+    <para>
+     Information is coded to the first six meaningful letters rather than
+     four.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The initial letter is coded rather than kept as is.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Where two consecutive letters have a single sound, they are coded as a
+     single number.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     When a letter or combination of letters may have two different sounds,
+     it is double coded under the two different codes.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     A letter or combination of letters maps into ten possible codes rather
+     than seven.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  The <function>daitch_mokotoff</function> function shown in
+  <xref linkend="functions-daitch-mokotoff-table"/> generates Daitch-Mokotoff
+  soundex codes for matching of similar-sounding names.
+ </para>
+
+ <table id="functions-daitch-mokotoff-table">
+  <title><filename>daitch_mokotoff</filename> Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <function>daitch_mokotoff</function> ( <parameter>name</parameter> <type>text</type> )
+        <returnvalue>text</returnvalue>
+       </para>
+       <para>
+        Generates Daitch-Mokotoff soundex codes.
+       </para></entry>
+      </row>
+     </tbody>
+  </tgroup>
+ </table>
+
+ <indexterm>
+  <primary>daitch_mokotoff</primary>
+ </indexterm>
+ <para>
+  <function>daitch_mokotoff</function> generates Daitch-Mokotoff soundex codes for the specified <parameter>name</parameter>. Since alternate soundex codes are separated by spaces, the returned text is suited for use in Full Text Search, see <xref linkend="textsearch"/>.
+ </para>
+
+</sect1>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 89454e99b9..9ac43e5928 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -117,6 +117,7 @@
 <!ENTITY btree-gist      SYSTEM "btree-gist.sgml">
 <!ENTITY citext          SYSTEM "citext.sgml">
 <!ENTITY cube            SYSTEM "cube.sgml">
+<!ENTITY daitch-mokotoff SYSTEM "daitch-mokotoff.sgml">
 <!ENTITY dblink          SYSTEM "dblink.sgml">
 <!ENTITY dict-int        SYSTEM "dict-int.sgml">
 <!ENTITY dict-xsyn       SYSTEM "dict-xsyn.sgml">

Attachment: soundex-comparison.sh
Description: Bourne shell script

Reply via email to