diff --git a/doc/src/sgml/ref/alter_tsdictionary.sgml b/doc/src/sgml/ref/alter_tsdictionary.sgml
index 59c33666557584e0ed735a1f84ce059a50d1b095..a2929c70d12e55e514773810de54a4702dc6aa35 100644
--- a/doc/src/sgml/ref/alter_tsdictionary.sgml
+++ b/doc/src/sgml/ref/alter_tsdictionary.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/alter_tsdictionary.sgml,v 1.1 2007/08/21 21:08:47 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_tsdictionary.sgml,v 1.2 2007/08/22 01:39:44 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -20,7 +20,9 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> ( OPTION = <replaceable class="parameter">init_options</replaceable> )
+ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> (
+    <replaceable class="parameter">option</replaceable> [ = <replaceable class="parameter">value</replaceable> ] [, ... ]
+)
 ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> RENAME TO <replaceable>newname</replaceable>
 ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> OWNER TO <replaceable>newowner</replaceable>
 </synopsis>
@@ -31,8 +33,8 @@ ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> OWNER TO <replaceab
 
   <para>
    <command>ALTER TEXT SEARCH DICTIONARY</command> changes the definition of
-   a text search dictionary.  You can change the dictionary's initialization
-   options, or change the dictionary's name or owner.
+   a text search dictionary.  You can change the dictionary's
+   template-specific options, or change the dictionary's name or owner.
   </para>
 
   <para>
@@ -56,11 +58,22 @@ ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> OWNER TO <replaceab
    </varlistentry>
 
    <varlistentry>
-    <term><replaceable class="parameter">init_options</replaceable></term>
+    <term><replaceable class="parameter">option</replaceable></term>
     <listitem>
      <para>
-      A new list of initialization options, or <literal>NULL</> to
-      remove all options.
+      The name of a template-specific option to be set for this dictionary.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">value</replaceable></term>
+    <listitem>
+     <para>
+      The new value to use for a template-specific option.
+      If the equal sign and value are omitted, then any previous
+      setting for the option is removed from the dictionary,
+      allowing the default to be used.
      </para>
     </listitem>
    </varlistentry>
@@ -83,18 +96,31 @@ ALTER TEXT SEARCH DICTIONARY <replaceable>name</replaceable> OWNER TO <replaceab
     </listitem>
    </varlistentry>
  </variablelist>
+
+  <para>
+   Template-specific options can appear in any order.
+  </para>
  </refsect1>
   
  <refsect1>
   <title>Examples</title>
 
   <para>
-   The following example command sets the language and stopword list
-   for a Snowball-based dictionary.
+   The following example command changes the stopword list
+   for a Snowball-based dictionary.  Other parameters remain unchanged.
+  </para>
+
+<programlisting>
+ALTER TEXT SEARCH DICTIONARY my_dict ( StopWords = newrussian );
+</programlisting>  
+
+  <para>
+   The following example command changes the language option to dutch,
+   and removes the stopword option entirely.
   </para>
 
 <programlisting>
-ALTER TEXT SEARCH DICTIONARY my_russian ( option = 'Language=russian, StopWords=my_russian' );
+ALTER TEXT SEARCH DICTIONARY my_dict ( language = dutch, StopWords );
 </programlisting>  
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_tsdictionary.sgml b/doc/src/sgml/ref/create_tsdictionary.sgml
index 81c6a0c6edb2fb8885f1daec47128b61266334d0..9aa53a6a7c484d331a0662cd1a9bbe84095eaf1a 100644
--- a/doc/src/sgml/ref/create_tsdictionary.sgml
+++ b/doc/src/sgml/ref/create_tsdictionary.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_tsdictionary.sgml,v 1.1 2007/08/21 21:08:47 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_tsdictionary.sgml,v 1.2 2007/08/22 01:39:44 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -22,7 +22,7 @@ PostgreSQL documentation
 <synopsis>
 CREATE TEXT SEARCH DICTIONARY <replaceable class="parameter">name</replaceable> (
     TEMPLATE = <replaceable class="parameter">template</replaceable>
-    [, OPTION = <replaceable class="parameter">init_options</replaceable> ]
+    [, <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ]]
 )
 </synopsis>
  </refsynopsisdiv>
@@ -78,17 +78,46 @@ CREATE TEXT SEARCH DICTIONARY <replaceable class="parameter">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><replaceable class="parameter">init_options</replaceable></term>
+    <term><replaceable class="parameter">option</replaceable></term>
     <listitem>
      <para>
-      A list of initialization options for the template functions.
-      This is a string containing <replaceable>keyword</> <literal>=</>
-      <replaceable>value</> pairs.  The specific keywords allowed
-      vary depending on the text search template.
+      The name of a template-specific option to be set for this dictionary.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">value</replaceable></term>
+    <listitem>
+     <para>
+      The value to use for a template-specific option.  If the value
+      is not a simple identifier or number, it must be quoted (but you can
+      always quote it, if you wish).
      </para>
     </listitem>
    </varlistentry>
   </variablelist>
+
+  <para>
+   The options can appear in any order.
+  </para>
+ </refsect1>
+  
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   The following example command creates a Snowball-based dictionary
+   with a nonstandard list of stop words.
+  </para>
+
+<programlisting>
+CREATE TEXT SEARCH DICTIONARY my_russian (
+    template = snowball,
+    language = russian,
+    stopwords = myrussian
+);
+</programlisting>  
  </refsect1>
  
  <refsect1>
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index af34c58c7c230c4d003bd871f791516793bb9b21..7c5a1c49a33040bf388599e412be9d9c711f3d75 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -9,12 +9,13 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.2 2007/08/21 21:24:00 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.3 2007/08/22 01:39:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
-#include "miscadmin.h"
+
+#include <ctype.h>
 
 #include "access/heapam.h"
 #include "access/genam.h"
@@ -31,6 +32,8 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
 #include "tsearch/ts_cache.h"
 #include "tsearch/ts_public.h"
@@ -86,7 +89,7 @@ get_ts_parser_func(DefElem *defel, int attnum)
 			break;
 		case Anum_pg_ts_parser_prsheadline:
 			nargs = 3;
-			typeId[1] = TEXTOID;
+			typeId[1] = INTERNALOID;
 			typeId[2] = TSQUERYOID;
 			break;
 		case Anum_pg_ts_parser_prslextype:
@@ -407,6 +410,53 @@ makeDictionaryDependencies(HeapTuple tuple)
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 }
 
