diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 8fa0515c6a0..1050db5c983 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2248,6 +2248,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
+
+ servicefile
+
+
+ This option specifies the name of the per-user connection service file
+ (see ).
+ Defaults to ~/.pg_service.conf, or
+ %APPDATA%\postgresql\.pg_service.conf on
+ Microsoft Windows.
+
+
+
+
target_session_attrs
@@ -9504,7 +9517,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
On Microsoft Windows, it is named
%APPDATA%\postgresql\.pg_service.conf (where
%APPDATA% refers to the Application Data subdirectory
- in the user's profile). A different file name can be specified by
+ in the user's profile). A different file name can be specified using the
+ servicefile key word in a libpq connection string or by
setting the environment variable PGSERVICEFILE.
The system-wide file is named pg_service.conf.
By default it is sought in the etc directory
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d5051f5e820..f16468be7d1 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -195,6 +195,9 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Database-Service", "", 20,
offsetof(struct pg_conn, pgservice)},
+ {"servicefile", "PGSERVICEFILE", NULL, NULL,
+ "Database-Service-File", "", 64, -1},
+
{"user", "PGUSER", NULL, NULL,
"Database-User", "", 20,
offsetof(struct pg_conn, pguser)},
@@ -5838,6 +5841,7 @@ static int
parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
{
const char *service = conninfo_getval(options, "service");
+ const char *service_fname = conninfo_getval(options, "servicefile");
char serviceFile[MAXPGPATH];
char *env;
bool group_found = false;
@@ -5857,11 +5861,18 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
return 0;
/*
- * Try PGSERVICEFILE if specified, else try ~/.pg_service.conf (if that
- * exists).
+ * First, check servicefile option on connection string. Second, check
+ * PGSERVICEFILE environment variable. Finally, check ~/.pg_service.conf
+ * (if that exists).
*/
- if ((env = getenv("PGSERVICEFILE")) != NULL)
+ if (service_fname != NULL)
+ {
+ strlcpy(serviceFile, service_fname, sizeof(serviceFile));
+ }
+ else if ((env = getenv("PGSERVICEFILE")) != NULL)
+ {
strlcpy(serviceFile, env, sizeof(serviceFile));
+ }
else
{
char homedir[MAXPGPATH];
@@ -6023,6 +6034,16 @@ parseServiceFile(const char *serviceFile,
goto exit;
}
+ if (strcmp(key, "servicefile") == 0)
+ {
+ libpq_append_error(errorMessage,
+ "nested servicefile specifications not supported in service file \"%s\", line %d",
+ serviceFile,
+ linenr);
+ result = 3;
+ goto exit;
+ }
+
/*
* Set the parameter --- but don't override any previous
* explicit setting.
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..4714c253792 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -123,6 +123,7 @@ tests += {
't/004_load_balance_dns.pl',
't/005_negotiate_encryption.pl',
't/006_service.pl',
+ 't/007_servicefile_opt.pl',
],
'env': {
'with_ssl': ssl_library,
diff --git a/src/interfaces/libpq/t/007_servicefile_opt.pl b/src/interfaces/libpq/t/007_servicefile_opt.pl
new file mode 100644
index 00000000000..7d33bd18563
--- /dev/null
+++ b/src/interfaces/libpq/t/007_servicefile_opt.pl
@@ -0,0 +1,100 @@
+# Copyright (c) 2023-2024, PostgreSQL Global Development Group
+use strict;
+use warnings FATAL => 'all';
+use Config;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+# This tests "servicefile option" on connection string
+
+# Cluster setup which is shared for testing both load balancing methods
+my $node = PostgreSQL::Test::Cluster->new('node');
+
+# Create a data directory with initdb
+$node->init();
+
+# Start the PostgreSQL server
+$node->start();
+
+my $td = PostgreSQL::Test::Utils::tempdir;
+my $srvfile = "$td/pgsrv.conf";
+
+# Create a service file
+open my $fh, '>', $srvfile or die $!;
+if ($windows_os) {
+
+ # Windows: use CRLF
+ print $fh "[my_srv]", "\r\n";
+ print $fh join( "\r\n", split( ' ', $node->connstr ) ), "\r\n";
+
+ # Escape backslashes for use in connection string later
+ $srvfile =~ s/\\/\\\\/g;
+}
+else {
+ # Non-Windows: use LF
+ print $fh "[my_srv]", "\n";
+ print $fh join( "\n", split( ' ', $node->connstr ) ), "\n";
+}
+close $fh;
+
+# Check that servicefile option works as expected
+{
+ $node->connect_ok(
+ q{service=my_srv servicefile='} . $srvfile . q{'},
+ 'service=my_srv servicefile=...',
+ sql => "SELECT 'connect1'",
+ expected_stdout => qr/connect1/
+ );
+
+ # Escape slashes in servicefile path for use in connection string
+ # Consider that the servicefile path may contain backslashes on Windows
+ my $encoded_srvfile = $srvfile =~ s{([\\/])}{
+ $1 eq '/' ? '%2F' : '%5C'
+ }ger;
+
+ # Escape a colon in servicefile path of Windows
+ $encoded_srvfile =~ s/:/%3A/g;
+
+ $node->connect_ok(
+ 'postgresql:///?service=my_srv&servicefile=' . $encoded_srvfile,
+ 'postgresql:///?service=my_srv&servicefile=...',
+ sql => "SELECT 'connect2'",
+ expected_stdout => qr/connect2/
+ );
+
+ local $ENV{PGSERVICE} = 'my_srv';
+ $node->connect_ok(
+ q{servicefile='} . $srvfile . q{'},
+ 'envvar: PGSERVICE=my_srv + servicefile=...',
+ sql => "SELECT 'connect3'",
+ expected_stdout => qr/connect3/
+ );
+
+ $node->connect_ok(
+ 'postgresql://?servicefile=' . $encoded_srvfile,
+ 'envvar: PGSERVICE=my_srv + postgresql://?servicefile=...',
+ sql => "SELECT 'connect4'",
+ expected_stdout => qr/connect4/
+ );
+}
+
+# Check that servicefile option takes precedence over PGSERVICEFILE environment variable
+{
+ local $ENV{PGSERVICEFILE} = 'non-existent-file.conf';
+
+ $node->connect_fails(
+ 'service=my_srv',
+ 'service=... fails with wrong PGSERVICEFILE',
+ expected_stderr => qr/service file "non-existent-file\.conf" not found/
+ );
+
+ $node->connect_ok(
+ q{service=my_srv servicefile='} . $srvfile . q{'},
+ 'servicefile= takes precedence over PGSERVICEFILE',
+ sql => "SELECT 'connect5'",
+ expected_stdout => qr/connect5/
+ );
+}
+
+done_testing();