*** a/src/interfaces/libpq/Makefile
--- b/src/interfaces/libpq/Makefile
***************
*** 121,126 **** install: all installdirs install-lib
--- 121,129 ----
  	$(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
  	$(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'
  
+ check installcheck:
+ 	$(MAKE) -C test $@
+ 
  installdirs: installdirs-lib
  	$(MKDIR_P) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
  
***************
*** 132,137 **** uninstall: uninstall-lib
--- 135,141 ----
  	rm -f '$(DESTDIR)$(datadir)/pg_service.conf.sample'
  
  clean distclean: clean-lib
+ 	$(MAKE) -C test $@
  	rm -f $(OBJS) pthread.h libpq.rc
  # Might be left over from a Win32 client-only build
  	rm -f pg_config_paths.h
***************
*** 142,145 **** clean distclean: clean-lib
--- 146,150 ----
  	rm -f encnames.c wchar.c
  
  maintainer-clean: distclean maintainer-clean-lib
+ 	$(MAKE) -C test $@
  	rm -f libpq-dist.rc
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
***************
*** 282,287 **** static const PQEnvironmentOption EnvironmentOptions[] =
--- 282,290 ----
  	}
  };
  
+ /* The connection URI must start with either of the following designators: */
+ static const char uri_designator[] = "postgresql://";
+ static const char short_uri_designator[] = "postgres://";
  
  static bool connectOptions1(PGconn *conn, const char *conninfo);
  static bool connectOptions2(PGconn *conn);
***************
*** 297,303 **** static PQconninfoOption *conninfo_parse(const char *conninfo,
  static PQconninfoOption *conninfo_array_parse(const char *const * keywords,
  					 const char *const * values, PQExpBuffer errorMessage,
  					 bool use_defaults, int expand_dbname);
! static char *conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword);
  static void defaultNoticeReceiver(void *arg, const PGresult *res);
  static void defaultNoticeProcessor(void *arg, const char *message);
--- 300,325 ----
  static PQconninfoOption *conninfo_array_parse(const char *const * keywords,
  					 const char *const * values, PQExpBuffer errorMessage,
  					 bool use_defaults, int expand_dbname);
! static PQconninfoOption *conninfo_uri_parse(const char *uri,
! 				PQExpBuffer errorMessage);
! static bool conninfo_uri_parse_options(PQconninfoOption *options,
! 				const char *uri, char *buf,
! 				PQExpBuffer errorMessage);
! static bool conninfo_uri_parse_params(char *params,
! 				PQconninfoOption *connOptions,
! 				PQExpBuffer errorMessage);
! static char *conninfo_uri_decode(const char *str, PQExpBuffer errorMessage);
! static bool get_hexdigit(char digit, int *value);
! static const char *conninfo_getval(PQconninfoOption *connOptions,
! 				const char *keyword);
! static PQconninfoOption *conninfo_storeval(PQconninfoOption *connOptions,
! 				const char *keyword, const char *value,
! 				PQExpBuffer errorMessage, bool ignoreMissing);
! static PQconninfoOption *conninfo_store_uri_encoded_value(
! 				PQconninfoOption *connOptions,
! 				const char *keyword, const char *encoded_value,
! 				PQExpBuffer errorMessage, bool ignoreMissing);
! static PQconninfoOption *conninfo_find(PQconninfoOption *connOptions,
  				const char *keyword);
  static void defaultNoticeReceiver(void *arg, const PGresult *res);
  static void defaultNoticeProcessor(void *arg, const char *message);
***************
*** 580,586 **** PQconnectStart(const char *conninfo)
  static void
  fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
  {
! 	char	   *tmp;
  
  	/*
  	 * Move option values into conn structure
--- 602,608 ----
  static void
  fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
  {
! 	const char	   *tmp;
  
  	/*
  	 * Move option values into conn structure
***************
*** 3741,3747 **** ldapServiceLookup(const char *purl, PQconninfoOption *options,
  static int
  parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
  {
! 	char	   *service = conninfo_getval(options, "service");
  	char		serviceFile[MAXPGPATH];
  	char	   *env;
  	bool		group_found = false;
--- 3763,3769 ----
  static int
  parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
  {
! 	const char *service = conninfo_getval(options, "service");
  	char		serviceFile[MAXPGPATH];
  	char	   *env;
  	bool		group_found = false;
***************
*** 4131,4163 **** conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
  		}
  
  		/*
! 		 * Now we have the name and the value. Search for the param record.
! 		 */
! 		for (option = options; option->keyword != NULL; option++)
! 		{
! 			if (strcmp(option->keyword, pname) == 0)
! 				break;
! 		}
! 		if (option->keyword == NULL)
! 		{
! 			printfPQExpBuffer(errorMessage,
! 						 libpq_gettext("invalid connection option \"%s\"\n"),
! 							  pname);
! 			PQconninfoFree(options);
! 			free(buf);
! 			return NULL;
! 		}
! 
! 		/*
! 		 * Store the value
  		 */
! 		if (option->val)
! 			free(option->val);
! 		option->val = strdup(pval);
! 		if (!option->val)
  		{
- 			printfPQExpBuffer(errorMessage,
- 							  libpq_gettext("out of memory\n"));
  			PQconninfoFree(options);
  			free(buf);
  			return NULL;
--- 4153,4162 ----
  		}
  
  		/*
! 		 * Now that we have the name and the value, store the record.
  		 */
! 		if (!conninfo_storeval(options, pname, pval, errorMessage, false))
  		{
  			PQconninfoFree(options);
  			free(buf);
  			return NULL;
***************
*** 4278,4285 **** conninfo_array_parse(const char *const * keywords, const char *const * values,
  		/* first find "dbname" if any */
  		if (strcmp(pname, "dbname") == 0)
  		{
  			/* next look for "=" in the value */
! 			if (pvalue && strchr(pvalue, '='))
  			{
  				/*
  				 * Must be a conninfo string, so parse it, but do not use
--- 4277,4299 ----
  		/* first find "dbname" if any */
  		if (strcmp(pname, "dbname") == 0)
  		{
+ 			/*
+ 			 * Look for URI prefix.  This has to come before the check for "=",
+ 			 * since URI might as well contain "=" if extra parameters are
+ 			 * given.
+ 			 */
+ 			if (pvalue && (
+ 					(strncmp(pvalue, uri_designator,
+ 							 sizeof(uri_designator) - 1) == 0) ||
+ 					(strncmp(pvalue, short_uri_designator,
+ 							 sizeof(short_uri_designator) - 1) == 0)))
+ 			{
+ 				str_options = conninfo_uri_parse(pvalue, errorMessage);
+ 				if (str_options == NULL)
+ 					return NULL;
+ 			}
  			/* next look for "=" in the value */
! 			else if (pvalue && strchr(pvalue, '='))
  			{
  				/*
  				 * Must be a conninfo string, so parse it, but do not use
***************
*** 4454,4469 **** conninfo_array_parse(const char *const * keywords, const char *const * values,
  	return options;
  }
  
  static char *
  conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword)
  {
  	PQconninfoOption *option;
  
  	for (option = connOptions; option->keyword != NULL; option++)
  	{
  		if (strcmp(option->keyword, keyword) == 0)
! 			return option->val;
  	}
  
  	return NULL;
--- 4468,5043 ----
  	return options;
  }
  
+ /*
+  * Connection URI parser routine
+  *
+  * If successful, a malloc'd PQconninfoOption array is returned.
+  * If not successful, NULL is returned and an error message is
+  * left in errorMessage.
+  *
+  * This is only a wrapper for conninfo_uri_parse_options, which does all of the
+  * heavy lifting, while this function handles proper (de-)allocation of memory
+  * buffers.
+  */
+ static PQconninfoOption *
+ conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage)
+ {
+ 	PQconninfoOption *options;
+ 	char *buf;
+ 
+ 	resetPQExpBuffer(errorMessage);
+ 
+ 	/* Make a working copy of PQconninfoOptions */
+ 	options = malloc(sizeof(PQconninfoOptions));
+ 	if (options == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return NULL;
+ 	}
+ 	memcpy(options, PQconninfoOptions, sizeof(PQconninfoOptions));
+ 
+ 	/* Make a writable copy of the URI buffer */
+ 	buf = strdup(uri);
+ 	if (buf == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		free(options);
+ 		return NULL;
+ 	}
+ 
+ 	if (!conninfo_uri_parse_options(options, uri, buf, errorMessage))
+ 	{
+ 		free(options);
+ 		options = NULL;
+ 	}
+ 
+ 	free(buf);
+ 	return options;
+ }
+ 
+ /*
+  * Connection URI parser: actual parser routine
+  *
+  * If successful, returns true while the options array is filled with parsed
+  * options from the passed URI
+  * If not successful, returns false and fills errorMessage accordingly.
+  *
+  * Parses the connection URI string in 'buf' (while destructively modifying
+  * it,) according to the URI syntax below.  The 'uri' parameter is only used
+  * for error reporting and 'buf' should initially contain a writable copy of
+  * 'uri'.
+  *
+  * The general form for connection URI is the following:
+  *
+  *   postgresql://user:pw@host:port/database
+  *
+  * To specify a IPv6 host address, enclose the address in square brackets:
+  *
+  *   postgresql://[::1]/database
+  *
+  * Connection parameters may follow the base URI using this syntax:
+  *
+  *   postgresql://host/database?param1=value1&param2=value2&...
+  */
+ static bool
+ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
+ 						   char *buf, PQExpBuffer errorMessage)
+ {
+ 	char *p;
+ 	char *start = buf;
+ 	char lastc = '\0';
+ 	char *user = NULL;
+ 
+ 	/* Assume URI prefix is already verified by the caller */
+ 	if (strncmp(uri, uri_designator, sizeof(uri_designator) - 1) == 0)
+ 	{
+ 		start += sizeof(uri_designator) - 1;
+ 	}
+ 	else if (strncmp(uri, short_uri_designator,
+ 					 sizeof(short_uri_designator) - 1) == 0)
+ 	{
+ 		start += sizeof(short_uri_designator) - 1;
+ 	}
+ 	else
+ 	{
+ 		/* Should never happen */
+ 		printfPQExpBuffer(errorMessage,
+ 						  libpq_gettext("invalid URI \"%s\" propagated to internal parser routine"),
+ 						  uri);
+ 		return false;
+ 	}
+ 	p = start;
+ 
+ 	/* Check for local unix socket dir at start of URI */
+ 	if (*p == '/')
+ 	{
+ 		/* Look for possible query parameters */
+ 		while (*p && *p != '?')
+ 			++p;
+ 		lastc = *p;
+ 		*p = '\0';
+ 
+ 		if (!conninfo_store_uri_encoded_value(options, "host", start,
+ 											  errorMessage, false))
+ 			return false;
+ 	}
+ 	else
+ 	{
+ 		/* Not a unix socket dir: parse as host name/address */
+ 		const char *host;
+ 
+ 		/* Look ahead for possible user credentials designator */
+ 		while (*p && *p != '@' && *p != '/')
+ 			++p;
+ 		if (*p != '@')
+ 		{
+ 			/*
+ 			 * No username/password designator found.
+ 			 *
+ 			 * Reset to start of URI and parse as "scheme://netloc/..."
+ 			 * instead.
+ 			 */
+ 			p = start;
+ 		}
+ 		else
+ 		{
+ 			/*
+ 			 * Found username/password designator, so URI should be of the form
+ 			 * "scheme://user@netloc" or "scheme://user:password@netloc".
+ 			 */
+ 			user = start;
+ 
+ 			p = user;
+ 			while (*p != ':' && *p != '@')
+ 				++p;
+ 
+ 			/* Save last char and cut off at end of user name */
+ 			lastc = *p;
+ 			*p = '\0';
+ 
+ 			if (!*user)
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("missing username specifier in URI: %s\n"),
+ 								  uri);
+ 				return false;
+ 			}
+ 			if (!conninfo_store_uri_encoded_value(options, "user", user,
+ 												  errorMessage, false))
+ 				return false;
+ 
+ 			if (lastc == ':')
+ 			{
+ 				const char *password = p + 1;
+ 
+ 				while (*p != '@')
+ 					++p;
+ 				*p = '\0';
+ 
+ 				if (!*password)
+ 				{
+ 					printfPQExpBuffer(errorMessage,
+ 									  libpq_gettext("missing password specifier in URI: %s\n"),
+ 									  uri);
+ 					return false;
+ 				}
+ 				if (!conninfo_store_uri_encoded_value(options, "password",
+ 													  password,
+ 													  errorMessage, false))
+ 					return false;
+ 			}
+ 
+ 			/* Advance past end of parsed user name or password token */
+ 			++p;
+ 		}
+ 
+ 		/*
+ 		 * "p" has been incremented past optional URI credential information at
+ 		 * this point and now points at the "netloc" part of the URI.
+ 		 *
+ 		 * Look for IPv6 address 
+ 		 */
+ 		if (*p == '[')
+ 		{
+ 			host = ++p;
+ 			while (*p && *p != ']')
+ 				++p;
+ 			if (!*p)
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("end of string reached when looking for matching ']' in IPv6 host address in URI: %s\n"),
+ 								  uri);
+ 				return false;
+ 			}
+ 			if (p == host)
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("IPv6 host address may not be empty in URI: %s\n"),
+ 								  uri);
+ 				return false;
+ 			}
+ 
+ 			/* Cut off the bracket and advance */
+ 			*(p++) = '\0';
+ 
+ 			/* 
+ 			 * The address must be followed by a port specifier or a slash or a
+ 			 * query.
+ 			 */
+ 			if (*p && *p != ':' && *p != '/' && *p != '?')
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("unexpected '%c' at position %td in URI (expecting ':' or '/'): %s\n"),
+ 								  *p, p - buf + 1, uri);
+ 				return false;
+ 			}
+ 		}
+ 		else
+ 		{
+ 			/* not an IPv6 address: DNS-named or IPv4 netloc */
+ 			host = p;
+ 
+ 			/*
+ 			 * Look for port specifier (colon) or end of host specifier
+ 			 * (slash), or query (question mark).
+ 			 */
+ 			while (*p && *p != ':' && *p != '/' && *p != '?')
+ 				++p;
+ 		}
+ 
+ 		/* Save the hostname terminator before we null it */
+ 		lastc = *p;
+ 		*p = '\0';
+ 
+ 		if (user && !*host)
+ 		{
+ 			printfPQExpBuffer(errorMessage,
+ 							  libpq_gettext("must specify host when using username specifier in URI: %s\n"),
+ 							  uri);
+ 			return false;
+ 		}
+ 		if (!conninfo_store_uri_encoded_value(options, "host", host,
+ 											  errorMessage, false))
+ 			return false;
+ 
+ 		if (lastc == ':')
+ 		{
+ 			const char *port = ++p; /* advance past host terminator */
+ 
+ 			while (*p && *p != '/' && *p != '?')
+ 				++p;
+ 
+ 			lastc = *p;
+ 			*p = '\0';
+ 
+ 			if (!*port)
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("missing port specifier in URI: %s\n"),
+ 								  uri);
+ 				return false;
+ 			}
+ 			if (!conninfo_store_uri_encoded_value(options, "port", port,
+ 												  errorMessage, false))
+ 				return false;
+ 		}
+ 	}
+ 
+ 	if (lastc && lastc != '?')
+ 	{
+ 		const char *dbname = ++p; /* advance past host terminator */
+ 
+ 		/* Look for query parameters */
+ 		while (*p && *p != '?')
+ 			++p;
+ 
+ 		lastc = *p;
+ 		*p = '\0';
+ 
+ 		/*
+ 		 * Avoid setting dbname to an empty string, as it forces the default
+ 		 * value (username) and ignores $PGDATABASE, as opposed to not setting
+ 		 * it at all.
+ 		 */
+ 		if (*dbname &&
+ 			!conninfo_store_uri_encoded_value(options, "dbname", dbname,
+ 											  errorMessage, false))
+ 			return false;
+ 	}
+ 
+ 	if (lastc)
+ 	{
+ 		++p; /* advance past terminator */
+ 
+ 		if (!conninfo_uri_parse_params(p, options, errorMessage))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Connection URI parameters parser routine
+  *
+  * If successful, returns true while connOptions is filled with parsed
+  * parameters.
+  * If not successful, returns false and fills errorMessage accordingly.
+  *
+  * Destructively modifies 'params' buffer.
+  */
+ static bool
+ conninfo_uri_parse_params(char *params,
+ 						  PQconninfoOption *connOptions,
+ 						  PQExpBuffer errorMessage)
+ {
+ 	char *token;
+ 	char *savep;
+ 
+ 	while ((token = strtok_r(params, "&", &savep)))
+ 	{
+ 		const char *keyword;
+ 		const char *value;
+ 		char *p = strchr(token, '=');
+ 
+ 		if (p == NULL)
+ 		{
+ 			printfPQExpBuffer(errorMessage,
+ 							  libpq_gettext("missing key/value separator '=' in URI query parameter: %s\n"),
+ 							  token);
+ 			return false;
+ 		}
+ 
+ 		/* Cut off keyword and advance to value */
+ 		*(p++) = '\0';
+ 
+ 		keyword = token;
+ 		value = p;
+ 
+ 		/* Special keyword handling for improved JDBC compatibility */
+ 		if (strcmp(keyword, "ssl") == 0 &&
+ 			strcmp(value, "true") == 0)
+ 		{
+ 			keyword = "sslmode";
+ 			value = "require";
+ 		}
+ 
+ 		/*
+ 		 * Store the value if corresponding option was found -- ignore
+ 		 * otherwise.
+ 		 *
+ 		 * In theory, the keyword might be also percent-encoded, but hardly
+ 		 * that's ever needed by anyone.
+ 		 */
+ 		if (!conninfo_store_uri_encoded_value(connOptions, keyword, value,
+ 											  errorMessage, true))
+ 		{
+ 			fprintf(stderr,
+ 					libpq_gettext("WARNING: ignoring unrecognized URI query parameter: %s\n"),
+ 					keyword);
+ 		}
+ 
+ 		params = NULL; /* proceed to the next token with strtok_r */
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Connection URI decoder routine
+  *
+  * If successful, returns the malloc'd decoded string.
+  * If not successful, returns NULL and fills errorMessage accordingly.
+  *
+  * The string is decoded by replacing any percent-encoded tokens with
+  * corresponding characters, while preserving any non-encoded characters.  A
+  * percent-encoded token is a character triplet: a percent sign, followed by a
+  * pair of hexadecimal digits (0-9A-F), where lower- and upper-case letters are
+  * treated identically.
+  */
  static char *
+ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
+ {
+ 	char *buf = malloc(strlen(str) + 1);
+ 	char *p = buf;
+ 	const char *q = str;
+ 
+ 	if (buf == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return NULL;
+ 	}
+ 
+ 	for (;;)
+ 	{
+ 		if (*q != '%')
+ 		{
+ 			/* copy and check for NUL terminator */
+ 			if (!(*(p++) = *(q++)))
+ 				break;
+ 		}
+ 		else
+ 		{
+ 			int hi;
+ 			int lo;
+ 
+ 			++q; /* skip the percent sign itself */
+ 
+ 			/*
+ 			 * Possible EOL will be caught by the first call to get_hexdigit(),
+ 			 * so we never dereference an invalid q pointer.
+ 			 */
+ 			if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("invalid percent-encoded token (a pair of hexadecimal digits expected after every '%%' symbol, use '%%25' to encode percent symbol itself): %s\n"),
+ 								  str);
+ 				free(buf);
+ 				return NULL;
+ 			}
+ 
+ 			*(p++) = (hi << 4) | lo;
+ 		}
+ 	}
+ 
+ 	return buf;
+ }
+ 
+ /*
+  * Convert hexadecimal digit character to it's integer value.
+  *
+  * If successful, returns true and value is filled with digit's base 16 value.
+  * If not successful, returns false.
+  *
+  * Lower- and upper-case letters in the range A-F are treated identically.
+  */
+ static bool
+ get_hexdigit(char digit, int *value)
+ {
+ 	if ('0' <= digit && digit <= '9')
+ 	{
+ 		*value = digit - '0';
+ 	}
+ 	else if ('A' <= digit && digit <= 'F')
+ 	{
+ 		*value = digit - 'A' + 10;
+ 	}
+ 	else if ('a' <= digit && digit <= 'f')
+ 	{
+ 		*value = digit - 'a' + 10;
+ 	}
+ 	else
+ 	{
+ 		return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Find an option value corresponding to the keyword in the connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding option's value.
+  * If not successful, returns NULL.
+  */
+ static const char *
  conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword)
  {
+ 	PQconninfoOption *option = conninfo_find(connOptions, keyword);
+ 
+ 	return option ? option->val : NULL;
+ }
+ 
+ /*
+  * Store a (new) value for an option corresponding to the keyword in
+  * connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding PQconninfoOption,
+  * which value is replaced with a strdup'd copy of the passed value string.
+  * The existing value for the option is free'd before replacing, if any.
+  *
+  * If not successful, returns NULL and fills errorMessage accordingly.
+  */
+ static PQconninfoOption *
+ conninfo_storeval(PQconninfoOption *connOptions,
+ 				  const char *keyword, const char *value,
+ 				  PQExpBuffer errorMessage, bool ignoreMissing)
+ {
+ 	PQconninfoOption *option = conninfo_find(connOptions, keyword);
+ 	char *value_copy;
+ 
+ 	if (option == NULL)
+ 	{
+ 		if (!ignoreMissing)
+ 			printfPQExpBuffer(errorMessage,
+ 							  libpq_gettext("invalid connection option \"%s\"\n"),
+ 							  keyword);
+ 		return NULL;
+ 	}
+ 
+ 	value_copy = strdup(value);
+ 	if (value_copy == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return NULL;
+ 	}
+ 
+ 	if (option->val)
+ 		free(option->val);
+ 	option->val = value_copy;
+ 
+ 	return option;
+ }
+ 
+ /*
+  * Store a (new) possibly URI-encoded value for an option corresponding to the
+  * keyword in connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding PQconninfoOption,
+  * which value is replaced with a URI-decoded copy of the passed value string.
+  * The existing value for the option is free'd before replacing, if any.
+  *
+  * If not successful, returns NULL and fills errorMessage accordingly.  If the
+  * corresponding option was not found, but ignoreMissing is true: doesn't fill
+  * errorMessage.
+  *
+  * See also conninfo_storeval.
+  */
+ static PQconninfoOption *
+ conninfo_store_uri_encoded_value(PQconninfoOption *connOptions,
+ 								 const char *keyword, const char *encoded_value,
+ 								 PQExpBuffer errorMessage, bool ignoreMissing)
+ {
+ 	PQconninfoOption *option;
+ 	char *decoded_value = conninfo_uri_decode(encoded_value, errorMessage);
+ 
+ 	if (decoded_value == NULL)
+ 		return NULL;
+ 
+ 	option = conninfo_storeval(connOptions, keyword, decoded_value,
+ 							   errorMessage, ignoreMissing);
+ 	free(decoded_value);
+ 
+ 	return option;
+ }
+ 
+ /*
+  * Find a PQconninfoOption option corresponding to the keyword in the
+  * connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding PQconninfoOption
+  * structure.
+  * If not successful, returns NULL.
+  */
+ static PQconninfoOption *
+ conninfo_find(PQconninfoOption *connOptions, const char *keyword)
+ {
  	PQconninfoOption *option;
  
  	for (option = connOptions; option->keyword != NULL; option++)
  	{
  		if (strcmp(option->keyword, keyword) == 0)
! 			return option;
  	}
  
  	return NULL;
*** /dev/null
--- b/src/interfaces/libpq/test/Makefile
***************
*** 0 ****
--- 1,11 ----
+ subdir = src/interfaces/libpq/test
+ top_builddir = ../../../..
+ include $(top_builddir)/src/Makefile.global
+ 
+ all: installcheck
+ 
+ installcheck:
+ 	BINDIR='$(bindir)' SUBDIR='$(subdir)' $(SHELL) ./regress.sh
+ 
+ clean distclean maintainer-clean:
+ 	rm -f regress.out regress.diff
*** /dev/null
--- b/src/interfaces/libpq/test/README
***************
*** 0 ****
--- 1,20 ----
+ This is a testsuite for testing libpq URI connection string syntax.
+ 
+ To run the suite, use 'make installcheck' command.  It works by
+ running regress.sh from this directory with appropriate environment
+ set up.
+ 
+ The test itself works by running the psql command installed by 'make
+ install' with different -d option arguments and collecting the output,
+ then comparing that to the expected 'correct' output.
+ 
+ The test script assumes that the server is running on localhost and
+ accepting local and IPv4,6 connections on port $PGPORT.  Attempts to
+ connect are made from user $PGUSER (which defaults to $USER,) to the
+ database specified in $PGDATABASE (which defaults to "regression".)
+ The database is created by the script prior to running the actual
+ tests.
+ 
+ The connection URI lines to test are read from the file 'regress.in',
+ and are eval-d before passing them to psql (like `eval echo $line`,)
+ thus ${variable} substitutions are carried out.
*** /dev/null
--- b/src/interfaces/libpq/test/expected.out
***************
*** 0 ****
--- 1,37 ----
+ postgresql://${PGUSER}@localhost:${PGPORT}/${PGDATABASE}
+ postgresql://${PGUSER}@localhost/${PGDATABASE}
+ postgresql://localhost:${PGPORT}/${PGDATABASE}
+ postgresql://localhost/${PGDATABASE}
+ postgresql://${PGUSER}@localhost:${PGPORT}/
+ postgresql://${PGUSER}@localhost/
+ postgresql://localhost:${PGPORT}/
+ postgresql://localhost:${PGPORT}
+ postgresql://localhost/${PGDATABASE}
+ postgresql://localhost/
+ postgresql://localhost
+ postgresql://
+ postgresql://%6Cocalhost/
+ postgresql://localhost/${PGDATABASE}?user=${PGUSER}
+ postgresql://localhost/${PGDATABASE}?user=${PGUSER}&port=${PGPORT}
+ postgresql://localhost/${PGDATABASE}?user=${PGUSER}&port=${PGPORT}
+ postgresql://localhost:${PGPORT}?user=${PGUSER}
+ postgresql://localhost?user=${PGUSER}
+ postgresql://localhost?
+ postgresql://[::1]:${PGPORT}/${PGDATABASE}
+ postgresql://[::1]/${PGDATABASE}
+ postgresql://[::1]/
+ postgresql://[::1]
+ postgres://
+ postgres:///tmp
+ WARNING: ignoring unrecognized URI query parameter: uzer
+ postgresql://localhost?uzer=
+ psql: FATAL:  database "postgre://" does not exist
+ psql: end of string reached when looking for matching ']' in IPv6 host address in URI: postgres://[::1
+ psql: IPv6 host address may not be empty in URI: postgres://[]
+ psql: unexpected 'z' at position 17 in URI (expecting ':' or '/'): postgres://[::1]z
+ psql: missing key/value separator '=' in URI query parameter: zzz
+ psql: invalid percent-encoded token (a pair of hexadecimal digits expected after every '%' symbol, use '%25' to encode percent symbol itself): %zz
+ psql: invalid percent-encoded token (a pair of hexadecimal digits expected after every '%' symbol, use '%25' to encode percent symbol itself): %1
+ psql: invalid percent-encoded token (a pair of hexadecimal digits expected after every '%' symbol, use '%25' to encode percent symbol itself): %
+ psql: missing username specifier in URI: postgres://@localhost
+ psql: missing port specifier in URI: postgres://localhost:/
*** /dev/null
--- b/src/interfaces/libpq/test/regress.in
***************
*** 0 ****
--- 1,36 ----
+ postgresql://${PGUSER}@localhost:${PGPORT}/${PGDATABASE}
+ postgresql://${PGUSER}@localhost/${PGDATABASE}
+ postgresql://localhost:${PGPORT}/${PGDATABASE}
+ postgresql://localhost/${PGDATABASE}
+ postgresql://${PGUSER}@localhost:${PGPORT}/
+ postgresql://${PGUSER}@localhost/
+ postgresql://localhost:${PGPORT}/
+ postgresql://localhost:${PGPORT}
+ postgresql://localhost/${PGDATABASE}
+ postgresql://localhost/
+ postgresql://localhost
+ postgresql://
+ postgresql://%6Cocalhost/
+ postgresql://localhost/${PGDATABASE}?user=${PGUSER}
+ postgresql://localhost/${PGDATABASE}?user=${PGUSER}&port=${PGPORT}
+ postgresql://localhost/${PGDATABASE}?user=${PGUSER}&port=${PGPORT}
+ postgresql://localhost:${PGPORT}?user=${PGUSER}
+ postgresql://localhost?user=${PGUSER}
+ postgresql://localhost?
+ postgresql://[::1]:${PGPORT}/${PGDATABASE}
+ postgresql://[::1]/${PGDATABASE}
+ postgresql://[::1]/
+ postgresql://[::1]
+ postgres://
+ postgres:///tmp
+ postgresql://localhost?uzer=
+ postgre://
+ postgres://[::1
+ postgres://[]
+ postgres://[::1]z
+ postgresql://localhost?zzz
+ postgresql://%zz
+ postgresql://%1
+ postgresql://%
+ postgres://@localhost
+ postgres://localhost:/
*** /dev/null
--- b/src/interfaces/libpq/test/regress.sh
***************
*** 0 ****
--- 1,38 ----
+ #!/bin/sh
+ if [ -z "$PGUSER" ]; then
+ PGUSER=$USER
+ fi
+ if [ -z "$PGPORT" ]; then
+ PGPORT=5432
+ fi
+ if [ -z "$PGDATABASE" ]; then
+ PGDATABASE=regression
+ fi
+ export PGUSER PGPORT PGDATABASE
+ 
+ "${BINDIR}/createdb" "${PGDATABASE}"
+ 
+ echo "Running libpq URI support test..."
+ 
+ while read line
+ do
+     # Frist, expand variables in the test URI line.
+     uri=$(eval echo "$line")
+ 
+     # But SELECT the original line, so test result doesn't depend on
+     # the substituted values.
+     "${BINDIR}/psql" -d "$uri" -At -c "SELECT '$line'"
+ done <regress.in >regress.out 2>&1
+ 
+ if diff -c expected.out regress.out >regress.diff; then
+     echo "========================================"
+     echo "All tests passed"
+     exit 0
+ else
+     echo "========================================"
+     echo "FAILED: the test result differs from the expected output"
+     echo
+     echo "Review the difference in ${SUBDIR}/regress.diff"
+     echo "========================================"
+     exit 1
+ fi