Subject: Re: Add ldapservice connection parameter

From: Andrew Jackson <[email-masked]>
Date: Mon, 12 Jan 2026 17:53:14 -0600

Steven and Roman, Thank you for the review.

Here is an updated patch that addresses both of these issues.

On Mon, Jan 12, 2026 at 4:06 AM Roman Khapov <[email-masked]> wrote:
>
> Hi!
>
> Thanks for your patch!
>
> Adding to the one Steven wrote, I noticed one typo in the patch:
>
> @@ -2337,7 +2337,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
>        <term><literal>ldapservice</literal></term>
>        <listitem>
>         <para>
> -        This option specifies an LDAP query that can be used to reference connection paremeters
> +        This option specifies an LDAP query that can be used to reference connection parameters
>          stored in an LDAP server. This functionality is described in more detail in <xref linkend="libpq-ldap"/>.
>          </para>
>
> paremeters -> parameters
>
> --
> Best regards,
> Roman Khapov
From a365a3a3cb1b9ac937a9009d1bd71bf06e9f4eea Mon Sep 17 00:00:00 2001
From: CommanderKeynes <[email-masked]>
Date: Sun, 23 Mar 2025 17:27:32 -0500
Subject: [PATCH] Add ldapservice connection parameter

Currently there exists, only in pg_service.conf, the ability to look
up connection parameters from a centralized LDAP server. This patch
expands the usability of this be allowing it to be specified directly in
a connection string instead of only in a pg_service.conf file.
---
 doc/src/sgml/libpq.sgml                             | 10 ++++++++++
 src/interfaces/libpq/fe-connect.c                   | 12 ++++++++++++
 src/interfaces/libpq/libpq-int.h                    |  1 +
 src/test/ldap/t/003_ldap_connection_param_lookup.pl | 12 ++++++++++++
 4 files changed, 35 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d05938feda..20a48a22c6b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2333,6 +2333,16 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-ldapservice" xreflabel="ldapservice">
+      <term><literal>ldapservice</literal></term>
+      <listitem>
+       <para>
+        This option specifies an LDAP query that can be used to reference connection parameters
+        stored in an LDAP server. This functionality is described in more detail in <xref linkend="libpq-ldap"/>.
+        </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
       <term><literal>target_session_attrs</literal></term>
       <listitem>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a0d2f749811..6be2b792be5 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -206,6 +206,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Database-Service-File", "", 64,
 	offsetof(struct pg_conn, pgservicefile)},
 
+	{"ldapservice", "PGLDAPSERVICE", NULL, NULL,
+		"Database-LDAP-Service", "", 20,
+	offsetof(struct pg_conn, pgldapservice)},
+
 	{"user", "PGUSER", NULL, NULL,
 		"Database-User", "", 20,
 	offsetof(struct pg_conn, pguser)},
