diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 394aa87900286a1bc289fc9ff6327b89ffa33c27..50924a78f0e79da1c0bc264d0aa692721698dbe9 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -368,8 +368,8 @@
      installation's <literal>SHAREDIR/extension</literal> directory.  There
      must also be at least one <acronym>SQL</> script file, which follows the
      naming pattern
-     <literal><replaceable>extension</>-<replaceable>version</>.sql</literal>
-     (for example, <literal>foo-1.0.sql</> for version <literal>1.0</> of
+     <literal><replaceable>extension</>--<replaceable>version</>.sql</literal>
+     (for example, <literal>foo--1.0.sql</> for version <literal>1.0</> of
      extension <literal>foo</>).  By default, the script file(s) are also
      placed in the <literal>SHAREDIR/extension</literal> directory; but the
      control file can specify a different directory for the script file(s).
@@ -378,7 +378,7 @@
     <para>
      The file format for an extension control file is the same as for the
      <filename>postgresql.conf</> file, namely a list of
-     <replaceable>parameter-name</> <literal>=</> <replaceable>value</>
+     <replaceable>parameter_name</> <literal>=</> <replaceable>value</>
      assignments, one per line.  Blank lines and comments introduced by
      <literal>#</> are allowed.  Be sure to quote any value that is not
      a single word or number.
@@ -477,7 +477,7 @@
      In addition to the primary control file
      <literal><replaceable>extension</>.control</literal>,
      an extension can have secondary control files named in the style
-     <literal><replaceable>extension</>-<replaceable>version</>.control</literal>.
+     <literal><replaceable>extension</>--<replaceable>version</>.control</literal>.
      If supplied, these must be located in the script file directory.
      Secondary control files follow the same format as the primary control
      file.  Any parameters set in a secondary control file override the
@@ -671,15 +671,15 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
      dynamically from one version to the next, you should provide
      <firstterm>update scripts</> that make the necessary changes to go from
      one version to the next.  Update scripts have names following the pattern
-     <literal><replaceable>extension</>-<replaceable>oldversion</>-<replaceable>newversion</>.sql</literal>
-     (for example, <literal>foo-1.0-1.1.sql</> contains the commands to modify
+     <literal><replaceable>extension</>--<replaceable>oldversion</>--<replaceable>newversion</>.sql</literal>
+     (for example, <literal>foo--1.0--1.1.sql</> contains the commands to modify
      version <literal>1.0</> of extension <literal>foo</> into version
      <literal>1.1</>).
     </para>
 
     <para>
      Given that a suitable update script is available, the command
-     <command>ALTER EXTENSION ... UPDATE</> will update an installed extension
+     <command>ALTER EXTENSION UPDATE</> will update an installed extension
      to the specified new version.  The update script is run in the same
      environment that <command>CREATE EXTENSION</> provides for installation
      scripts: in particular, <varname>search_path</> is set up in the same
@@ -712,7 +712,7 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
      class="parameter">old_version</> option, which causes it to not run the
      normal installation script for the target version, but instead the update
      script named
-     <literal><replaceable>extension</>-<replaceable>old_version</>-<replaceable>target_version</>.sql</literal>.
+     <literal><replaceable>extension</>--<replaceable>old_version</>--<replaceable>target_version</>.sql</literal>.
      The choice of the dummy version name to use as <replaceable
      class="parameter">old_version</> is up to the extension author, though
      <literal>unpackaged</> is a common convention.  If you have multiple
@@ -723,7 +723,7 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
     <para>
      <command>ALTER EXTENSION</> is able to execute sequences of update
      script files to achieve a requested update.  For example, if only
-     <literal>foo-1.0-1.1.sql</> and <literal>foo-1.1-2.0.sql</> are
+     <literal>foo--1.0--1.1.sql</> and <literal>foo--1.1--2.0.sql</> are
      available, <command>ALTER EXTENSION</> will apply them in sequence if an
      update to version <literal>2.0</> is requested when <literal>1.0</> is
      currently installed.
@@ -734,11 +734,13 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
      of version names: for example, it does not know whether <literal>1.1</>
      follows <literal>1.0</>.  It just matches up the available version names
      and follows the path that requires applying the fewest update scripts.
+     (A version name can actually be any string that doesn't contain
+     <literal>--</> or leading or trailing <literal>-</>.)
     </para>
 
     <para>
      Sometimes it is useful to provide <quote>downgrade</> scripts, for
-     example <literal>foo-1.1-1.0.sql</> to allow reverting the changes
+     example <literal>foo--1.1--1.0.sql</> to allow reverting the changes
      associated with version <literal>1.1</>.  If you do that, be careful
      of the possibility that a downgrade script might unexpectedly
      get applied because it yields a shorter path.  The risky case is where
@@ -761,7 +763,7 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
     </para>
 
     <para>
-     The script file <filename>pair-1.0.sql</> looks like this:
+     The script file <filename>pair--1.0.sql</> looks like this:
 
 <programlisting><![CDATA[
 CREATE TYPE pair AS ( k text, v text );
@@ -803,7 +805,7 @@ relocatable = true
 
 <programlisting>
 EXTENSION = pair
-DATA = pair-1.0.sql
+DATA = pair--1.0.sql
 
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
@@ -860,7 +862,7 @@ include $(PGXS)
 <programlisting>
 MODULES = isbn_issn
 EXTENSION = isbn_issn
-DATA_built = isbn_issn-1.0.sql
+DATA = isbn_issn--1.0.sql
 DOCS = README.isbn_issn
 
 PG_CONFIG = pg_config
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 99aface408f29a130a80b8d5884a394790d2835a..0661303fea3bf330fc30fc5d7c8655dcb8a73b46 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -57,9 +57,6 @@
 bool			creating_extension = false;
 Oid				CurrentExtensionObject = InvalidOid;
 
-/* Character that separates extension & version names in a script filename */
-#define EXT_VERSION_SEP  '-'
-
 /*
  * Internal data structure to hold the results of parsing a control file
  */
@@ -225,9 +222,42 @@ get_extension_schema(Oid ext_oid)
 static void
 check_valid_extension_name(const char *extensionname)
 {
+	int			namelen = strlen(extensionname);
+
 	/*
-	 * No directory separators (this is sufficient to prevent ".." style
-	 * attacks).
+	 * Disallow empty names (the parser rejects empty identifiers anyway,
+	 * but let's check).
+	 */
+	if (namelen == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension name: \"%s\"", extensionname),
+				 errdetail("Extension names must not be empty.")));
+
+	/*
+	 * No double dashes, since that would make script filenames ambiguous.
+	 */
+	if (strstr(extensionname, "--"))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension name: \"%s\"", extensionname),
+				 errdetail("Extension names must not contain \"--\".")));
+
+	/*
+	 * No leading or trailing dash either.  (We could probably allow this,
+	 * but it would require much care in filename parsing and would make
+	 * filenames visually if not formally ambiguous.  Since there's no
+	 * real-world use case, let's just forbid it.)
+	 */
+	if (extensionname[0] == '-' || extensionname[namelen - 1] == '-')
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension name: \"%s\"", extensionname),
+				 errdetail("Extension names must not begin or end with \"-\".")));
+
+	/*
+	 * No directory separators either (this is sufficient to prevent ".."
+	 * style attacks).
 	 */
 	if (first_dir_separator(extensionname) != NULL)
 		ereport(ERROR,
@@ -239,16 +269,39 @@ check_valid_extension_name(const char *extensionname)
 static void
 check_valid_version_name(const char *versionname)
 {
-	/* No separators --- would risk confusion of install vs update scripts */
-	if (strchr(versionname, EXT_VERSION_SEP))
+	int			namelen = strlen(versionname);
+
+	/*
+	 * Disallow empty names (we could possibly allow this, but there seems
+	 * little point).
+	 */
+	if (namelen == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension version name: \"%s\"", versionname),
+				 errdetail("Version names must not be empty.")));
+
+	/*
+	 * No double dashes, since that would make script filenames ambiguous.
+	 */
+	if (strstr(versionname, "--"))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension version name: \"%s\"", versionname),
+				 errdetail("Version names must not contain \"--\".")));
+
+	/*
+	 * No leading or trailing dash either.
+	 */
+	if (versionname[0] == '-' || versionname[namelen - 1] == '-')
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("invalid extension version name: \"%s\"", versionname),
-				 errdetail("Version names must not contain the character \"%c\".",
-						   EXT_VERSION_SEP)));
+				 errdetail("Version names must not begin or end with \"-\".")));
+
 	/*
-	 * No directory separators (this is sufficient to prevent ".." style
-	 * attacks).
+	 * No directory separators either (this is sufficient to prevent ".."
+	 * style attacks).
 	 */
 	if (first_dir_separator(versionname) != NULL)
 		ereport(ERROR,
@@ -336,8 +389,8 @@ get_extension_aux_control_filename(ExtensionControlFile *control,
 	scriptdir = get_extension_script_directory(control);
 
 	result = (char *) palloc(MAXPGPATH);
-	snprintf(result, MAXPGPATH, "%s/%s%c%s.control",
-			 scriptdir, control->name, EXT_VERSION_SEP, version);
+	snprintf(result, MAXPGPATH, "%s/%s--%s.control",
+			 scriptdir, control->name, version);
 
 	pfree(scriptdir);
 
@@ -355,12 +408,11 @@ get_extension_script_filename(ExtensionControlFile *control,
 
 	result = (char *) palloc(MAXPGPATH);
 	if (from_version)
-		snprintf(result, MAXPGPATH, "%s/%s%c%s%c%s.sql",
-				 scriptdir, control->name, EXT_VERSION_SEP, from_version,
-				 EXT_VERSION_SEP, version);
+		snprintf(result, MAXPGPATH, "%s/%s--%s--%s.sql",
+				 scriptdir, control->name, from_version, version);
 	else
-		snprintf(result, MAXPGPATH, "%s/%s%c%s.sql",
-				 scriptdir, control->name, EXT_VERSION_SEP, version);
+		snprintf(result, MAXPGPATH, "%s/%s--%s.sql",
+				 scriptdir, control->name, version);
 
 	pfree(scriptdir);
 
@@ -426,7 +478,7 @@ parse_extension_control_file(ExtensionControlFile *control,
 			if (version)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+						 errmsg("parameter \"%s\" cannot be set in a secondary extension control file",
 								item->name)));
 
 			control->directory = pstrdup(item->value);
@@ -436,7 +488,7 @@ parse_extension_control_file(ExtensionControlFile *control,
 			if (version)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+						 errmsg("parameter \"%s\" cannot be set in a secondary extension control file",
 								item->name)));
 
 			control->default_version = pstrdup(item->value);
@@ -907,16 +959,18 @@ get_ext_ver_list(ExtensionControlFile *control)
 
 		/* ... matching extension name followed by separator */
 		if (strncmp(de->d_name, control->name, extnamelen) != 0 ||
-			de->d_name[extnamelen] != EXT_VERSION_SEP)
+			de->d_name[extnamelen] != '-' ||
+			de->d_name[extnamelen + 1] != '-')
 			continue;
 