+/*
+ * verify that a template's init method accepts a proposed option list
+ */
+static void
+verify_dictoptions(Oid tmplId, List *dictoptions)
+{
+	HeapTuple	tup;
+	Form_pg_ts_template tform;
+	Oid			initmethod;
+
+	tup = SearchSysCache(TSTEMPLATEOID,
+						 ObjectIdGetDatum(tmplId),
+						 0, 0, 0);
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "cache lookup failed for text search template %u",
+			 tmplId);
+	tform = (Form_pg_ts_template) GETSTRUCT(tup);
+
+	initmethod = tform->tmplinit;
+
+	if (!OidIsValid(initmethod))
+	{
+		/* If there is no init method, disallow any options */
+		if (dictoptions)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("text search template \"%s\" does not accept options",
+							NameStr(tform->tmplname))));
+	}
+	else
+	{
+		/*
+		 * Copy the options just in case init method thinks it can scribble
+		 * on them ...
+		 */
+		dictoptions = copyObject(dictoptions);
+
+		/*
+		 * Call the init method and see if it complains.  We don't worry about
+		 * it leaking memory, since our command will soon be over anyway.
+		 */
+		(void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
+	}
+
+	ReleaseSysCache(tup);
+}
+
 /*
  * CREATE TEXT SEARCH DICTIONARY
  */
@@ -419,7 +469,8 @@ DefineTSDictionary(List *names, List *parameters)
 	Datum		values[Natts_pg_ts_dict];
 	char		nulls[Natts_pg_ts_dict];
 	NameData	dname;
-	int			i;
+	Oid			templId = InvalidOid;
+	List	   *dictoptions = NIL;
 	Oid			dictOid;
 	Oid			namespaceoid;
 	AclResult	aclresult;
@@ -434,18 +485,6 @@ DefineTSDictionary(List *names, List *parameters)
 		aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
 					   get_namespace_name(namespaceoid));
 
-	for (i = 0; i < Natts_pg_ts_dict; i++)
-	{
-		nulls[i] = ' ';
-		values[i] = ObjectIdGetDatum(InvalidOid);
-	}
-
-	namestrcpy(&dname, dictname);
-	values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
-	values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
-	values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
-	nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
-
 	/*
 	 * loop over the definition list and extract the information we need.
 	 */
@@ -455,42 +494,41 @@ DefineTSDictionary(List *names, List *parameters)
 
 		if (pg_strcasecmp(defel->defname, "template") == 0)
 		{
-			Oid			templId;
-
 			templId = TSTemplateGetTmplid(defGetQualifiedName(defel), false);
-
-			values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
-			nulls[Anum_pg_ts_dict_dicttemplate - 1] = ' ';
 		}
-		else if (pg_strcasecmp(defel->defname, "option") == 0)
+		else
 		{
-			char	   *opt = defGetString(defel);
-
-			if (pg_strcasecmp(opt, "null") != 0)
-			{
-				values[Anum_pg_ts_dict_dictinitoption - 1] =
-					DirectFunctionCall1(textin, CStringGetDatum(opt));
-				nulls[Anum_pg_ts_dict_dictinitoption - 1] = ' ';
-			}
+			/* Assume it's an option for the dictionary itself */
+			dictoptions = lappend(dictoptions, defel);
 		}
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("text search dictionary parameter \"%s\" not recognized",
-							defel->defname)));
 	}
 
 	/*
 	 * Validation
 	 */
-	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_dict_dicttemplate - 1])))
+	if (!OidIsValid(templId))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("text search template is required")));
 
+	verify_dictoptions(templId, dictoptions);
+
 	/*
 	 * Looks good, insert
 	 */
+	memset(values, 0, sizeof(values));
+	memset(nulls, ' ', sizeof(nulls));
+
+	namestrcpy(&dname, dictname);
+	values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
+	values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+	values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
+	values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
+	if (dictoptions)
+		values[Anum_pg_ts_dict_dictinitoption - 1] =
+			PointerGetDatum(serialize_deflist(dictoptions));
+	else
+		nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
 
 	dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
 
@@ -652,6 +690,9 @@ AlterTSDictionary(AlterTSDictionaryStmt * stmt)
 	Relation	rel;
 	Oid			dictId;
 	ListCell   *pl;
+	List	   *dictoptions;
+	Datum		opt;
+	bool		isnull;
 	Datum		repl_val[Natts_pg_ts_dict];
 	char		repl_null[Natts_pg_ts_dict];
 	char		repl_repl[Natts_pg_ts_dict];
@@ -673,41 +714,67 @@ AlterTSDictionary(AlterTSDictionaryStmt * stmt)
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
 					   NameListToString(stmt->dictname));
 
-	memset(repl_val, 0, sizeof(repl_val));
-	memset(repl_null, ' ', sizeof(repl_null));
-	memset(repl_repl, ' ', sizeof(repl_repl));
+	/* deserialize the existing set of options */
+	opt = SysCacheGetAttr(TSDICTOID, tup,
+						  Anum_pg_ts_dict_dictinitoption,
+						  &isnull);
+	if (isnull)
+		dictoptions = NIL;
+	else
+		dictoptions = deserialize_deflist(opt);
 
 	/*
-	 * NOTE: because we only support altering the option, not the template,
-	 * there is no need to update dependencies.
+	 * Modify the options list as per specified changes
 	 */
 	foreach(pl, stmt->options)
 	{
 		DefElem    *defel = (DefElem *) lfirst(pl);
+		ListCell   *cell;
+		ListCell   *prev;
+		ListCell   *next;
 
-		if (pg_strcasecmp(defel->defname, "option") == 0)
+		/*
+		 * Remove any matches ...
+		 */
+		prev = NULL;
+		for (cell = list_head(dictoptions); cell; cell = next)
 		{
-			char	   *opt = defGetString(defel);
+			DefElem    *oldel = (DefElem *) lfirst(cell);
 
-			if (pg_strcasecmp(opt, "null") == 0)
-			{
-				repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
-			}
+			next = lnext(cell);
+			if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
+				dictoptions = list_delete_cell(dictoptions, cell, prev);
 			else
-			{
-				repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
-					DirectFunctionCall1(textin, CStringGetDatum(opt));
-				repl_null[Anum_pg_ts_dict_dictinitoption - 1] = ' ';
-			}
-			repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
+				prev = cell;
 		}
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("text search dictionary parameter \"%s\" not recognized",
-							defel->defname)));
+
+		/*
+		 * and add new value if it's got one
+		 */
+		if (defel->arg)
+			dictoptions = lappend(dictoptions, defel);
 	}
 
+	/*
+	 * Validate
+	 */
+	verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
+					   dictoptions);
+
+	/*
+	 * Looks good, update
+	 */
+	memset(repl_val, 0, sizeof(repl_val));
+	memset(repl_null, ' ', sizeof(repl_null));
+	memset(repl_repl, ' ', sizeof(repl_repl));
+
+	if (dictoptions)
+		repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
+			PointerGetDatum(serialize_deflist(dictoptions));
+	else
+		repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
+	repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
+
 	newtup = heap_modifytuple(tup, RelationGetDescr(rel),
 							  repl_val, repl_null, repl_repl);
 
@@ -715,6 +782,12 @@ AlterTSDictionary(AlterTSDictionaryStmt * stmt)
 
 	CatalogUpdateIndexes(rel, newtup);
 
+	/*
+	 * NOTE: because we only support altering the options, not the template,
+	 * there is no need to update dependencies.  This might have to change
+	 * if the options ever reference inside-the-database objects.
+	 */
+
 	heap_freetuple(newtup);
 	ReleaseSysCache(tup);
 
@@ -1941,3 +2014,265 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt, HeapTuple tup)
 
 	heap_close(relMap, RowExclusiveLock);
 }
+
+
+/*
+ * Serialize dictionary options, producing a TEXT datum from a List of DefElem
+ *
+ * This is used to form the value stored in pg_ts_dict.dictinitoption.
+ * For the convenience of pg_dump, the output is formatted exactly as it
+ * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
+ * same options.
+ *
+ * Note that we assume that only the textual representation of an option's
+ * value is interesting --- hence, non-string DefElems get forced to strings.
+ */
+text *
+serialize_deflist(List *deflist)
+{
+	text	   *result;
+	StringInfoData buf;
+	ListCell   *l;
+
+	initStringInfo(&buf);
+
+	foreach(l, deflist)
+	{
+		DefElem    *defel = (DefElem *) lfirst(l);
+		char	   *val = defGetString(defel);
+
+		appendStringInfo(&buf, "%s = ",
+					 quote_identifier(defel->defname));
+		/* If backslashes appear, force E syntax to determine their handling */
+		if (strchr(val, '\\'))
+			appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
+		appendStringInfoChar(&buf, '\'');
+		while (*val)
+		{
+			char		ch = *val++;
+
+			if (SQL_STR_DOUBLE(ch, true))
+				appendStringInfoChar(&buf, ch);
+			appendStringInfoChar(&buf, ch);
+		}
+		appendStringInfoChar(&buf, '\'');
+		if (lnext(l) != NULL)
+			appendStringInfo(&buf, ", ");
+	}
+
+	result = CStringGetTextP(buf.data);
+	pfree(buf.data);
+	return result;
+}
+
+/*
+ * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
+ *
+ * This is also used for prsheadline options, so for backward compatibility
+ * we need to accept a few things serialize_deflist() will never emit:
+ * in particular, unquoted and double-quoted values.
+ */
+List *
+deserialize_deflist(Datum txt)
+{
+	text	   *in = DatumGetTextP(txt); /* in case it's toasted */
+	List	   *result = NIL;
+	int			len = VARSIZE(in) - VARHDRSZ;
+	char	   *ptr,
+			   *endptr,
+			   *workspace,
+			   *wsptr = NULL,
+			   *startvalue = NULL;
+	typedef enum {
+		CS_WAITKEY,
+		CS_INKEY,
+		CS_INQKEY,
+		CS_WAITEQ,
+		CS_WAITVALUE,
+		CS_INSQVALUE,
+		CS_INDQVALUE,
+		CS_INWVALUE
+	} ds_state;
+	ds_state	state = CS_WAITKEY;
+
+	workspace = (char *) palloc(len + 1);		/* certainly enough room */
+	ptr = VARDATA(in);
+	endptr = ptr + len;
+	for (; ptr < endptr; ptr++)
+	{
+		switch (state)
+		{
+			case CS_WAITKEY:
+				if (isspace((unsigned char) *ptr) || *ptr == ',')
+					continue;
+				if (*ptr == '"')
+				{
+					wsptr = workspace;
+					state = CS_INQKEY;
+				}
+				else
+				{
+					wsptr = workspace;
+					*wsptr++ = *ptr;
+					state = CS_INKEY;
+				}
+				break;
+			case CS_INKEY:
+				if (isspace((unsigned char) *ptr))
+				{
+					*wsptr++ = '\0';
+					state = CS_WAITEQ;
+				}
+				else if (*ptr == '=')
+				{
+					*wsptr++ = '\0';
+					state = CS_WAITVALUE;
+				}
+				else
+				{
+					*wsptr++ = *ptr;
+				}
+				break;
+			case CS_INQKEY:
+				if (*ptr == '"')
+				{
+					if (ptr+1 < endptr && ptr[1] == '"')
+					{
+						/* copy only one of the two quotes */
+						*wsptr++ = *ptr++;
+					}
+					else
+					{
+						*wsptr++ = '\0';
+						state = CS_WAITEQ;
+					}
+				}
+				else
+				{
+					*wsptr++ = *ptr;
+				}
+				break;
+			case CS_WAITEQ:
+				if (*ptr == '=')
+					state = CS_WAITVALUE;
+				else if (!isspace((unsigned char) *ptr))
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("invalid parameter list format: \"%s\"",
+									TextPGetCString(in))));
+				break;
+			case CS_WAITVALUE:
+				if (*ptr == '\'')
+				{
+					startvalue = wsptr;
+					state = CS_INSQVALUE;
+				}
+				else if (*ptr == 'E' && ptr+1 < endptr && ptr[1] == '\'')
+				{
+					ptr++;
+					startvalue = wsptr;
+					state = CS_INSQVALUE;
+				}
+				else if (*ptr == '"')
+				{
+					startvalue = wsptr;
+					state = CS_INDQVALUE;
+				}
+				else if (!isspace((unsigned char) *ptr))
+				{
+					startvalue = wsptr;
+					*wsptr++ = *ptr;
+					state = CS_INWVALUE;
+				}
+				break;
+			case CS_INSQVALUE:
+				if (*ptr == '\'')
+				{
+					if (ptr+1 < endptr && ptr[1] == '\'')
+					{
+						/* copy only one of the two quotes */
+						*wsptr++ = *ptr++;
+					}
+					else
+					{
+						*wsptr++ = '\0';
+						result = lappend(result,
+										 makeDefElem(pstrdup(workspace),
+													 (Node *) makeString(pstrdup(startvalue))));
+						state = CS_WAITKEY;
+					}
+				}
+				else if (*ptr == '\\')
+				{
+					if (ptr+1 < endptr && ptr[1] == '\\')
+					{
+						/* copy only one of the two backslashes */
+						*wsptr++ = *ptr++;
+					}
+					else
+						*wsptr++ = *ptr;
+				}
+				else
+				{
+					*wsptr++ = *ptr;
+				}
+				break;
+			case CS_INDQVALUE:
+				if (*ptr == '"')
+				{
+					if (ptr+1 < endptr && ptr[1] == '"')
+					{
+						/* copy only one of the two quotes */
+						*wsptr++ = *ptr++;
+					}
+					else
+					{
+						*wsptr++ = '\0';
+						result = lappend(result,
+										 makeDefElem(pstrdup(workspace),
+													 (Node *) makeString(pstrdup(startvalue))));
+						state = CS_WAITKEY;
+					}
+				}
+				else
+				{
+					*wsptr++ = *ptr;
+				}
+				break;
+			case CS_INWVALUE:
+				if (*ptr == ',' || isspace((unsigned char) *ptr))
+				{
+					*wsptr++ = '\0';
+					result = lappend(result,
+									 makeDefElem(pstrdup(workspace),
+												 (Node *) makeString(pstrdup(startvalue))));
+					state = CS_WAITKEY;
+				}
+				else
+				{
+					*wsptr++ = *ptr;
+				}
+				break;
+			default:
+				elog(ERROR, "unrecognized deserialize_deflist state: %d",
+					 state);
+		}
+	}
+
+	if (state == CS_INWVALUE)
+	{
+		*wsptr++ = '\0';
+		result = lappend(result,
+						 makeDefElem(pstrdup(workspace),
+									 (Node *) makeString(pstrdup(startvalue))));
+	}
+	else if (state != CS_WAITKEY)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid parameter list format: \"%s\"",
+						TextPGetCString(in))));
+
+	pfree(workspace);
+
+	return result;
+}
diff --git a/src/backend/snowball/dict_snowball.c b/src/backend/snowball/dict_snowball.c
index f0bc2feedecbb02ed6a7401de6b0ea7015fdfe61..03f2dd928c2a3e77a41af2e81314ed8e57d21c0d 100644
--- a/src/backend/snowball/dict_snowball.c
+++ b/src/backend/snowball/dict_snowball.c
@@ -6,12 +6,13 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/snowball/dict_snowball.c,v 1.1 2007/08/21 01:11:16 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/snowball/dict_snowball.c,v 1.2 2007/08/22 01:39:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "commands/defrem.h"
 #include "fmgr.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_public.h"