@@ -5955,12 +5959,20 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 {
 	const char *service = conninfo_getval(options, "service");
 	const char *service_fname = conninfo_getval(options, "servicefile");
+	const char *ldapservice = conninfo_getval(options, "ldapservice");
 	char		serviceFile[MAXPGPATH];
 	char	   *env;
 	bool		group_found = false;
 	int			status;
 	struct stat stat_buf;
 
+#ifdef USE_LDAP
+	if (ldapservice != NULL)
+		if (strncmp(ldapservice, "ldap", 4) == 0)
+			if (!ldapServiceLookup(ldapservice, options, errorMessage))
+				return 0;
+#endif
+
 	/*
 	 * We have to special-case the environment variable PGSERVICE here, since
 	 * this is and should be called before inserting environment defaults for
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index fb6a7cbf15d..b540d3ac054 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -392,6 +392,7 @@ struct pg_conn
 	char	   *pgservice;		/* Postgres service, if any */
 	char	   *pgservicefile;	/* path to a service file containing
 								 * service(s) */
+	char	   *pgldapservice;	/* Postgres LDAP service, if any */
 	char	   *pguser;			/* Postgres username and password, if any */
 	char	   *pgpass;
 	char	   *pgpassfile;		/* path to a file containing password(s) */
diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
index 359fc7a998a..6985be65408 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -196,6 +196,18 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	$dummy_node->connect_ok(
+		"ldapservice=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapservice" string',
+		sql => "SELECT 'connect2_4'",
+		expected_stdout => qr/connect2_4/);
+
+	$dummy_node->connect_ok(
+		"postgres://?ldapservice=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapservice"',
+		sql => "SELECT 'connect2_5'",
+		expected_stdout => qr/connect2_5/);
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
-- 
2.49.0

---

From: Andrew Jackson <[email-masked]>
Date: Sun, 22 Mar 2026 18:38:41 -0500

Hi,

Noticed 1 variable that was unused during non-LDAP builds. Tested
locally and did not see the error/warning. Also some minor cleanup
(comments, definition placement, etc).

Thanks,
Andrew Jackson

> >          stored in an LDAP server. This functionality is described in more detail in <xref linkend="libpq-ldap"/>.
From e429c7f5387466fddcd765eb65f23db21f19e769 Mon Sep 17 00:00:00 2001
From: CommanderKeynes <[email-masked]>
Date: Sun, 23 Mar 2025 17:27:32 -0500
Subject: [PATCH] Add ldapservice connection parameter

Currently there exists, only in pg_service.conf, the ability to look
up connection parameters from a centralized LDAP server. This patch
expands the usability of this be allowing it to be specified directly in
a connection string instead of only in a pg_service.conf file.
---
 doc/src/sgml/libpq.sgml                            | 10 ++++++++++
 src/interfaces/libpq/fe-connect.c                  | 14 ++++++++++++++
 src/interfaces/libpq/libpq-int.h                   |  1 +
 .../ldap/t/003_ldap_connection_param_lookup.pl     | 12 ++++++++++++
 4 files changed, 37 insertions(+)

index a0d2f749811..7a6caaebd88 100644
 
@@ -5955,12 +5959,22 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 
+#ifdef USE_LDAP
 
2.51.2

---

From: Laurenz Albe <[email-masked]>
Date: Fri, 27 Mar 2026 23:48:00 +0100

> (comments, definition placement, etc).

I don't have an LDAP server handy, so I couldn't test the patch,
but I read through it.

I think that this is a useful addition.

+#ifdef USE_LDAP

I think that the test if the string starts with "ldap" is midguided.
It was probably copied from the other call site of ldapServiceLookup(),
but there it is necessary, because an entry in the connection service
file could start with something else.

A value for the parameter "ldapservice" that doesn't start with "ldap"
should cause an error.

ldapServiceLookup() checks if the string starts with "ldap://" and
throws an error if it doesn't.  So I'd say that you should simply
remove the test if the string starts with "ldap".

I also think that it is wrong to return 0 (success) if
ldapServiceLookup() fails.  Why should it be OK to specify a bad
LDAP URL?

I don't understand why that code is in parseServiceInfo().
After all, it has nothing to do with a connection service file.
Calling it from conninfo_add_defaults() would make more sense to me.
For the same reason, I am not entirely happy with the name
"ldapservice", but I can't think of anything better.

The way your code is now, "ldapservice" sets default values that
get overridden by explicitly named parameters.  I think the
documentation should mention that.

Yours,
Laurenz Albe

---

From: Andrew Jackson <[email-masked]>
Date: Sun, 29 Mar 2026 08:55:54 -0500

Laurenz,

Thank you for the review.

Included with this email are 2 patches.
`0004-Add-ldapservice-connection-parameter.patch` contains all of the
changes you recommended:
- Removed the redundant/unneeded check for the string ldap
- The function has  now been moved from parseServiceInfo() to
conninfo_add_defaults().
  - I will say though the reason I included this in parseServiceInfo()
was I consider this new LDAP functionality and the existing
pg_service.conf() functionality all different ways of accessing a
postgres "service". I thought it made sense to handle all service
parsing in a single function. Happy to keep it moved it if I was
incorrect about this
- A failed parseServiceInfo() now returns false in conninfo_add_defaults()
- Made documentation a bit more explicit.

Also I added support for specifying PGLDAPPSERVICE in the form of an
env var as well. Also added additional tests.

> For the same reason, I am not entirely happy with the name "ldapservice", but I can't think of anything better.
I have a few suggestions here. Maybe `remoteserviceuri` would be  a
better name. This tells the reader that it's a uri and not the name of
a service. LDAP is not mentioned because maybe in the future this
functionality could be expanded to read connection parameters  from an
HTTP server, postgres side channel, etc.

Alternatively we could bypass the naming altogether and add this
functionality into the existing pgservice parameter. I have attached a
second patch `0001-Allow-LDAP-lookup-from-pgservice-connection-paramete.patch`
that has an implementation of this. The idea being that if you pass a
string that starts with ldap:// it will look up the service at the
given LDAP uri instead of looking for the corresponding service in the
pg_service.conf file. This would be a breaking change for anyone who
populated their service names  as  LDAP uris, I would imagine this
would be a very small number of users, if any. It also ends up being a
smaller patch overall.

> I don't have an LDAP server handy, so I couldn't test the patch

On the off chance that it helps, we now have unit tests over the LDAP
service functionality. It should automatically start a slapd server,
etc. If you add a false assertion in the perl code it should fail the
test and also dump a bunch of files into the src/test/ldap directory.
You can copy paste the slapd invocation that is left there. I did have
to fight with App Armor a bit to get it to be okay with a slapd
process reading files in unexpected locations.

Thanks again for the review,
Andrew Jackson
From 5f9a720a526471286864d92559e6361460ca04cb Mon Sep 17 00:00:00 2001
From: Andrew Jackson <[email-masked]>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Allow LDAP lookup from pgservice connection parameter

This patch adds a check in parseServiceInfo that checks if pgservice is
an LDAP scheme address and if so attempts to connect to that LDAP server
and grab connection parameters from there. This is a breaking change in
that it is possible that people previously named their pgservice after
an LDAP address.
---
 doc/src/sgml/libpq.sgml                       |  4 +++
 src/interfaces/libpq/fe-connect.c             |  5 ++++
 .../t/003_ldap_connection_param_lookup.pl     | 25 +++++++++++++++++++
 3 files changed, 34 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..4d230a2a65f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2333,6 +2333,10 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         name in <filename>pg_service.conf</filename> that holds additional connection parameters.
         This allows applications to specify only a service name so connection parameters
         can be centrally maintained. See <xref linkend="libpq-pgservice"/>.
+
+        You can also specify and LDAP address here which will look up parameters from an LDAP
+        server. See <xref linkend="libpq-ldap"/>. Please note that if an LDAP address is specified
+        no attempt will be made to look up parameters in <filename>pg_service.conf</filename>.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..50ada2939b6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -6006,6 +6006,11 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 	if (service == NULL)
 		return 0;
 
+#ifdef USE_LDAP
+	if (pg_strncasecmp(service, LDAP_URL, strlen(LDAP_URL)) == 0)
+		return ldapServiceLookup(service, options, errorMessage);
+#endif
+
 	/*
 	 * First, try the "servicefile" option in connection string.  Then, try
 	 * the PGSERVICEFILE environment variable.  Finally, check
diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
index 359fc7a998a..67da90578cf 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -80,6 +80,9 @@ append_to_file(
 	$srvfile_valid, qq{
 [my_srv]
 ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[my_srv_2]
+ldapservice=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
 });
 
 # File defined with no contents, used as default value for
@@ -196,6 +199,28 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	$dummy_node->connect_ok(
+		"service=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "service" string populated with LDAP address',
+		sql => "SELECT 'connect2_4'",
+		expected_stdout => qr/connect2_4/);
+
+	$dummy_node->connect_ok(
+		"postgres://?service=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapservice" string populated with LDAP address',
+		sql => "SELECT 'connect2_5'",
+		expected_stdout => qr/connect2_5/);
+
+	local $ENV{PGSERVICE} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "service" provided by env var populated with LDAP address',
+		sql => "SELECT 'connect2_6'",
+		expected_stdout => qr/connect2_6/);
+	delete $ENV{PGLDAPSERVICE};
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
-- 
2.51.2

---

From: Andrew Jackson <[email-masked]>
Date: Mon, 30 Mar 2026 12:32:20 -0500

Andrey,

>I like "ldapservice" approach more. I don't like breaking changes, even for very small number of users.

After thinking about it more it would be pretty easy to make this non
breaking for users. The attached patch
`pgservice-0002-Allow-LDAP-lookup-from-pgservice-connection-paramete.patch`
implements this. Basically if the ldap connection parameter happens to
already exist in pg_service, no LDAP lookup is performed, the values
are instead taken from the pg_service.conf file. I'm fine with either
implementation direction that is the most simple though.

Also attaching the patch
`ldapserviceurl-0005-Add-ldapservice-connection-parameter.patch` which
addresses some issues you found with the previous patch.
- renames parameter from ldapservice to ldapserviceurl
- Adds a comment above the ldapserviceurl call explaining the reason
for ignoring any error info besides zero/non-zero
- Changes dispsize to 64. In reality it maybe should be higher but I
was not comfortable changing it to something like 1024 because it was
an order of magnitude larger than anything else. Just set it to the
current max of 64.
- Also moved the parameter definition and added comments about how it
is defined even on non LDAP builds. This coincides with similar
comments for SSL/GSS specific parameters, though there are no such
comments left for oauth.

Let me know if there is anything else you see missing.

Thanks again,
Andrew Jackson
From 0020d2027b581e6bdfc5b78785a9b9171b7f7ef2 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <[email-masked]>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Allow LDAP lookup from pgservice connection parameter

v0002 patch

This patch Removes all backwards compatibility issues. Now if the LDAP
URL was specified in pg_service.conf then the values from
pg_service.conf will be used and the LDAP lookup will not be performed.

Cool
---
 doc/src/sgml/libpq.sgml                       |  6 +++
 src/interfaces/libpq/fe-connect.c             |  5 ++
 .../t/003_ldap_connection_param_lookup.pl     | 48 +++++++++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..88a5db2388d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2333,6 +2333,12 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         name in <filename>pg_service.conf</filename> that holds additional connection parameters.
         This allows applications to specify only a service name so connection parameters
         can be centrally maintained. See <xref linkend="libpq-pgservice"/>.
+
+        You can also specify an LDAP URL here which will look up connection parameters from an
+        LDAP server. See <xref linkend="libpq-ldap"/>. Please note that if the LDAP URL
+        specified here exists as a service name in <filename>pg_service.conf</filename>, then
+        the <filename>pg_service.conf</filename> values will be used instead and no LDAP lookup
+        will be performed.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..b12b9bceb12 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -6048,6 +6048,11 @@ next_file:
 last_file:
 	if (!group_found)
 	{
+#ifdef USE_LDAP
+		if (strncmp(service, "ldap", 4) == 0)
+			/* if error ignore ldapServiceLookup return value, just return 3 */
+			return ldapServiceLookup(service, options, errorMessage) ? 3 : 0;
+#endif
 		libpq_append_error(errorMessage, "definition of service \"%s\" not found", service);
 		return 3;
 	}
diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
index 359fc7a998a..976c196df8c 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -62,6 +62,20 @@ description:port=} . $node->port . qq{
 
 $ldap->ldapadd_file($ldif_valid);
 
+my $ldif_valid_invalid = "$td/connection_params_ldif_valid_invalid.ldif";
+append_to_file(
+	$ldif_valid_invalid , qq{
+version:1
+dn:cn=mydatabasefoundinpgservice,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:mydatabasefoundinpgservice
+description:host=} . $node->host . qq{
+description:port=} . $node->port . qq{
+});
+$ldap->ldapadd_file($ldif_valid_invalid);
+
 my ($ldap_server, $ldap_port, $ldaps_port, $ldap_url,
 	$ldaps_url, $ldap_basedn, $ldap_rootdn
 ) = $ldap->prop(qw(server port s_port url s_url basedn rootdn));
@@ -80,6 +94,9 @@ append_to_file(
 	$srvfile_valid, qq{
 [my_srv]
 ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabasefoundinpgservice)]
+port=1234
 });
 
 # File defined with no contents, used as default value for
@@ -196,6 +213,37 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	$dummy_node->connect_ok(
+		"service=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "service" string populated with LDAP address',
+		sql => "SELECT 'connect2_4'",
+		expected_stdout => qr/connect2_4/);
+
+	$dummy_node->connect_ok(
+		"postgres://?service=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapservice" string populated with LDAP address',
+		sql => "SELECT 'connect2_5'",
+		expected_stdout => qr/connect2_5/);
+
+	local $ENV{PGSERVICE} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "service" provided by env var populated with LDAP address',
+		sql => "SELECT 'connect2_6'",
+		expected_stdout => qr/connect2_6/);
+	delete $ENV{PGLDAPSERVICE};
+
+	# Below test should fail because the service value is defined as a literal service in pg_service.conf.
+	# The pg_service.conf entry should have an incorrect port and LDAP should not be looked up after reading
+	# the incorrect port
+	$dummy_node->connect_fails(
+		'service=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabasefoundinpgservice)"',
+		'connection using cn=mydatabasefoundinpgservice',
+		expected_stdout =>
+		  qr/definition of service "undefined-service" not found/);
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
-- 
2.51.2

From b56968599037f402b0b4c040483bd67e61bf2429 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <[email-masked]>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Add ldapservice connection parameter

This adds the PGLDAPSERVICE env var that provides an envvar interface
to this functionality. Also the functionality has been moved to
conninfo_add_defaults after review. Also 2 tests have been added. One to
validate the env var functionality and one to validate that this
parameter is ignored when used in pg_service.conf files.

-- 0005 patch changes
This patch changes the relevant naming from ldapservice to
ldapserviceurl. I also add a comment above the new ldapservicelookup
call that explains the reasoning for ignoring all non zero return codes.
Also I increase the value of dispsize for ldapserviceurl from 20 to 64.
It could be reasonable to increase it past 64, was originally thinking
about bumping it to 1024 but I am not sure what these values are used
for in the codebase and thought it was safer to just bump it to the
currently existing max for any parameter. Moves the placement of
ldapserviceurl to below the GSS options and adds a comment explaining
that the parameter is exposed even in non LDAP builds. This was done to
reflect similar comments in GSS/SSL, though this comment does not exist
for the new OAUTH parameters so this may not have been a good choice.
Also Add filename tags to filename reference in docs
---
 doc/src/sgml/libpq.sgml                       | 23 +++++++++++++++
 src/interfaces/libpq/fe-connect.c             | 24 +++++++++++++++
 src/interfaces/libpq/libpq-int.h              |  1 +
 .../t/003_ldap_connection_param_lookup.pl     | 29 +++++++++++++++++++
 4 files changed, 77 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..0e4b7f81551 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2350,6 +2350,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-ldapserviceurl" xreflabel="ldapserviceurl">
+      <term><literal>ldapserviceurl</literal></term>
+      <listitem>
+       <para>
+        This option specifies an LDAP query that can be used to reference connection parameters
+        stored on an LDAP server. Any connection parameter that is looked up in this way is
+        overridden by explicitly named connection parameters. This parameter is ignored when used
+        in a <filename>pg_service.conf</filename> file. This functionality is described in more
+        detail in <xref linkend="libpq-ldap"/>.
+        </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
       <term><literal>target_session_attrs</literal></term>
       <listitem>
@@ -9170,6 +9183,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGLDAPSERVICEURL</envar></primary>
+      </indexterm>
+      <envar>PGLDAPSERVICEURL</envar> behaves the same as the
+      <xref linkend="libpq-connect-ldapserviceurl"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..d076bfe7701 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -376,6 +376,15 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"GSS-delegation", "", 1,
 	offsetof(struct pg_conn, gssdelegation)},
 
+	/*
+	 * As with SSL and GSS options, ldapserviceurl is exposed even in builds
+	 * that do not have support
+	 */
+	{"ldapserviceurl", "PGLDAPSERVICEURL", NULL, NULL,
+		"Database-LDAP-Service", "", 64,
+	offsetof(struct pg_conn, pgldapserviceurl)},
+
+
 	{"replication", NULL, NULL, NULL,
 		"Replication", "D", 5,
 	offsetof(struct pg_conn, replication)},
@@ -6726,6 +6735,21 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 	PQconninfoOption *sslmode_default = NULL,
 			   *sslrootcert = NULL;
 	char	   *tmp;
+#ifdef USE_LDAP
+	const char *ldapserviceurl = conninfo_getval(options, "ldapserviceurl");
+
+	if (ldapserviceurl == NULL)
+		ldapserviceurl = getenv("PGLDAPSERVICEURL");
+
+	if (ldapserviceurl != NULL)
+
+		/*
+		 * ldapServiceLookup has 4 potential return values. We only care here
+		 * if it succeeded, if it failed we dont care why, return failure.
+		 */
+		if (ldapServiceLookup(ldapserviceurl, options, errorMessage) != 0)
+			return false;
+#endif
 
 	/*
 	 * If there's a service spec, use it to obtain any not-explicitly-given
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bd7eb59f5f8..f8b10635d41 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -392,6 +392,7 @@ struct pg_conn
 	char	   *pgservice;		/* Postgres service, if any */
 	char	   *pgservicefile;	/* path to a service file containing
 								 * service(s) */
+	char	   *pgldapserviceurl;	/* Postgres LDAP service URL, if any */
 	char	   *pguser;			/* Postgres username and password, if any */
 	char	   *pgpass;
 	char	   *pgpassfile;		/* path to a file containing password(s) */
diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
index 359fc7a998a..f1c5cc85eb6 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -80,6 +80,9 @@ append_to_file(
 	$srvfile_valid, qq{
 [my_srv]
 ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[my_srv_2]
+ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
 });
 
 # File defined with no contents, used as default value for
@@ -196,6 +199,32 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapservice" string',
+		sql => "SELECT 'connect2_4'",
+		expected_stdout => qr/connect2_4/);
+
+	$dummy_node->connect_ok(
+		"postgres://?ldapserviceurl=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapserviceurl"',
+		sql => "SELECT 'connect2_5'",
+		expected_stdout => qr/connect2_5/);
+
+	local $ENV{PGLDAPSERVICEURL} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "ldapserviceurl" provided by env var',
+		sql => "SELECT 'connect2_6'",
+		expected_stdout => qr/connect2_6/);
+	delete $ENV{PGLDAPSERVICEURL};
+
+	$dummy_node->connect_fails(
+		'',
+		'connection fails with ldapserviceurl specified in pg_service.conf file');
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
-- 
2.51.2

---

From: Andrew Jackson <[email-masked]>
Date: Wed, 1 Apr 2026 18:07:47 -0500

Attached is an updated patch.

> I think there should be more tests for failure.

This is fixed. I have added a new suite of tests that cover various
(but not exhaustive) failure scenarios within ldapServiceLookup. Prior
to this patch there were no tests for failure within ldapServiceLookup
so I think it's an improvement.

I have also added  5 additional tests to validate corner cases around
the new ldapserviceurl functionality.

> Good, but you should append something to the "errorMessage", like
conninfo_add_defaults() does elsewhere.

Added an error append in this patch. It has the impact of printing 2
lines of errors on failure though: one from the newly appended message
and one from ldapServiceLookup. Not sure if there are other examples
of this behavior in libpq. Will look into this  more tommorow.

> About the tests: there is one test "connection fails with ldapserviceurl specified in pg_service.conf file", but it doesn't look like it is doing what it says.

You are correct, this test was incorrect. All of the tests should now
have expected_stdout/stderr conditions so they should be a lot more
precise now.

> The version number of the patches should be before that, like v5-0001-First-patch-version-5.patch

Understood. All future patches will be attached in this manner.

>  I think it is confusing to repurpose an existing parameter like that.

Understood. Will stick to the idea  of using a new connection parameter.

Thanks,
Andrew Jackson
From c2fbf9424561b5e3e9d20fda245c321f97c8f818 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <[email-masked]>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Add ldapservice connection parameter

-- v5 patch changes

-- v6 patch changes

- Updated libpq.sgml with additional verbiage around corner cases
  interactions between different parameter settings, etc
- Changed existing guard in service file parse from "ldap" to "ldap:"
  to accomodate new connection parameter that begin with "ldap"
- Add error raising for ldapserviceurl found during pg_service.conf
  parse
- add libpq append error message
- Expanded unit tests
    - Added 6 new LDAP entries for different test scenarios
    - Ensured that we are asserting expected_stdout for every test
    - Added set of unit tests that cover error conditions in
      ldapServiceLookup that are currently not tested in postgres
      codebase
    - Added 5 new tests that cover corner cases of new ldapserviceurl
      functionality
---
 doc/src/sgml/libpq.sgml                       |  43 +++-
 src/interfaces/libpq/fe-connect.c             |  43 +++-
 src/interfaces/libpq/libpq-int.h              |   1 +
 .../t/003_ldap_connection_param_lookup.pl     | 229 ++++++++++++++++++
 4 files changed, 309 insertions(+), 7 deletions(-)

index 6db823808fc..01308321020 100644

+     <varlistentry id="libpq-connect-ldapserviceurl" xreflabel="ldapserviceurl">
+      <term><literal>ldapserviceurl</literal></term>
+      <listitem>
+       <para>
+        This option specifies an LDAP query that can be used to reference connection parameters
+        stored on an LDAP server. Any connection parameter that is looked up in this way is
+        overridden by explicitly named connection parameters or environment variables. This
+        option cannot be specified in a <filename>pg_service.conf</filename> file. This
+        functionality is described in more detail in <xref linkend="libpq-ldap"/>.
+        </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
       <term><literal>target_session_attrs</literal></term>
       <listitem>
@@ -9170,6 +9183,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>

+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGLDAPSERVICEURL</envar></primary>
+      </indexterm>
+      <envar>PGLDAPSERVICEURL</envar> behaves the same as the
+      <xref linkend="libpq-connect-ldapserviceurl"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
@@ -9659,12 +9682,20 @@ user=admin
   <para>
    LDAP connection parameter lookup uses the connection service file
    <filename>pg_service.conf</filename> (see <xref
-   linkend="libpq-pgservice"/>).  A line in a
-   <filename>pg_service.conf</filename> stanza that starts with
-   <literal>ldap://</literal> will be recognized as an LDAP URL and an
-   LDAP query will be performed. The result must be a list of
-   <literal>keyword = value</literal> pairs which will be used to set
-   connection options.  The URL must conform to
+   linkend="libpq-pgservice"/>) or the
+   <xref linkend="libpq-connect-ldapserviceurl"/> connection parameter.
+   A line in a <filename>pg_service.conf</filename> stanza that starts with
+   <literal>ldap://</literal> or an explicitly provided
+   <xref linkend="libpq-connect-ldapserviceurl"/> connection parameter
+   will be recognized as an LDAP URL and an LDAP query will be performed.
+   Please note that if both methods are used the in conjunction any parameter
+   found via <xref linkend="libpq-connect-ldapserviceurl"/> will take precedence
+   over results found in the lookup from the LDAP URL specified in
+   <filename>pg_service.conf</filename>. Also please note that any
+   <xref linkend="libpq-connect-ldapserviceurl"/> value
+   that is found during an LDAP lookup will be ignored. The result of an LDAP lookup
+   must be a list of <literal>keyword = value</literal> pairs which will be used to
+   set connection options. The URL must conform to
    <ulink url="https://datatracker.ietf.org/doc/html/rfc1959">RFC 1959</ulink>
    and be of the form
 <synopsis>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..95fa2f8fdc5 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -376,6 +376,15 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"GSS-delegation", "", 1,
 	offsetof(struct pg_conn, gssdelegation)},

