Commit b035cb9d authored by Alvaro Herrera's avatar Alvaro Herrera

Accept postgres:// URIs in libpq connection functions

postgres:// URIs are an attempt to "stop the bleeding" in this general
area that has been said to occur due to external projects adopting their
own syntaxes.  The syntaxes supported by this patch:

 postgres://[user[:pwd]@][unix-socket][:port[/dbname]][?param1=value1&...]
 postgres://[user[:pwd]@][net-location][:port][/dbname][?param1=value1&...]

should be enough to cover most interesting cases without having to
resort to "param=value" pairs, but those are provided for the cases that
need them regardless.

libpq documentation has been shuffled around a bit, to avoid stuffing
all the format details into the PQconnectdbParams description, which was
already a bit overwhelming.  The list of keywords has moved to its own
subsection, and the details on the URI format live in another subsection.

This includes a simple test program, as requested in discussion, to
ensure that interesting corner cases continue to work appropriately in
the future.

Author: Alexander Shulgin
Some tweaking by Álvaro Herrera, Greg Smith, Daniel Farina, Peter Eisentraut
Reviewed by Robert Haas, Alexey Klyukin (offlist), Heikki Linnakangas,
Marko Kreen, and others

Oh, it also supports postgresql:// but that's probably just an accident.
parent 3769fa5f
This diff is collapsed.
......@@ -115,7 +115,10 @@ PostgreSQL documentation
argument on the command line.
</para>
<para>
If this parameter contains an <symbol>=</symbol> sign, it is treated as a
If this parameter contains an <symbol>=</symbol> sign or starts
with a valid <acronym>URI</acronym> prefix
(<literal>postgresql://</literal>
or <literal>postgres://</literal>), it is treated as a
<parameter>conninfo</parameter> string. See <xref linkend="libpq-connect"> for more information.
</para>
</listitem>
......@@ -596,11 +599,13 @@ PostgreSQL documentation
<para>
An alternative way to specify connection parameters is in a
<parameter>conninfo</parameter> string, which is used instead of a
database name. This mechanism give you very wide control over the
<parameter>conninfo</parameter> string or
a <acronym>URI</acronym>, which is used instead of a database
name. This mechanism give you very wide control over the
connection. For example:
<programlisting>
$ <userinput>psql "service=myservice sslmode=require"</userinput>
$ <userinput>psql postgresql://dbmaster:5433/mydb?sslmode=require</userinput>
</programlisting>
This way you can also use LDAP for connection parameter lookup as
described in <xref linkend="libpq-ldap">.
......
......@@ -121,6 +121,9 @@ install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'
installcheck:
$(MAKE) -C test $@
installdirs: installdirs-lib
$(MKDIR_P) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
......@@ -132,6 +135,7 @@ uninstall: uninstall-lib
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,4 +146,5 @@ clean distclean: clean-lib
rm -f encnames.c wchar.c
maintainer-clean: distclean maintainer-clean-lib
$(MAKE) -C test $@
rm -f libpq-dist.rc
This diff is collapsed.
subdir = src/interfaces/libpq/test
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
ifeq ($(PORTNAME), win32)
LDLIBS += -lws2_32
endif
override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
override LDLIBS := $(libpq_pgport) $(LDLIBS)
PROGS = uri-regress
all: $(PROGS)
installcheck: all
SRCDIR='$(top_srcdir)' SUBDIR='$(subdir)' \
$(SHELL) $(top_srcdir)/$(subdir)/regress.sh
clean distclean maintainer-clean:
rm -f $(PROGS)
rm -f regress.out regress.diff
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, which in turn feeds up lines from 'regress.in' to
'uri-regress' test program and compares the output against the correct
one in 'expected.out' file.
trying postgresql://uri-user:secret@host:12345/db
user='uri-user' password='secret' dbname='db' host='host' port='12345' (inet)
trying postgresql://uri-user@host:12345/db
user='uri-user' dbname='db' host='host' port='12345' (inet)
trying postgresql://uri-user@host/db
user='uri-user' dbname='db' host='host' (inet)
trying postgresql://host:12345/db
dbname='db' host='host' port='12345' (inet)
trying postgresql://host/db
dbname='db' host='host' (inet)
trying postgresql://uri-user@host:12345/
user='uri-user' host='host' port='12345' (inet)
trying postgresql://uri-user@host/
user='uri-user' host='host' (inet)
trying postgresql://uri-user@
user='uri-user' host='' (local)
trying postgresql://host:12345/
host='host' port='12345' (inet)
trying postgresql://host:12345
host='host' port='12345' (inet)
trying postgresql://host/db
dbname='db' host='host' (inet)
trying postgresql://host/
host='host' (inet)
trying postgresql://host
host='host' (inet)
trying postgresql://
host='' (local)
trying postgresql://?hostaddr=127.0.0.1
host='' hostaddr='127.0.0.1' (inet)
trying postgresql://example.com?hostaddr=63.1.2.4
host='example.com' hostaddr='63.1.2.4' (inet)
trying postgresql://%68ost/
host='host' (inet)
trying postgresql://host/db?user=uri-user
user='uri-user' dbname='db' host='host' (inet)
trying postgresql://host/db?user=uri-user&port=12345
user='uri-user' dbname='db' host='host' port='12345' (inet)
trying postgresql://host/db?u%73er=someotheruser&port=12345
user='someotheruser' dbname='db' host='host' port='12345' (inet)
trying postgresql://host/db?u%7aer=someotheruser&port=12345
WARNING: ignoring unrecognized URI query parameter: u%7aer
dbname='db' host='host' port='12345' (inet)
trying postgresql://host:12345?user=uri-user
user='uri-user' host='host' port='12345' (inet)
trying postgresql://host?user=uri-user
user='uri-user' host='host' (inet)
trying postgresql://host?
host='host' (inet)
trying postgresql://[::1]:12345/db
dbname='db' host='::1' port='12345' (inet)
trying postgresql://[::1]/db
dbname='db' host='::1' (inet)
trying postgresql://[2001:db8::1234]/
host='2001:db8::1234' (inet)
trying postgresql://[200z:db8::1234]/
host='200z:db8::1234' (inet)
trying postgresql://[::1]
host='::1' (inet)
trying postgres://
host='' (local)
trying postgres:///tmp
host='/tmp' (local)
trying postgresql://host?uzer=
WARNING: ignoring unrecognized URI query parameter: uzer
host='host' (inet)
trying postgre://
uri-regress: missing "=" after "postgre://" in connection info string
trying postgres://[::1
uri-regress: end of string reached when looking for matching ']' in IPv6 host address in URI: postgres://[::1
trying postgres://[]
uri-regress: IPv6 host address may not be empty in URI: postgres://[]
trying postgres://[::1]z
uri-regress: unexpected 'z' at position 17 in URI (expecting ':' or '/'): postgres://[::1]z
trying postgresql://host?zzz
uri-regress: missing key/value separator '=' in URI query parameter: zzz
trying postgresql://host?value1&value2
uri-regress: missing key/value separator '=' in URI query parameter: value1
trying postgresql://host?key=key=value
uri-regress: extra key/value separator '=' in URI query parameter: key
trying postgres://host?dbname=%XXfoo
uri-regress: invalid percent-encoded token: %XXfoo
trying postgresql://a%00b
uri-regress: forbidden value %00 in percent-encoded value: a%00b
trying postgresql://%zz
uri-regress: invalid percent-encoded token: %zz
trying postgresql://%1
uri-regress: invalid percent-encoded token: %1
trying postgresql://%
uri-regress: invalid percent-encoded token: %
trying postgres://@host
uri-regress: invalid empty username specifier in URI: postgres://@host
trying postgres://host:/
uri-regress: missing port specifier in URI: postgres://host:/
trying postgres://otheruser@/no/such/directory
user='otheruser' host='/no/such/directory' (local)
trying postgres://otheruser@/no/such/socket/path:12345
user='otheruser' host='/no/such/socket/path' port='12345' (local)
trying postgres://otheruser@/path/to/socket:12345/db
user='otheruser' dbname='db' host='/path/to/socket' port='12345' (local)
postgresql://uri-user:secret@host:12345/db
postgresql://uri-user@host:12345/db
postgresql://uri-user@host/db
postgresql://host:12345/db
postgresql://host/db
postgresql://uri-user@host:12345/
postgresql://uri-user@host/
postgresql://uri-user@
postgresql://host:12345/
postgresql://host:12345
postgresql://host/db
postgresql://host/
postgresql://host
postgresql://
postgresql://?hostaddr=127.0.0.1
postgresql://example.com?hostaddr=63.1.2.4
postgresql://%68ost/
postgresql://host/db?user=uri-user
postgresql://host/db?user=uri-user&port=12345
postgresql://host/db?u%73er=someotheruser&port=12345
postgresql://host/db?u%7aer=someotheruser&port=12345
postgresql://host:12345?user=uri-user
postgresql://host?user=uri-user
postgresql://host?
postgresql://[::1]:12345/db
postgresql://[::1]/db
postgresql://[2001:db8::1234]/
postgresql://[200z:db8::1234]/
postgresql://[::1]
postgres://
postgres:///tmp
postgresql://host?uzer=
postgre://
postgres://[::1
postgres://[]
postgres://[::1]z
postgresql://host?zzz
postgresql://host?value1&value2
postgresql://host?key=key=value
postgres://host?dbname=%XXfoo
postgresql://a%00b
postgresql://%zz
postgresql://%1
postgresql://%
postgres://@host
postgres://host:/
postgres://otheruser@/no/such/directory
postgres://otheruser@/no/such/socket/path:12345
postgres://otheruser@/path/to/socket:12345/db
#!/bin/sh
while read line
do
echo "trying $line"
./uri-regress "$line"
echo ""
done < "${SRCDIR}/${SUBDIR}"/regress.in >regress.out 2>&1
if diff -c "${SRCDIR}/${SUBDIR}/"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
/*
* uri-regress.c
* A test program for libpq URI format
*
* This is a helper for libpq conninfo regression testing. It takes a single
* conninfo string as a parameter, parses it using PQconninfoParse, and then
* prints out the values from the parsed PQconninfoOption struct that differ
* from the defaults (obtained from PQconndefaults).
*
* Portions Copyright (c) 2012, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/interfaces/libpq/test/uri-regress.c
*/
#include "postgres_fe.h"
#include "libpq-fe.h"
int
main(int argc, char *argv[])
{
PQconninfoOption *opts;
PQconninfoOption *defs;
PQconninfoOption *opt;
PQconninfoOption *def;
char *errmsg = NULL;
bool local = true;
if (argc != 2)
return 1;
opts = PQconninfoParse(argv[1], &errmsg);
if (opts == NULL)
{
fprintf(stderr, "uri-regress: %s\n", errmsg);
return 1;
}
defs = PQconndefaults();
if (defs == NULL)
{
fprintf(stderr, "uri-regress: cannot fetch default options\n");
return 1;
}
/*
* Loop on the options, and print the value of each if not the default.
*
* XXX this coding assumes that PQconninfoOption structs always have the
* keywords in the same order.
*/
for (opt = opts, def = defs; opt->keyword; ++opt, ++def)
{
if (opt->val != NULL)
{
if (def->val == NULL || strcmp(opt->val, def->val) != 0)
printf("%s='%s' ", opt->keyword, opt->val);
/*
* Try to detect if this is a Unix-domain socket or inet. This is
* a bit grotty but it's the same thing that libpq itself does.
*
* Note that we directly test for '/' instead of using
* is_absolute_path, as that would be considerably more messy.
* This would fail on Windows, but that platform doesn't have
* Unix-domain sockets anyway.
*/
if (*opt->val &&
(strcmp(opt->keyword, "hostaddr") == 0 ||
(strcmp(opt->keyword, "host") == 0 && *opt->val != '/')))
{
local = false;
}
}
}
if (local)
printf("(local)\n");
else
printf("(inet)\n");
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment