*** 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); *************** *** 293,298 **** static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions); --- 296,305 ---- static void freePGconn(PGconn *conn); static void closePGconn(PGconn *conn); static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage); + static PQconninfoOption *parse_connection_string(const char *conninfo, + PQExpBuffer errorMessage, bool use_defaults); + static int uri_prefix_length(const char *connstr); + static bool recognized_connection_string(const char *connstr); static PQconninfoOption *conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, bool use_defaults); static PQconninfoOption *conninfo_array_parse(const char *const * keywords, *************** *** 300,306 **** static PQconninfoOption *conninfo_array_parse(const char *const * keywords, bool use_defaults, int expand_dbname); static bool conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage); ! 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); --- 307,332 ---- bool use_defaults, int expand_dbname); static bool conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage); ! static PQconninfoOption *conninfo_uri_parse(const char *uri, ! PQExpBuffer errorMessage, bool use_defaults); ! 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); *************** *** 583,589 **** PQconnectStart(const char *conninfo) static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions) { ! char *tmp; /* * Move option values into conn structure --- 609,615 ---- static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions) { ! const char *tmp; /* * Move option values into conn structure *************** *** 680,686 **** connectOptions1(PGconn *conn, const char *conninfo) /* * Parse the conninfo string */ ! connOptions = conninfo_parse(conninfo, &conn->errorMessage, true); if (connOptions == NULL) { conn->status = CONNECTION_BAD; --- 706,712 ---- /* * Parse the conninfo string */ ! connOptions = parse_connection_string(conninfo, &conn->errorMessage, true); if (connOptions == NULL) { conn->status = CONNECTION_BAD; *************** *** 881,889 **** PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, return NULL; /* ! * If the dbName parameter contains '=', assume it's a conninfo string. */ ! if (dbName && strchr(dbName, '=')) { if (!connectOptions1(conn, dbName)) return conn; --- 907,916 ---- return NULL; /* ! * If the dbName parameter contains a connection string, parse it into conn ! * struct using connectOptions1. */ ! if (dbName && recognized_connection_string(dbName)) { if (!connectOptions1(conn, dbName)) return conn; *************** *** 3756,3762 **** 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; --- 3783,3789 ---- static int parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage) { ! const char *service = conninfo_getval(options, "service"); char serviceFile[MAXPGPATH]; char *env; bool group_found = false; *************** *** 3991,3997 **** PQconninfoParse(const char *conninfo, char **errmsg) initPQExpBuffer(&errorBuf); if (PQExpBufferDataBroken(errorBuf)) return NULL; /* out of memory already :-( */ ! connOptions = conninfo_parse(conninfo, &errorBuf, false); if (connOptions == NULL && errmsg) *errmsg = errorBuf.data; else --- 4018,4024 ---- initPQExpBuffer(&errorBuf); if (PQExpBufferDataBroken(errorBuf)) return NULL; /* out of memory already :-( */ ! connOptions = parse_connection_string(conninfo, &errorBuf, false); if (connOptions == NULL && errmsg) *errmsg = errorBuf.data; else *************** *** 4019,4024 **** conninfo_init(PQExpBuffer errorMessage) --- 4046,4097 ---- } /* + * Connection string parser dispatcher + */ + static PQconninfoOption * + parse_connection_string(const char *connstr, PQExpBuffer errorMessage, + bool use_defaults) + { + /* Parse as URI if connection string matches URI prefix */ + if (uri_prefix_length(connstr) != 0) + return conninfo_uri_parse(connstr, errorMessage, use_defaults); + + /* Parse as default otherwise */ + return conninfo_parse(connstr, errorMessage, use_defaults); + } + + /* + * Checks if connection string starts with either of the valid URI prefix + * designators. + * + * Returns the URI prefix length, 0 if the string doesn't contain a URI prefix. + */ + static int + uri_prefix_length(const char *connstr) + { + if (strncmp(connstr, uri_designator, + sizeof(uri_designator) - 1) == 0) + return sizeof(uri_designator) - 1; + + if (strncmp(connstr, short_uri_designator, + sizeof(short_uri_designator) - 1) == 0) + return sizeof(short_uri_designator) - 1; + + return 0; + } + + /* + * Recognized connection string either starts with a valid URI prefix or + * contains a "=" in it. + */ + static bool + recognized_connection_string(const char *connstr) + { + return uri_prefix_length(connstr) != 0 || + strchr(connstr, '=') != NULL; + } + + /* * Conninfo parser routine * * If successful, a malloc'd PQconninfoOption array is returned. *************** *** 4159,4191 **** 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; --- 4232,4241 ---- } /* ! * 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; *************** *** 4244,4260 **** conninfo_array_parse(const char *const * keywords, const char *const * values, const char *pvalue = values[i]; /* 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 ! * defaults here -- those get picked up later. We only want to ! * override for those parameters actually passed. ! */ ! str_options = conninfo_parse(pvalue, errorMessage, false); if (str_options == NULL) return NULL; } --- 4294,4309 ---- const char *pvalue = values[i]; /* first find "dbname" if any */ ! if (strcmp(pname, "dbname") == 0 && pvalue) { ! /* ! * If value is a connection string, parse it, but do not use defaults ! * here -- those get picked up later. We only want to override for ! * those parameters actually passed. ! */ ! if (recognized_connection_string(pvalue)) { ! str_options = parse_connection_string(pvalue, errorMessage, false); if (str_options == NULL) return NULL; } *************** *** 4442,4457 **** conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage) return true; } 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; --- 4491,5073 ---- return true; } + /* + * 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. + * + * Defaults are supplied (from a service file, environment variables, etc) + * for unspecified options, but only if use_defaults is TRUE. + * + * 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, + bool use_defaults) + { + PQconninfoOption *options; + char *buf; + + resetPQExpBuffer(errorMessage); + + /* Make a working copy of PQconninfoOptions */ + options = conninfo_init(errorMessage); + if (options == NULL) + return NULL; + + /* Make a writable copy of the URI buffer */ + buf = strdup(uri); + if (buf == NULL) + { + printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); + PQconninfoFree(options); + return NULL; + } + + if (!conninfo_uri_parse_options(options, uri, buf, errorMessage)) + { + free(buf); + PQconninfoFree(options); + return NULL; + } + free(buf); + + /* + * Add in defaults if the caller wants that. + */ + if (use_defaults) + { + if (!conninfo_add_defaults(options, errorMessage)) + { + PQconninfoFree(options); + return NULL; + } + } + + 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¶m2=value2&... + */ + static bool + conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, + char *buf, PQExpBuffer errorMessage) + { + int prefix_len; + char *p; + char *start = buf; + char lastc = '\0'; + char *user = NULL; + + /* Skip the URI prefix */ + prefix_len = uri_prefix_length(uri); + if (prefix_len == 0) + { + /* Should never happen */ + printfPQExpBuffer(errorMessage, + libpq_gettext("invalid URI \"%s\" propagated to internal parser routine"), + uri); + return false; + } + start += prefix_len; + 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 == '@') + { + /* + * 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; + } + else + { + /* + * No username/password designator found. + * + * Reset to start of URI and parse as "scheme://netloc/..." + * instead. + */ + p = start; + } + + /* + * "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.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