@@ -185,59 +186,44 @@ locate_stem_module(DictSnowball * d, char *lang)
 Datum
 dsnowball_init(PG_FUNCTION_ARGS)
 {
-	text	   *in;
+	List	   *dictoptions = (List *) PG_GETARG_POINTER(0);
 	DictSnowball *d;
-	Map		   *cfg,
-			   *pcfg;
 	bool		stoploaded = false;
-
-	/* init functions must defend against NULLs for themselves */
-	if (PG_ARGISNULL(0) || PG_GETARG_POINTER(0) == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("NULL config not allowed for Snowball")));
-	in = PG_GETARG_TEXT_P(0);
+	ListCell   *l;
 
 	d = (DictSnowball *) palloc0(sizeof(DictSnowball));
 	d->stoplist.wordop = recode_and_lowerstr;
 
-	parse_keyvalpairs(in, &cfg);
-	pcfg = cfg;
-	PG_FREE_IF_COPY(in, 0);
-
-	while (pcfg && pcfg->key)
+	foreach(l, dictoptions)
 	{
-		if (pg_strcasecmp("StopWords", pcfg->key) == 0)
+		DefElem    *defel = (DefElem *) lfirst(l);
+
+		if (pg_strcasecmp("StopWords", defel->defname) == 0)
 		{
 			if (stoploaded)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple StopWords parameters")));
-			readstoplist(pcfg->value, &d->stoplist);
+			readstoplist(defGetString(defel), &d->stoplist);
 			sortstoplist(&d->stoplist);
 			stoploaded = true;
 		}
-		else if (pg_strcasecmp("Language", pcfg->key) == 0)
+		else if (pg_strcasecmp("Language", defel->defname) == 0)
 		{
 			if (d->stem)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple Language parameters")));
-			locate_stem_module(d, pcfg->value);
+			locate_stem_module(d, defGetString(defel));
 		}
 		else
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized Snowball parameter: \"%s\"",
-							pcfg->key)));
+							defel->defname)));
 		}
-
-		pfree(pcfg->key);
-		pfree(pcfg->value);
-		pcfg++;
 	}
-	pfree(cfg);
 
 	if (!d->stem)
 		ereport(ERROR,
diff --git a/src/backend/snowball/snowball.sql.in b/src/backend/snowball/snowball.sql.in
index 5f1f3e772e80a1d78701e67124f0ce8e4f9a8526..873a5bf5592d11497d772a90c8a3442ddac70adb 100644
--- a/src/backend/snowball/snowball.sql.in
+++ b/src/backend/snowball/snowball.sql.in
@@ -1,9 +1,9 @@
--- $PostgreSQL: pgsql/src/backend/snowball/snowball.sql.in,v 1.1 2007/08/21 01:11:16 tgl Exp $$
+-- $PostgreSQL: pgsql/src/backend/snowball/snowball.sql.in,v 1.2 2007/08/22 01:39:44 tgl Exp $$
 
 -- text search configuration for _CFGNAME_ language
 CREATE TEXT SEARCH DICTIONARY _DICTNAME_
 	(TEMPLATE = snowball,
-	OPTION = 'Language=_DICTNAME__STOPWORDS_');
+	Language = _DICTNAME_ _STOPWORDS_);
 
 COMMENT ON TEXT SEARCH DICTIONARY _DICTNAME_ IS 'Snowball stemmer for _DICTNAME_ language';
 
diff --git a/src/backend/tsearch/dict_ispell.c b/src/backend/tsearch/dict_ispell.c
index f7cee1073005f1303c481a889437a10cf61a1acc..802a64508787460f496a724521a2f37c91222e10 100644
--- a/src/backend/tsearch/dict_ispell.c
+++ b/src/backend/tsearch/dict_ispell.c
@@ -7,12 +7,13 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tsearch/dict_ispell.c,v 1.1 2007/08/21 01:11:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tsearch/dict_ispell.c,v 1.2 2007/08/22 01:39:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "commands/defrem.h"
 #include "tsearch/dicts/spell.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_public.h"
@@ -30,59 +31,49 @@ typedef struct
 Datum
 dispell_init(PG_FUNCTION_ARGS)
 {
+	List	   *dictoptions = (List *) PG_GETARG_POINTER(0);
 	DictISpell *d;
-	Map		   *cfg,
-			   *pcfg;
 	bool		affloaded = false,
 				dictloaded = false,
 				stoploaded = false;
-	text	   *in;
-
-	/* init functions must defend against NULLs for themselves */
-	if (PG_ARGISNULL(0) || PG_GETARG_POINTER(0) == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("NULL config not allowed for ISpell")));
-	in = PG_GETARG_TEXT_P(0);
-
-	parse_keyvalpairs(in, &cfg);
-	PG_FREE_IF_COPY(in, 0);
+	ListCell   *l;
 
 	d = (DictISpell *) palloc0(sizeof(DictISpell));
 	d->stoplist.wordop = recode_and_lowerstr;
 
-	pcfg = cfg;
-	while (pcfg->key)
+	foreach(l, dictoptions)
 	{
-		if (pg_strcasecmp("DictFile", pcfg->key) == 0)
+		DefElem    *defel = (DefElem *) lfirst(l);
+
+		if (pg_strcasecmp(defel->defname, "DictFile") == 0)
 		{
 			if (dictloaded)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple DictFile parameters")));
 			NIImportDictionary(&(d->obj),
-							   get_tsearch_config_filename(pcfg->value,
+							   get_tsearch_config_filename(defGetString(defel),
 														   "dict"));
 			dictloaded = true;
 		}
-		else if (pg_strcasecmp("AffFile", pcfg->key) == 0)
+		else if (pg_strcasecmp(defel->defname, "AffFile") == 0)
 		{
 			if (affloaded)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple AffFile parameters")));
 			NIImportAffixes(&(d->obj),
-							get_tsearch_config_filename(pcfg->value,
+							get_tsearch_config_filename(defGetString(defel),
 														"affix"));
 			affloaded = true;
 		}
-		else if (pg_strcasecmp("StopWords", pcfg->key) == 0)
+		else if (pg_strcasecmp(defel->defname, "StopWords") == 0)
 		{
 			if (stoploaded)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple StopWords parameters")));
-			readstoplist(pcfg->value, &(d->stoplist));
+			readstoplist(defGetString(defel), &(d->stoplist));
 			sortstoplist(&(d->stoplist));
 			stoploaded = true;
 		}
@@ -91,13 +82,9 @@ dispell_init(PG_FUNCTION_ARGS)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized ISpell parameter: \"%s\"",
-							pcfg->key)));
+							defel->defname)));
 		}
-		pfree(pcfg->key);
-		pfree(pcfg->value);
-		pcfg++;
 	}
-	pfree(cfg);
 
 	if (affloaded && dictloaded)
 	{
diff --git a/src/backend/tsearch/dict_simple.c b/src/backend/tsearch/dict_simple.c
index 2c1bc3d017ee33fdc0f11abe1a1df49fd4b0d288..fcc08ea180da5d561626e6b80a69004f8001de85 100644
--- a/src/backend/tsearch/dict_simple.c
+++ b/src/backend/tsearch/dict_simple.c
@@ -7,12 +7,13 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tsearch/dict_simple.c,v 1.1 2007/08/21 01:11:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tsearch/dict_simple.c,v 1.2 2007/08/22 01:39:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "commands/defrem.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_public.h"
 #include "tsearch/ts_utils.h"
@@ -28,18 +29,34 @@ typedef struct
 Datum
 dsimple_init(PG_FUNCTION_ARGS)
 {
+	List	   *dictoptions = (List *) PG_GETARG_POINTER(0);
 	DictExample *d = (DictExample *) palloc0(sizeof(DictExample));
+	bool		stoploaded = false;
+	ListCell   *l;
 
 	d->stoplist.wordop = recode_and_lowerstr;
 
-	if (!PG_ARGISNULL(0) && PG_GETARG_POINTER(0) != NULL)
+	foreach(l, dictoptions)
 	{
-		text   *in = PG_GETARG_TEXT_P(0);
-		char   *filename = TextPGetCString(in);
+		DefElem    *defel = (DefElem *) lfirst(l);
 
-		readstoplist(filename, &d->stoplist);
-		sortstoplist(&d->stoplist);
-		pfree(filename);
+		if (pg_strcasecmp("StopWords", defel->defname) == 0)
+		{
+			if (stoploaded)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("multiple StopWords parameters")));
+			readstoplist(defGetString(defel), &d->stoplist);
+			sortstoplist(&d->stoplist);
+			stoploaded = true;
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("unrecognized simple dictionary parameter: \"%s\"",
+							defel->defname)));
+		}
 	}
 
 	PG_RETURN_POINTER(d);
diff --git a/src/backend/tsearch/dict_thesaurus.c b/src/backend/tsearch/dict_thesaurus.c
index 8c544ad4f8aba495bfed226815181fb987edc1f5..70700db41fb8653d38188abbcd07a2579f8a12d8 100644
--- a/src/backend/tsearch/dict_thesaurus.c
+++ b/src/backend/tsearch/dict_thesaurus.c
@@ -7,13 +7,14 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tsearch/dict_thesaurus.c,v 1.1 2007/08/21 01:11:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tsearch/dict_thesaurus.c,v 1.2 2007/08/22 01:39:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "catalog/namespace.h"
+#include "commands/defrem.h"
 #include "storage/fd.h"
 #include "tsearch/ts_cache.h"
 #include "tsearch/ts_locale.h"
@@ -593,57 +594,43 @@ compileTheSubstitute(DictThesaurus * d)
 Datum
 thesaurus_init(PG_FUNCTION_ARGS)
 {
+	List	   *dictoptions = (List *) PG_GETARG_POINTER(0);
 	DictThesaurus *d;
-	Map		   *cfg,
-			   *pcfg;
-	text	   *in;
 	char	   *subdictname = NULL;
 	bool		fileloaded = false;
-
-	/* init functions must defend against NULLs for themselves */
-	if (PG_ARGISNULL(0) || PG_GETARG_POINTER(0) == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("NULL config not allowed for Thesaurus")));
-	in = PG_GETARG_TEXT_P(0);
-
-	parse_keyvalpairs(in, &cfg);
-	PG_FREE_IF_COPY(in, 0);
+	ListCell   *l;
 
 	d = (DictThesaurus *) palloc0(sizeof(DictThesaurus));
 
-	pcfg = cfg;
-	while (pcfg->key)
+	foreach(l, dictoptions)
 	{
-		if (pg_strcasecmp("DictFile", pcfg->key) == 0)
+		DefElem    *defel = (DefElem *) lfirst(l);
+
+		if (pg_strcasecmp("DictFile", defel->defname) == 0)
 		{
 			if (fileloaded)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple DictFile parameters")));
-			thesaurusRead(pcfg->value, d);
+			thesaurusRead(defGetString(defel), d);
 			fileloaded = true;
 		}
-		else if (pg_strcasecmp("Dictionary", pcfg->key) == 0)
+		else if (pg_strcasecmp("Dictionary", defel->defname) == 0)
 		{
 			if (subdictname)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("multiple Dictionary parameters")));
-			subdictname = pstrdup(pcfg->value);
+			subdictname = pstrdup(defGetString(defel));
 		}
 		else
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized Thesaurus parameter: \"%s\"",
-							pcfg->key)));
+							defel->defname)));
 		}
-		pfree(pcfg->key);
-		pfree(pcfg->value);
-		pcfg++;
 	}