+	/*
@@ -6134,7 +6143,10 @@ parseServiceFile(const char *serviceFile,
 				bool		found_keyword;

 #ifdef USE_LDAP
-				if (strncmp(line, "ldap", 4) == 0)
+				/*
+				 * Is this a potential ldapurl or a ldapserviceurl parameter?
+				 */
+				if (strncmp(line, "ldap:", 5) == 0)
 				{
 					int			rc = ldapServiceLookup(line, options, errorMessage);

@@ -6176,6 +6188,16 @@ parseServiceFile(const char *serviceFile,
 					goto exit;
 				}

+				if (strcmp(key, "ldapserviceurl") == 0)
+				{
+					libpq_append_error(errorMessage,
+									   "ldapserviceurl parameters are not supported in service file \"%s\", line %d",
+									   serviceFile,
+									   linenr);
+					result = 3;
+					goto exit;
+				}
+
 				if (strcmp(key, "servicefile") == 0)
 				{
 					libpq_append_error(errorMessage,
@@ -6726,6 +6748,25 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 	PQconninfoOption *sslmode_default = NULL,
 			   *sslrootcert = NULL;
 	char	   *tmp;
+#ifdef USE_LDAP
+	const char *ldapserviceurl = conninfo_getval(options, "ldapserviceurl");
+
+	if (ldapserviceurl == NULL)
+		ldapserviceurl = getenv("PGLDAPSERVICEURL");
+
+	if (ldapserviceurl != NULL)
+	{
+
+		/*
+		 * ldapServiceLookup has 4 potential return values. We only care here
+		 * if it succeeded, if it failed we dont care why, return failure.
+		 */
+		if (ldapServiceLookup(ldapserviceurl, options, errorMessage) != 0){
+			libpq_append_error(errorMessage, "ldapserviceurl lookup failed");
+			return false;
+		}
+	}
+#endif

 	/*
 	 * If there's a service spec, use it to obtain any not-explicitly-given
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bd7eb59f5f8..f8b10635d41 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -392,6 +392,7 @@ struct pg_conn
 	char	   *pgservice;		/* Postgres service, if any */
 	char	   *pgservicefile;	/* path to a service file containing
 								 * service(s) */
+	char	   *pgldapserviceurl;	/* Postgres LDAP service URL, if any */
 	char	   *pguser;			/* Postgres username and password, if any */
 	char	   *pgpass;
 	char	   *pgpassfile;		/* path to a file containing password(s) */
diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
index 359fc7a998a..a9469fe011f 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -56,6 +56,85 @@ changetype:add
 objectclass:top
 objectclass:device
 cn:mydatabase
+description:application_name=application_name_from_ldap
+description:host=} . $node->host . qq{
+description:port=} . $node->port . qq{
+});
+
+# Valid ldap record with geqo and application_name set
+# to test how parameters are set/overridden
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=mydatabasegeqooff,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:mydatabasegeqooff
+description:application_name=application_name_from_database2
+description:options=--geqo=off
+});
+
+# Invalid LDAP record with no entries
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=noentries,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:noentries
+});
+
+# Invalid LDAP record with missing = after application_name
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=missingequal,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:missingequal
+description:application_name
+});
+
+# Invalid LDAP record with non existent parameter
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=invalidconnoptionservice,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:invalidconnoptionservice
+description:invalidconnoption=1
+});
+
+# Invalid LDAP record with missing = in options parameter
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=pgoptionmissingequals,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:pgoptionmissingequals
+description:options=--geqo off
+description:host=} . $node->host . qq{
+description:port=} . $node->port . qq{
+});
+
+# Valid LDAP record which has ldapserviceurl parameter
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=ldapurlinldapurl,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:ldapurlinldapurl
+description:ldapserviceurl=ldap://localhost:1234/dc=example,dc=net?description?one?(cn=mydatabasegeqooff)
+description:application_name=application_name_from_ldapurlinldapurl
 description:host=} . $node->host . qq{
 description:port=} . $node->port . qq{
 });
@@ -80,6 +159,16 @@ append_to_file(
 	$srvfile_valid, qq{
 [my_srv]
 ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[my_srv_geqo_off]
+ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabasegeqooff)
+
+[ldapserviceurl_in_pg_service_test]
+ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[pg_service_application_name]
+application_name=pg_service
+options=--geqo=off
 });

 # File defined with no contents, used as default value for
@@ -196,10 +285,150 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);

+	delete $ENV{PGSERVICE};
+
+	unlink($srvfile_default);
+}
+
+# Test ldapserviceurl connection parameter
+{
+	# Create copy of valid file
+	my $srvfile_default = "$td/pg_service.conf";
+	copy($srvfile_valid, $srvfile_default);
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapserviceurl"',
+		sql => "SELECT 'connect2_4', current_setting('application_name')",
+		expected_stdout => qr/connect2_4\|application_name_from_ldap/);
+
+	$dummy_node->connect_ok(
+		"postgres://?ldapserviceurl=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapserviceurl" in uri format',
+		sql => "SELECT 'connect2_5', current_setting('application_name')",
+		expected_stdout => qr/connect2_5\|application_name_from_ldap/);
+
+	local $ENV{PGLDAPSERVICEURL} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "ldapserviceurl" provided by env var',
+		sql => "SELECT 'connect2_6', current_setting('application_name')",
+		expected_stdout => qr/connect2_6\|application_name_from_ldap/);
+
+	$dummy_node->connect_fails(
+		'ldapserviceurl=ldap://localhost:invalidport/dc=example,dc=net?description?one?(cn=mydatabase)',
+		'connection fails with because valid PGLDAPSERVICEURL env var overridden by explicit parameter with bad port',
+		expected_stderr => qr/invalid LDAP URL .*: invalid port number/);
+
+	delete $ENV{PGLDAPSERVICEURL};
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) application_name=explicit_parameter",
+		'connection with correct "ldapservice" but with application_name overridden by explicit connection parameter',
+		sql => "SELECT 'connect2_7', current_setting('application_name')",
+		expected_stdout => qr/connect2_7\|explicit_parameter/);
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) service=pg_service_application_name",
+		'connection with correct "ldapservice" string, service file application_name overridden by ldap, geqo off set by service file',
+		sql => "SELECT 'connect2_8', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout => qr/connect2_8\|application_name_from_ldap\|off/);
+
+	# test that geqo grabbed from service file service my_srv_geqo_off via LDAP lookup
+	# conflicting application names present in both LDAP services queried (ldapserviceurl and service file)
+	# application name from service file is ignored because it has already been set by ldapserviceurl lookup
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) service=my_srv_geqo_off",
+		'connection with 2 LDAP lookups (pg_service.conf and ldapserviceurl)',
+		sql => "SELECT 'connect2_9', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout => qr/connect2_9\|application_name_from_ldap\|off/);
+
+	$dummy_node->connect_fails(
+		'service=ldapserviceurl_in_pg_service_test',
+		'connection fails with ldapserviceurl specified in pg_service.conf file',
+		expected_stderr => qr/ldapserviceurl parameters are not supported in service file .*, line .*/ );
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=ldapurlinldapurl)",
+		'ldapserviceurl that points to another ldapserviceurl will be ignored',
+		sql => "SELECT 'connect2_10', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout=> qr/connect2_10\|application_name_from_ldapurlinldapurl\|on/);
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }

+# negative tests for logic inside ldapServiceLookup
+{
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=http://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'ldapserviceurl that doesnt begin with ldap:// fails',
+		expected_stderr => qr/invalid LDAP URL .*: scheme must be ldap:\/\//);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/",
+		'ldapserviceurl that does not specify distinguished name',
+		expected_stderr => qr/invalid LDAP URL .*: missing distinguished name/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?",
+		'ldapserviceurl that does not specify attribute',
+		expected_stderr => qr/invalid LDAP URL .*: must have exactly one attribute/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description",
+		'ldapserviceurl that does not provide scope',
+		expected_stderr => qr/invalid LDAP URL .*: must have search scope \(base\/one\/sub\)/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one",
+		'ldapserviceurl that does not specify filter',
+		expected_stderr => qr/invalid LDAP URL .*: no filter/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:invalidportnumber/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'ldapserviceurl contains invalid port number',
+		expected_stderr => qr/invalid LDAP URL .*: invalid port number/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description,dn?one?(cn=mydatabase)",
+		'ldapserviceurl that contains 2 attributes',
+		expected_stderr => qr/invalid LDAP URL .*: must have exactly one attribute/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?invalidscope?(cn=mydatabase)",
+		'ldapserviceurl that does not provide valid scope',
+		expected_stderr => qr/invalid LDAP URL .*: must have search scope \(base\/one\/sub\)/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?sub?(cn=*)",
+		'ldapserviceurl that contains more than one entry',
+		expected_stderr => qr/more than one entry found on LDAP lookup/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=doesnotexist)",
+		'ldapserviceurl with no ldap entry',
+		expected_stderr => qr/no entry found on LDAP lookup/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=noentries)",
+		'ldapserviceurl that has no connection attributes',
+		expected_stderr => qr/attribute has no values on LDAP lookup/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=missingequal)",
+		'ldapservicesurl that has missing equal sign',
+		expected_stderr => qr/missing \"\=\" after \"application_name\n\" in connection info string/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=invalidconnoptionservice)",
+		'ldapserviceurl that contains nonexistent connection option',
+		expected_stderr => qr/invalid connection option \"invalidconnoption\"/);
+
+}
+
 $node->teardown_node;

 done_testing();
--
2.51.2

---

From: Laurenz Albe <[email-masked]>
Date: Thu, 02 Apr 2026 16:55:02 +0200

> Attached is an updated patch.

Thank you, it applies and passes the regression tests.

> of this behavior in libpq. Will look into this  more tommorow.

Oh, I didn't look at that.  If ldapServiceLookup() already appends a
meaningful error message, there is no need to add another one.

The documentation is better now.

>                     int         rc = ldapServiceLookup(line, options, errorMessage);

That change does not really belong to the patch, but is alright by me.

I'll mark the patch as "ready for committer".

Yours,
Laurenz Albe

---

From: Andrew Jackson <[email-masked]>
Date: Tue, 7 Apr 2026 11:20:20 -0500

Hi,

> meaningful error message, there is no need to add another one.

Updated patch to only append an error message when rc from
ldapServiceLookup is 2. This is the only non zero return of
ldapServiceLookup that does not append an error message because it is
meant to signal that the lookup was not successful but we can continue
parsing the service file. In this case I think it makes sense to raise
an error and append an error message. Also increased the specificity
of the test assertions.

> That change does not really belong to the patch, but is alright by me.

I added this because now that there is a connection parameter that
begins with ldap it could cause a confusing error message if the user
tries to use ldapserviceurl in a service file and are then given an
error message that roughly says 'invalid LDAP URL ".*" scheme must be
ldap://'.

Thanks,
Andrew Jackson
From 054f75860c0d6ce6493d669a5a466d3d1134d331 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <[email-masked]>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Add ldapservice connection parameter

-- v5 patch changes

-- v6 patch changes

-- v7 patch changes

Appended error message when ldapServiceLookup returns 2 during
ldapserviceurl lookup. In the context of pg_service.conf parsing return
code 2 is okay and service file parsing continues because there may be a
fallback LDAP URL. Here there is no fallback URL so returning an error
with an appropriate message makes sense. Added additional test case for
this condition. Also increased specificity of tests by wrapping expected
stderr/stdout in ^$, explicitly showing more quotations, and adding
"psql: error"
---
 doc/src/sgml/libpq.sgml                       |  43 +++-
 src/interfaces/libpq/fe-connect.c             |  54 +++-
 src/interfaces/libpq/libpq-int.h              |   1 +
 .../t/003_ldap_connection_param_lookup.pl     | 234 ++++++++++++++++++
 4 files changed, 325 insertions(+), 7 deletions(-)

 
+    <listitem>
+     <para>
+      <indexterm>
index db9b4c8edbf..01a01655490 100644
 