-		/* extract version names from 'extname-something.sql' filename */
-		vername = pstrdup(de->d_name + extnamelen + 1);
+		/* extract version names from 'extname--something.sql' filename */
+		vername = pstrdup(de->d_name + extnamelen + 2);
 		*strrchr(vername, '.') = '\0';
-		vername2 = strchr(vername, EXT_VERSION_SEP);
+		vername2 = strstr(vername, "--");
 		if (!vername2)
 			continue;			/* it's not an update script */
-		*vername2++ = '\0';
+		*vername2 = '\0';		/* terminate first version */
+		vername2 += 2;			/* and point to second */
 
 		/* Create ExtensionVersionInfos and link them together */
 		evi = get_ext_ver_info(vername, &evi_list);
@@ -979,6 +1033,20 @@ identify_update_path(ExtensionControlFile *control,
 				evi2->distance = newdist;
 				evi2->previous = evi;
 			}
+			else if (newdist == evi2->distance &&
+					 evi2->previous != NULL &&
+					 strcmp(evi->name, evi2->previous->name) < 0)
+			{
+				/*
+				 * Break ties in favor of the version name that comes first
+				 * according to strcmp().  This behavior is undocumented and
+				 * users shouldn't rely on it.  We do it just to ensure that
+				 * if there is a tie, the update path that is chosen does not
+				 * depend on random factors like the order in which directory
+				 * entries get visited.
+				 */
+				evi2->previous = evi;
+			}
 		}
 	}
 
@@ -1251,7 +1319,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 										requiredExtensions);
 
 	/*
-	 * Apply any comment on extension
+	 * Apply any control-file comment on extension
 	 */
 	if (control->comment != NULL)
 		CreateComments(extensionOid, ExtensionRelationId, 0, control->comment);
@@ -1544,6 +1612,10 @@ pg_available_extensions(PG_FUNCTION_ARGS)
 			extname = pstrdup(de->d_name);
 			*strrchr(extname, '.') = '\0';
 
+			/* ignore it if it's an auxiliary control file */
+			if (strstr(extname, "--"))
+				continue;
+
 			control = read_extension_control_file(extname);
 
 			memset(values, 0, sizeof(values));