-	pfree(cfg);
 
 	if (!fileloaded)
 		ereport(ERROR,
diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c
index bb0a75ca85aa348f11302d5c60a1efee46ecce2f..9270c403696156cf1deaf19af793ebb9cab577f8 100644
--- a/src/backend/tsearch/ts_utils.c
+++ b/src/backend/tsearch/ts_utils.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tsearch/ts_utils.c,v 1.1 2007/08/21 01:11:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tsearch/ts_utils.c,v 1.2 2007/08/22 01:39:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,169 +24,6 @@
 #include "utils/builtins.h"
 
 
-#define CS_WAITKEY	0
-#define CS_INKEY	1
-#define CS_WAITEQ	2
-#define CS_WAITVALUE	3
-#define CS_INVALUE	4
-#define CS_IN2VALUE 5
-#define CS_WAITDELIM	6
-#define CS_INESC	7
-#define CS_IN2ESC	8
-
-static char *
-nstrdup(char *ptr, int len)
-{
-	char	   *res = palloc(len + 1),
-			   *cptr;
-
-	memcpy(res, ptr, len);
-	res[len] = '\0';
-	cptr = ptr = res;
-	while (*ptr)
-	{
-		if (t_iseq(ptr, '\\'))
-			ptr++;
-		COPYCHAR(cptr, ptr);
-		cptr += pg_mblen(ptr);
-		ptr += pg_mblen(ptr);
-	}
-	*cptr = '\0';
-
-	return res;
-}
-
-/*
- * Parse a parameter string consisting of key = value clauses
- */
-void
-parse_keyvalpairs(text *in, Map ** m)
-{
-	Map		   *mptr;
-	char	   *ptr = VARDATA(in),
-			   *begin = NULL;
-	char		num = 0;
-	int			state = CS_WAITKEY;
-
-	while (ptr - VARDATA(in) < VARSIZE(in) - VARHDRSZ)
-	{
-		if (t_iseq(ptr, ','))
-			num++;
-		ptr += pg_mblen(ptr);
-	}
-
-	*m = mptr = (Map *) palloc(sizeof(Map) * (num + 2));
-	memset(mptr, 0, sizeof(Map) * (num + 2));
-	ptr = VARDATA(in);
-	while (ptr - VARDATA(in) < VARSIZE(in) - VARHDRSZ)
-	{
-		if (state == CS_WAITKEY)
-		{
-			if (t_isalpha(ptr))
-			{
-				begin = ptr;
-				state = CS_INKEY;
-			}
-			else if (!t_isspace(ptr))
-				ereport(ERROR,
-						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("invalid parameter list format: \"%s\"",
-								TextPGetCString(in))));
-		}
-		else if (state == CS_INKEY)
-		{
-			if (t_isspace(ptr))
-			{
-				mptr->key = nstrdup(begin, ptr - begin);
-				state = CS_WAITEQ;
-			}
-			else if (t_iseq(ptr, '='))
-			{
-				mptr->key = nstrdup(begin, ptr - begin);
-				state = CS_WAITVALUE;
-			}
-			else if (!t_isalpha(ptr))
-				ereport(ERROR,
-						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("invalid parameter list format: \"%s\"",
-								TextPGetCString(in))));
-		}
-		else if (state == CS_WAITEQ)
-		{
-			if (t_iseq(ptr, '='))
-				state = CS_WAITVALUE;
-			else if (!t_isspace(ptr))
-				ereport(ERROR,
-						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("invalid parameter list format: \"%s\"",
-								TextPGetCString(in))));
-		}
-		else if (state == CS_WAITVALUE)
-		{
-			if (t_iseq(ptr, '"'))
-			{
-				begin = ptr + 1;
-				state = CS_INVALUE;
-			}
-			else if (!t_isspace(ptr))
-			{
-				begin = ptr;
-				state = CS_IN2VALUE;
-			}
-		}
-		else if (state == CS_INVALUE)
-		{
-			if (t_iseq(ptr, '"'))
-			{
-				mptr->value = nstrdup(begin, ptr - begin);
-				mptr++;
-				state = CS_WAITDELIM;
-			}
-			else if (t_iseq(ptr, '\\'))
-				state = CS_INESC;
-		}
-		else if (state == CS_IN2VALUE)
-		{
-			if (t_isspace(ptr) || t_iseq(ptr, ','))
-			{
-				mptr->value = nstrdup(begin, ptr - begin);
-				mptr++;
-				state = (t_iseq(ptr, ',')) ? CS_WAITKEY : CS_WAITDELIM;
-			}
-			else if (t_iseq(ptr, '\\'))
-				state = CS_INESC;
-		}
-		else if (state == CS_WAITDELIM)
-		{
-			if (t_iseq(ptr, ','))
-				state = CS_WAITKEY;
-			else if (!t_isspace(ptr))
-				ereport(ERROR,
-						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("invalid parameter list format: \"%s\"",
-								TextPGetCString(in))));
-		}
-		else if (state == CS_INESC)
-			state = CS_INVALUE;
-		else if (state == CS_IN2ESC)
-			state = CS_IN2VALUE;
-		else
-			elog(ERROR, "unrecognized parse_keyvalpairs state: %d", state);
-		ptr += pg_mblen(ptr);
-	}
-
-	if (state == CS_IN2VALUE)
-	{
-		mptr->value = nstrdup(begin, ptr - begin);
-		mptr++;
-	}
-	else if (!(state == CS_WAITDELIM || state == CS_WAITKEY))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("invalid parameter list format: \"%s\"",
-						TextPGetCString(in))));
-}
-
 /*
  * Given the base name and extension of a tsearch config file, return
  * its full path name.  The base name is assumed to be user-supplied,
diff --git a/src/backend/tsearch/wparser.c b/src/backend/tsearch/wparser.c
index 0b374e8159eb130e0cc87e076ced2c10ebff0805..e927e98aab2f147ba9188d7e0f6e0dde5605d67f 100644
--- a/src/backend/tsearch/wparser.c
+++ b/src/backend/tsearch/wparser.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tsearch/wparser.c,v 1.1 2007/08/21 01:11:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tsearch/wparser.c,v 1.2 2007/08/22 01:39:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "tsearch/ts_cache.h"
 #include "tsearch/ts_public.h"
 #include "tsearch/ts_utils.h"
@@ -300,6 +301,7 @@ ts_headline_byid_opt(PG_FUNCTION_ARGS)
 	TSQuery		query = PG_GETARG_TSQUERY(2);
 	text	   *opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_P(3) : NULL;
 	HeadlineText prs;
+	List	   *prsoptions;
 	text	   *out;
 	TSConfigCacheEntry *cfg;
 	TSParserCacheEntry *prsobj;
@@ -313,9 +315,14 @@ ts_headline_byid_opt(PG_FUNCTION_ARGS)
 
 	hlparsetext(cfg->cfgId, &prs, query, VARDATA(in), VARSIZE(in) - VARHDRSZ);
 
+	if (opt)
+		prsoptions = deserialize_deflist(PointerGetDatum(opt));
+	else
+		prsoptions = NIL;
+
 	FunctionCall3(&(prsobj->prsheadline),
 				  PointerGetDatum(&prs),
-				  PointerGetDatum(opt),
+				  PointerGetDatum(prsoptions),
 				  PointerGetDatum(query));
 
 	out = generatHeadline(&prs);
diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c
index 8d71e3e914e3f4e7359b3ade870874c8bf40d714..5b47f66d07fdf77c93e21cf4bdbed3eeb0f8d3d6 100644
--- a/src/backend/tsearch/wparser_def.c
+++ b/src/backend/tsearch/wparser_def.c
@@ -7,13 +7,14 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tsearch/wparser_def.c,v 1.1 2007/08/21 01:11:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tsearch/wparser_def.c,v 1.2 2007/08/22 01:39:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "commands/defrem.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_public.h"
 #include "tsearch/ts_type.h"
@@ -1662,7 +1663,7 @@ Datum
 prsd_headline(PG_FUNCTION_ARGS)
 {
 	HeadlineParsedText *prs = (HeadlineParsedText *) PG_GETARG_POINTER(0);
-	text	   *opt = (text *) PG_GETARG_POINTER(1);	/* can't be toasted */
+	List	   *prsoptions = (List *) PG_GETARG_POINTER(1);
 	TSQuery		query = PG_GETARG_TSQUERY(2);
 
 	/* from opt + start and and tag */