+	/*
 
 #ifdef USE_LDAP

+				if (strcmp(key, "ldapserviceurl") == 0)
+				{
+					libpq_append_error(errorMessage,
+									   "ldapserviceurl parameters are not supported in service file \"%s\", line %d",
+									   serviceFile,
+									   linenr);
+					result = 3;
+					goto exit;
+				}
+
 				if (strcmp(key, "servicefile") == 0)
 				{
 					libpq_append_error(errorMessage,
@@ -6726,6 +6748,36 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 	PQconninfoOption *sslmode_default = NULL,
 			   *sslrootcert = NULL;
 	char	   *tmp;
+#ifdef USE_LDAP
+	int rc;
+	const char *ldapserviceurl = conninfo_getval(options, "ldapserviceurl");
+
+	if (ldapserviceurl == NULL)
+		ldapserviceurl = getenv("PGLDAPSERVICEURL");
+
+	if (ldapserviceurl != NULL)
+	{
+
+		/*
+		 * ldapServiceLookup has 4 potential return values. We only care here
+		 * if it succeeded, if it failed we dont care why, return failure.
+		 */
+		if ((rc = ldapServiceLookup(ldapserviceurl, options, errorMessage)) != 0){
+
+			/*
+			 * ldapServiceLookup == 2 is the only return code for libpq_append_error
+			 * that does not append error because when used in pg_service.conf it is
+			 * allowed to fallback to additional URLs without failing.
+			 */
+			if (rc == 2)
+				libpq_append_error(errorMessage,
+						           "connection could not be established to ldapserviceurl: \"%s\"",
+								   ldapserviceurl);
+
+			return false;
+		}
+	}
+#endif
 
 	/*
index 359fc7a998a..ce6aa33992e 100644
 
 # File defined with no contents, used as default value for
@@ -196,10 +285,155 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	unlink($srvfile_default);
+}
+
+# Test ldapserviceurl connection parameter
+{
+	# Create copy of valid file
+	my $srvfile_default = "$td/pg_service.conf";
+	copy($srvfile_valid, $srvfile_default);
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapserviceurl"',
+		sql => "SELECT 'connect2_4', current_setting('application_name')",
+		expected_stdout => qr/^connect2_4\|application_name_from_ldap$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:1234/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapserviceurl"',
+		expected_stderr => qr/^psql: error: connection could not be established to ldapserviceurl: \".*\"$/);
+
+	$dummy_node->connect_ok(
+		"postgres://?ldapserviceurl=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapserviceurl" in uri format',
+		sql => "SELECT 'connect2_5', current_setting('application_name')",
+		expected_stdout => qr/^connect2_5\|application_name_from_ldap$/);
+
+	local $ENV{PGLDAPSERVICEURL} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "ldapserviceurl" provided by env var',
+		sql => "SELECT 'connect2_6', current_setting('application_name')",
+		expected_stdout => qr/^connect2_6\|application_name_from_ldap$/);
+
+	$dummy_node->connect_fails(
+		'ldapserviceurl=ldap://localhost:invalidport/dc=example,dc=net?description?one?(cn=mydatabase)',
+		'connection fails with because valid PGLDAPSERVICEURL env var overridden by explicit parameter with bad port',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": invalid port number$/);
+
+	delete $ENV{PGLDAPSERVICEURL};
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) application_name=explicit_parameter",
+		'connection with correct "ldapservice" but with application_name overridden by explicit connection parameter',
+		sql => "SELECT 'connect2_7', current_setting('application_name')",
+		expected_stdout => qr/^connect2_7\|explicit_parameter$/);
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) service=pg_service_application_name",
+		'connection with correct "ldapservice" string, service file application_name overridden by ldap, geqo off set by service file',
+		sql => "SELECT 'connect2_8', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout => qr/^connect2_8\|application_name_from_ldap\|off$/);
+
+	# test that geqo grabbed from service file service my_srv_geqo_off via LDAP lookup
+	# conflicting application names present in both LDAP services queried (ldapserviceurl and service file)
+	# application name from service file is ignored because it has already been set by ldapserviceurl lookup
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) service=my_srv_geqo_off",
+		'connection with 2 LDAP lookups (pg_service.conf and ldapserviceurl)',
+		sql => "SELECT 'connect2_9', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout => qr/^connect2_9\|application_name_from_ldap\|off$/);
+
+	$dummy_node->connect_fails(
+		'service=ldapserviceurl_in_pg_service_test',
+		'connection fails with ldapserviceurl specified in pg_service.conf file',
+		expected_stderr => qr/^psql: error: ldapserviceurl parameters are not supported in service file .*, line .*$/ );
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=ldapurlinldapurl)",
+		'ldapserviceurl that points to another ldapserviceurl will be ignored',
+		sql => "SELECT 'connect2_10', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout=> qr/^connect2_10\|application_name_from_ldapurlinldapurl\|on$/);
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
 
+# negative tests for logic inside ldapServiceLookup
+{
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=http://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'ldapserviceurl that doesnt begin with ldap:// fails',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": scheme must be ldap:\/\/$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/",
+		'ldapserviceurl that does not specify distinguished name',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": missing distinguished name$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?",
+		'ldapserviceurl that does not specify attribute',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": must have exactly one attribute$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description",
+		'ldapserviceurl that does not provide scope',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": must have search scope \(base\/one\/sub\)$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one",
+		'ldapserviceurl that does not specify filter',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": no filter$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:invalidportnumber/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'ldapserviceurl contains invalid port number',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": invalid port number$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description,dn?one?(cn=mydatabase)",
+		'ldapserviceurl that contains 2 attributes',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": must have exactly one attribute$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?invalidscope?(cn=mydatabase)",
+		'ldapserviceurl that does not provide valid scope',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": must have search scope \(base\/one\/sub\)$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?sub?(cn=*)",
+		'ldapserviceurl that contains more than one entry',
+		expected_stderr => qr/^psql: error: more than one entry found on LDAP lookup$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=doesnotexist)",
+		'ldapserviceurl with no ldap entry',
+		expected_stderr => qr/^psql: error: no entry found on LDAP lookup$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=noentries)",
+		'ldapserviceurl that has no connection attributes',
+		expected_stderr => qr/^psql: error: attribute has no values on LDAP lookup$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=missingequal)",
+		'ldapservicesurl that has missing equal sign',
+		expected_stderr => qr/^psql: error: missing \"\=\" after \"application_name\n\" in connection info string$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=invalidconnoptionservice)",
+		'ldapserviceurl that contains nonexistent connection option',
+		expected_stderr => qr/^psql: error: invalid connection option \"invalidconnoption\"$/);
+
+}
+
 $node->teardown_node;
 
 done_testing();
-- 
2.51.2

---

From: Zsolt Parragi <[email-masked]>
Date: Wed, 8 Apr 2026 08:59:33 +0100

Hello

+ /*

This comment seems to be confusing to me, at first I thought that it
is the opposite of what the code below does, and then I realized that
no, it's just difficult to understand.

Maybe something like:

  /*
   * ldapServiceLookup() return code 2 means the LDAP server could
   * not be contacted. Unlike other non-zero returns, it does not
   * append an error message, because in pg_service.conf parsing
   * the caller silently falls back to the next URL. Here there is
   * no fallback, so we must provide an error message ourselves.
   */

+        This option specifies an LDAP query that can be used to
reference connection parameters
+        stored on an LDAP server. Any connection parameter that is
looked up in this way is
+        overridden by explicitly named connection parameters or
environment variables. This

Is the environment variable part true? ldapServiceLookup is now at
line 6765, environment variables are handled later at 6794 in
conninfo_add_defaults, so it is later, but it also has a NULL check in
it. If a value is already set in ldapServiceLookup, the environment
variable loop later won't override it.

---

From: Andrew Jackson <[email-masked]>
Date: Wed, 8 Apr 2026 11:31:54 -0500

Thank you for catching that. Attached patch adds your comment verbatim
and fixes the docs that previously contained incorrect info about the
envvar override.

I also added 2 new tests: one that validates that LDAP lookups
override envvars and another that tests the stderr when parsing an
LDAP entry with an unterminated quote in the option value.
Additionally I made some minor changes in other comments and in tests.

Thanks,
Andrew Jackson

> variable loop later won't override it.
From 98625b333a1ae21a28c5ad3fec1c3b46cd15dcf4 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <[email-masked]>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Add ldapservice connection parameter

-- v5 patch changes

-- v6 patch changes

-- v7 patch changes

v8

- Corrected documentation to specify that envvars do not in fact
  override parameters looked up from ldap
- Fixed confusing comment above libpq_append_error after ldapServiceLookup
- Added unit test to validate logic of ldap lookup overriding ENVVAR
- Added unit test replicating unterminated quote parsing of value in LDAP
  parameter lookup
- Removed unused ldap entry in testing
- changed application_name values for consistency/clarity in testing
---
 doc/src/sgml/libpq.sgml                       |  44 +++-
 src/interfaces/libpq/fe-connect.c             |  56 +++-
 src/interfaces/libpq/libpq-int.h              |   1 +
 .../t/003_ldap_connection_param_lookup.pl     | 246 ++++++++++++++++++
 4 files changed, 340 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..1af06e00bbe 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2350,6 +2350,20 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-ldapserviceurl" xreflabel="ldapserviceurl">
+      <term><literal>ldapserviceurl</literal></term>
+      <listitem>
+       <para>
+        This option specifies an LDAP query that can be used to reference connection parameters
+        stored on an LDAP server. Any connection parameter that is looked up in this way is
+        overridden by explicitly named connection parameters. Please note that environment
+        variables passed to libpq will be overridden by values found during the ldap lookup. This
+        option cannot be specified in a <filename>pg_service.conf</filename> file. This
+        functionality is described in more detail in <xref linkend="libpq-ldap"/>.
+        </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
       <term><literal>target_session_attrs</literal></term>
       <listitem>
@@ -9170,6 +9184,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
@@ -9659,12 +9683,20 @@ user=admin
index db9b4c8edbf..c329da70c2a 100644
 
+	/*
 
 #ifdef USE_LDAP
-				if (strncmp(line, "ldap", 4) == 0)
+				/*
+				 * Is this a potential LDAP URL or an ldapserviceurl parameter?
+				 */
+				if (strncmp(line, "ldap:", 5) == 0)
 				{
 					int			rc = ldapServiceLookup(line, options, errorMessage);

+				if (strcmp(key, "ldapserviceurl") == 0)
+				{
+					libpq_append_error(errorMessage,
+									   "ldapserviceurl parameters are not supported in service file \"%s\", line %d",
+									   serviceFile,
+									   linenr);
+					result = 3;
+					goto exit;
+				}
+
 				if (strcmp(key, "servicefile") == 0)
 				{
 					libpq_append_error(errorMessage,
@@ -6726,6 +6748,38 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 	PQconninfoOption *sslmode_default = NULL,
 			   *sslrootcert = NULL;
 	char	   *tmp;
+#ifdef USE_LDAP
+	int rc;
+	const char *ldapserviceurl = conninfo_getval(options, "ldapserviceurl");
+
+	if (ldapserviceurl == NULL)
+		ldapserviceurl = getenv("PGLDAPSERVICEURL");
+
+	if (ldapserviceurl != NULL)
+	{
+
+		/*
+		 * ldapServiceLookup has 4 potential return values. We only care here
+		 * if it succeeded, if it failed we dont care why, return failure.
+		 */
+		if ((rc = ldapServiceLookup(ldapserviceurl, options, errorMessage)) != 0){
+
+			/*
+			* ldapServiceLookup() return code 2 means the LDAP server could
+			* not be contacted. Unlike other non-zero returns, it does not
+			* append an error message, because in pg_service.conf parsing
+			* the caller silently falls back to the next URL. Here there is
+			* no fallback, so we must provide an error message ourselves.
+			*/
+			if (rc == 2)
+				libpq_append_error(errorMessage,
+						           "connection could not be established to ldapserviceurl: \"%s\"",
+								   ldapserviceurl);
+
+			return false;
+		}
+	}
+#endif
 
 	/*
index 359fc7a998a..8f33a41fbe3 100644
@@ -56,10 +56,87 @@ changetype:add
+description:application_name=appname_from_ldap_mydatabase
 
+# Valid ldap record with geqo and application_name set
+# to test how parameters are set/overridden
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=mydatabasegeqooff,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:mydatabasegeqooff
+description:application_name=appname_from_ldap_lookup_geqo_off
+description:options=--geqo=off
+});
+
+# Invalid LDAP record with no entries
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=noentries,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:noentries
+});
+
+# Invalid LDAP record with missing = after application_name
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=missingequal,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:missingequal
+description:application_name
+});
+
+# Invalid LDAP record with non existent parameter
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=invalidconnoptionservice,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:invalidconnoptionservice
+description:invalidconnoption=1
+});
+
+# Valid LDAP record which has ldapserviceurl parameter
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=ldapurlinldapurl,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:ldapurlinldapurl
+description:ldapserviceurl=ldap://localhost:1234/dc=example,dc=net?description?one?(cn=mydatabasegeqooff)
+description:application_name=appname_from_ldap_ldapurlinldapurl
+description:host=} . $node->host . qq{
+description:port=} . $node->port . qq{
+});
+
+# Invalid LDAP record which has ldapserviceurl parameter
+append_to_file(
+	$ldif_valid, qq{
+version:1
+dn:cn=unterminatedoptionquote,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:unterminatedoptionquote
+description:application_name=\'appname_from_ldap_unterminatedoptionquote
+});
+
 $ldap->ldapadd_file($ldif_valid);
 
@@ -80,6 +157,16 @@ append_to_file(
+[geqo_off]
 
 # File defined with no contents, used as default value for
@@ -196,10 +283,169 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	unlink($srvfile_default);
+}
+
+# Test ldapserviceurl connection parameter
+{
+	# Create copy of valid file
+	my $srvfile_default = "$td/pg_service.conf";
+	copy($srvfile_valid, $srvfile_default);
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapserviceurl"',
+		sql => "SELECT 'connect2_4', current_setting('application_name')",
+		expected_stdout => qr/^connect2_4\|appname_from_ldap_mydatabase$/);
+
+	$dummy_node->connect_fails(
+		"ldapserviceurl=ldap://localhost:1234/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapserviceurl"',
+		expected_stderr => qr/^psql: error: connection could not be established to ldapserviceurl: \".*\"$/);
+
+	$dummy_node->connect_ok(
+		"postgres://?ldapserviceurl=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapserviceurl" in uri format',
+		sql => "SELECT 'connect2_5', current_setting('application_name')",
+		expected_stdout => qr/^connect2_5\|appname_from_ldap_mydatabase$/);
+
+	local $ENV{PGLDAPSERVICEURL} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "ldapserviceurl" provided by env var',
+		sql => "SELECT 'connect2_6', current_setting('application_name')",
+		expected_stdout => qr/^connect2_6\|appname_from_ldap_mydatabase$/);
+
+	$dummy_node->connect_fails(
+		'ldapserviceurl=ldap://localhost:invalidport/dc=example,dc=net?description?one?(cn=mydatabase)',
+		'connection fails with because valid PGLDAPSERVICEURL env var overridden by explicit parameter with bad port',
+		expected_stderr => qr/^psql: error: invalid LDAP URL \".*\": invalid port number$/);
+
+	delete $ENV{PGLDAPSERVICEURL};
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) application_name=appname_from_explicit_parameter",
+		'connection with correct "ldapserviceurl" but with application_name overridden by explicit connection parameter',
+		sql => "SELECT 'connect2_7', current_setting('application_name')",
+		expected_stdout => qr/^connect2_7\|appname_from_explicit_parameter$/);
+
+	local $ENV{PGAPPNAME} = "envvar_parameter";
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapserviceurl" but with application_name overriding envvar',
+		sql => "SELECT 'connect2_8', current_setting('application_name')",
+		expected_stdout => qr/^connect2_8\|appname_from_ldap_mydatabase$/);
+
+	delete $ENV{PGAPPNAME};
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) service=pg_service_application_name",
+		'connection with correct "ldapserviceurl" string, service file application_name overridden by ldap, geqo off set by service file',
+		sql => "SELECT 'connect2_9', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout => qr/^connect2_9\|appname_from_ldap_mydatabase\|off$/);
+
+	# test that geqo grabbed from service file service geqo_off via LDAP lookup
+	# conflicting application names present in both LDAP services queried (ldapserviceurl and service file)
+	# application name from service file lookup is ignored because it has already been set by ldapserviceurl lookup
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) service=geqo_off",
+		'connection with 2 LDAP lookups (pg_service.conf and ldapserviceurl)',
+		sql => "SELECT 'connect2_10', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout => qr/^connect2_10\|appname_from_ldap_mydatabase\|off$/);
+
+	$dummy_node->connect_fails(
+		'service=ldapserviceurl_in_pg_service_test',
+		'connection fails with ldapserviceurl specified in pg_service.conf file',
+		expected_stderr => qr/^psql: error: ldapserviceurl parameters are not supported in service file .*, line .*$/ );
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=ldapurlinldapurl)",
+		'ldapserviceurl that points to another ldapserviceurl will be ignored',
+		sql => "SELECT 'connect2_11', current_setting('application_name'), current_setting('geqo');",
+		expected_stdout=> qr/^connect2_11\|appname_from_ldap_ldapurlinldapurl\|on$/);
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
 
+		"ldapserviceurl=ldap://localhost:invalidportnumber/dc=example,dc=net?description?one?(cn=mydatabase)", 'ldapserviceurl contains invalid port number',
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=unterminatedoptionquote)",
+		'ldapserviceurl that contains unterminated quote ',
+		expected_stderr => qr/^psql: error: unterminated quoted string in connection info string$/);
+
+}
+
 $node->teardown_node;
 
 done_testing();
-- 
2.51.2