@@ -1682,66 +1683,55 @@ prsd_headline(PG_FUNCTION_ARGS)
 
 	int			i;
 	int			highlight = 0;
+	ListCell   *l;
 
 	/* config */
 	prs->startsel = NULL;
 	prs->stopsel = NULL;
-	if (opt)
+	foreach(l, prsoptions)
 	{
-		Map		   *map,
-				   *mptr;
-
-		parse_keyvalpairs(opt, &map);
-		mptr = map;
-
-		while (mptr && mptr->key)
-		{
-			if (pg_strcasecmp(mptr->key, "MaxWords") == 0)
-				max_words = pg_atoi(mptr->value, 4, 1);
-			else if (pg_strcasecmp(mptr->key, "MinWords") == 0)
-				min_words = pg_atoi(mptr->value, 4, 1);
-			else if (pg_strcasecmp(mptr->key, "ShortWord") == 0)
-				shortword = pg_atoi(mptr->value, 4, 1);
-			else if (pg_strcasecmp(mptr->key, "StartSel") == 0)
-				prs->startsel = pstrdup(mptr->value);
-			else if (pg_strcasecmp(mptr->key, "StopSel") == 0)
-				prs->stopsel = pstrdup(mptr->value);
-			else if (pg_strcasecmp(mptr->key, "HighlightAll") == 0)
-				highlight = (
-							 pg_strcasecmp(mptr->value, "1") == 0 ||
-							 pg_strcasecmp(mptr->value, "on") == 0 ||
-							 pg_strcasecmp(mptr->value, "true") == 0 ||
-							 pg_strcasecmp(mptr->value, "t") == 0 ||
-							 pg_strcasecmp(mptr->value, "y") == 0 ||
-							 pg_strcasecmp(mptr->value, "yes") == 0) ?
-					1 : 0;
-
-			pfree(mptr->key);
-			pfree(mptr->value);
-
-			mptr++;
-		}
-		pfree(map);
-
-		if (highlight == 0)
-		{
-			if (min_words >= max_words)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("MinWords should be less than MaxWords")));
-			if (min_words <= 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("MinWords should be positive")));
-			if (shortword < 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("ShortWord should be >= 0")));
-		}
+		DefElem    *defel = (DefElem *) lfirst(l);
+		char	   *val = defGetString(defel);
+
+		if (pg_strcasecmp(defel->defname, "MaxWords") == 0)
+			max_words = pg_atoi(val, sizeof(int32), 0);
+		else if (pg_strcasecmp(defel->defname, "MinWords") == 0)
+			min_words = pg_atoi(val, sizeof(int32), 0);
+		else if (pg_strcasecmp(defel->defname, "ShortWord") == 0)
+			shortword = pg_atoi(val, sizeof(int32), 0);
+		else if (pg_strcasecmp(defel->defname, "StartSel") == 0)
+			prs->startsel = pstrdup(val);
+		else if (pg_strcasecmp(defel->defname, "StopSel") == 0)
+			prs->stopsel = pstrdup(val);
+		else if (pg_strcasecmp(defel->defname, "HighlightAll") == 0)
+			highlight = (pg_strcasecmp(val, "1") == 0 ||
+						 pg_strcasecmp(val, "on") == 0 ||
+						 pg_strcasecmp(val, "true") == 0 ||
+						 pg_strcasecmp(val, "t") == 0 ||
+						 pg_strcasecmp(val, "y") == 0 ||
+						 pg_strcasecmp(val, "yes") == 0);
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("unrecognized headline parameter: \"%s\"",
+							defel->defname)));
 	}
 
 	if (highlight == 0)
 	{
+		if (min_words >= max_words)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("MinWords should be less than MaxWords")));
+		if (min_words <= 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("MinWords should be positive")));
+		if (shortword < 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("ShortWord should be >= 0")));
+
 		while (hlCover(prs, query, &p, &q))
 		{
 			/* find cover len in words */
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index cd3a9dad57116e7cb1f12f674433cbaf572e6b8e..94051b8c32f1233c3c9e259bb349bb617b31c951 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -20,7 +20,7 @@
  * Copyright (c) 2006-2007, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.1 2007/08/21 01:11:19 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.2 2007/08/22 01:39:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,9 +37,9 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "miscadmin.h"
 #include "tsearch/ts_cache.h"
-#include "tsearch/ts_utils.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
@@ -252,7 +252,7 @@ lookup_ts_dictionary_cache(Oid dictId)
 					tptmpl;
 		Form_pg_ts_dict dict;
 		Form_pg_ts_template template;
-		MemoryContext saveCtx = NULL;
+		MemoryContext saveCtx;
 
 		tpdict = SearchSysCache(TSDICTOID,
 								ObjectIdGetDatum(dictId),
@@ -319,21 +319,30 @@ lookup_ts_dictionary_cache(Oid dictId)
 
 		if (OidIsValid(template->tmplinit))
 		{
-			bool		isnull;
+			List	   *dictoptions;
 			Datum		opt;
+			bool		isnull;
+			MemoryContext oldcontext;
+
+			/*
+			 * Init method runs in dictionary's private memory context,
+			 * and we make sure the options are stored there too
+			 */
+			oldcontext = MemoryContextSwitchTo(entry->dictCtx);
 
 			opt = SysCacheGetAttr(TSDICTOID, tpdict,
 								  Anum_pg_ts_dict_dictinitoption,
 								  &isnull);
 			if (isnull)
-				opt = PointerGetDatum(NULL);
+				dictoptions = NIL;
+			else
+				dictoptions = deserialize_deflist(opt);
 
-			/*
-			 * Init method runs in dictionary's private memory context
-			 */
-			saveCtx = MemoryContextSwitchTo(entry->dictCtx);
-			entry->dictData = DatumGetPointer(OidFunctionCall1(template->tmplinit, opt));
-			MemoryContextSwitchTo(saveCtx);
+			entry->dictData =
+				DatumGetPointer(OidFunctionCall1(template->tmplinit,
+											PointerGetDatum(dictoptions)));
+
+			MemoryContextSwitchTo(oldcontext);
 		}
 
 		ReleaseSysCache(tptmpl);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4d4d7f7986e9ffb241f004d4d247b5fa90e689c7..7da419db6aa966733f2e6410a9fafcac223a7f3c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.470 2007/08/21 01:11:21 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.471 2007/08/22 01:39:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -8288,11 +8288,9 @@ dumpTSDictionary(Archive *fout, TSDictInfo * dictinfo)
 
 	PQclear(res);
 
+	/* the dictinitoption can be dumped straight into the command */
 	if (dictinfo->dictinitoption)
-	{
-		appendPQExpBuffer(q, ",\n    OPTION = ");
-		appendStringLiteralConn(q, dictinfo->dictinitoption, g_conn);
-	}
+		appendPQExpBuffer(q, ",\n    %s", dictinfo->dictinitoption);
 
 	appendPQExpBuffer(q, " );\n");
 
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index a050ff1d3ccef34354552d21b5a8bb738e03f4a6..be40e7cf0f3d7efc0269d8a7978a35e6d2bf7d85 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.416 2007/08/21 01:11:22 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.417 2007/08/22 01:39:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200708201
+#define CATALOG_VERSION_NO	200708211
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 1a624c6dbe0cad293f6facf7ff40808c993caba3..a19bda2ab7e30485223f7c17320d8022397fa712 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.463 2007/08/21 01:11:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.464 2007/08/22 01:39:45 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -4311,7 +4311,7 @@ DATA(insert OID = 3718 (  prsd_nexttoken	PGNSP PGUID 12 1 0 f f t f i 3 2281 "22
 DESCR("");
 DATA(insert OID = 3719 (  prsd_end			PGNSP PGUID 12 1 0 f f t f i 1 2278 "2281" _null_ _null_ _null_ prsd_end - _null_ ));
 DESCR("");
-DATA(insert OID = 3720 (  prsd_headline		PGNSP PGUID 12 1 0 f f t f i 3 2281 "2281 25 3615" _null_ _null_ _null_ prsd_headline - _null_ ));
+DATA(insert OID = 3720 (  prsd_headline		PGNSP PGUID 12 1 0 f f t f i 3 2281 "2281 2281 3615" _null_ _null_ _null_ prsd_headline - _null_ ));
 DESCR("");
 DATA(insert OID = 3721 (  prsd_lextype		PGNSP PGUID 12 1 0 f f t f i 1 2281 "2281" _null_ _null_ _null_ prsd_lextype - _null_ ));
 DESCR("");
@@ -4321,22 +4321,22 @@ DESCR("normalize one word by dictionary");
 DATA(insert OID = 3724 (  ts_lexize			PGNSP PGUID 12 1 0 f f t f s 2 1009 "25 25" _null_ _null_ _null_ ts_lexize_byname - _null_ ));
 DESCR("normalize one word by dictionary");
 
-DATA(insert OID = 3725 (  dsimple_init		PGNSP PGUID 12 1 0 f f f f i 1 2281 "2281" _null_ _null_ _null_ dsimple_init - _null_ ));
+DATA(insert OID = 3725 (  dsimple_init		PGNSP PGUID 12 1 0 f f t f i 1 2281 "2281" _null_ _null_ _null_ dsimple_init - _null_ ));
 DESCR("");
 DATA(insert OID = 3726 (  dsimple_lexize	PGNSP PGUID 12 1 0 f f t f i 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ dsimple_lexize - _null_ ));
 DESCR("");
 
-DATA(insert OID = 3728 (  dsynonym_init		PGNSP PGUID 12 1 0 f f f f i 1 2281 "2281" _null_ _null_ _null_ dsynonym_init - _null_ ));
+DATA(insert OID = 3728 (  dsynonym_init		PGNSP PGUID 12 1 0 f f t f i 1 2281 "2281" _null_ _null_ _null_ dsynonym_init - _null_ ));
 DESCR("");
 DATA(insert OID = 3729 (  dsynonym_lexize	PGNSP PGUID 12 1 0 f f t f i 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ dsynonym_lexize - _null_ ));
 DESCR("");
 
-DATA(insert OID = 3731 (  dispell_init		PGNSP PGUID 12 1 0 f f f f i 1 2281 "2281" _null_ _null_ _null_ dispell_init - _null_ ));
+DATA(insert OID = 3731 (  dispell_init		PGNSP PGUID 12 1 0 f f t f i 1 2281 "2281" _null_ _null_ _null_ dispell_init - _null_ ));
 DESCR("");
 DATA(insert OID = 3732 (  dispell_lexize	PGNSP PGUID 12 1 0 f f t f i 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ dispell_lexize - _null_ ));
 DESCR("");
 
-DATA(insert OID = 3740 (  thesaurus_init	PGNSP PGUID 12 1 0 f f f f i 1 2281 "2281" _null_ _null_ _null_ thesaurus_init - _null_ ));
+DATA(insert OID = 3740 (  thesaurus_init	PGNSP PGUID 12 1 0 f f t f i 1 2281 "2281" _null_ _null_ _null_ thesaurus_init - _null_ ));
 DESCR("");
 DATA(insert OID = 3741 (  thesaurus_lexize	PGNSP PGUID 12 1 0 f f t f i 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ thesaurus_lexize - _null_ ));
 DESCR("");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 514507d26f08ec283bc877bad425d5e05f964187..e3c0af870df9833051808da8229eebdd92163b21 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.83 2007/08/21 01:11:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.84 2007/08/22 01:39:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -121,6 +121,9 @@ extern void RemoveTSConfigurationById(Oid cfgId);
 extern void AlterTSConfiguration(AlterTSConfigurationStmt *stmt);
 extern void AlterTSConfigurationOwner(List *name, Oid newOwnerId);
 
+extern text *serialize_deflist(List *deflist);
+extern List *deserialize_deflist(Datum txt);
+
 /* support routines in commands/define.c */
 
 extern char *case_translate_language_name(const char *input);
diff --git a/src/include/tsearch/ts_public.h b/src/include/tsearch/ts_public.h
index 8e8fa5cc6ff0ee4109b2787a2f7145da3299db1e..718abdb61d4aee65c90bda693bae48b290f3f469 100644
--- a/src/include/tsearch/ts_public.h
+++ b/src/include/tsearch/ts_public.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1998-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/tsearch/ts_public.h,v 1.1 2007/08/21 01:11:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tsearch/ts_public.h,v 1.2 2007/08/22 01:39:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,16 +59,6 @@ typedef struct
 /*
  * Common useful things for tsearch subsystem
  */
-
-/* simple parser of cfg string looking like "key=val, key='val'" */
-typedef struct
-{
-	char	   *key;
-	char	   *value;
-} Map;
-
-extern void parse_keyvalpairs(text *in, Map ** m);
-
 extern char *get_tsearch_config_filename(const char *basename,
 										 const char *extension);