v9-0001-servicefile-option-usage-on-connection-string-fea.patch
application/octet-stream
Filename: v9-0001-servicefile-option-usage-on-connection-string-fea.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v9-0001
Subject: servicefile option usage on connection string feature and its tests.
| File | + | − |
|---|---|---|
| doc/src/sgml/libpq.sgml | 36 | 1 |
| doc/src/sgml/ref/psql-ref.sgml | 9 | 0 |
| src/bin/psql/command.c | 2 | 0 |
| src/interfaces/libpq/exports.txt | 6 | 5 |
| src/interfaces/libpq/fe-connect.c | 35 | 4 |
| src/interfaces/libpq/libpq-fe.h | 1 | 0 |
| src/interfaces/libpq/libpq-int.h | 1 | 0 |
| src/interfaces/libpq/t/006_service.pl | 96 | 0 |
From 3f60cf47e50884aec2e77ac9a65b44cd84f63298 Mon Sep 17 00:00:00 2001
From: Ryo Kanbayashi <ryo.contact@gmail.com>
Date: Mon, 9 Jun 2025 22:20:04 +0900
Subject: [PATCH v9] servicefile option usage on connection string feature and
its tests.
---
doc/src/sgml/libpq.sgml | 37 ++++++++++-
doc/src/sgml/ref/psql-ref.sgml | 9 +++
src/bin/psql/command.c | 2 +
src/interfaces/libpq/exports.txt | 11 +--
src/interfaces/libpq/fe-connect.c | 39 +++++++++--
src/interfaces/libpq/libpq-fe.h | 1 +
src/interfaces/libpq/libpq-int.h | 1 +
src/interfaces/libpq/t/006_service.pl | 96 +++++++++++++++++++++++++++
8 files changed, 186 insertions(+), 10 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 695fe958c3e..5e410ae8004 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2320,6 +2320,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-servicefile" xreflabel="servicefile">
+ <term><literal>servicefile</literal></term>
+ <listitem>
+ <para>
+ This option specifies the name of the per-user connection service file
+ (see <xref linkend="libpq-pgservice"/>).
+ Defaults to <filename>~/.pg_service.conf</filename>, or
+ <filename>%APPDATA%\postgresql\.pg_service.conf</filename> on
+ Microsoft Windows.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
<term><literal>target_session_attrs</literal></term>
<listitem>
@@ -2760,6 +2773,25 @@ char *PQservice(const PGconn *conn);
</listitem>
</varlistentry>
+ <varlistentry id="libpq-PQserviceFile">
+ <term><function>PQserviceFile</function></term>
+
+ <listitem>
+ <para>
+ Returns the service file name of the active connection.
+<synopsis>
+char *PQserviceFile(const PGconn *conn);
+</synopsis>
+ </para>
+
+ <para>
+ <xref linkend="libpq-PQserviceFile"/> returns <symbol>NULL</symbol> if the
+ <parameter>conn</parameter> argument is <symbol>NULL</symbol>.
+ Otherwise, if there was no service file provided, it returns an empty string.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-PQtty">
<term><function>PQtty</function><indexterm><primary>PQtty</primary></indexterm></term>
@@ -9166,6 +9198,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
Defaults to <filename>~/.pg_service.conf</filename>, or
<filename>%APPDATA%\postgresql\.pg_service.conf</filename> on
Microsoft Windows.
+ <envar>This environment variable</envar> behaves the same as the <xref
+ linkend="libpq-connect-servicefile"/> connection parameter.
</para>
</listitem>
@@ -9596,7 +9630,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
On Microsoft Windows, it is named
<filename>%APPDATA%\postgresql\.pg_service.conf</filename> (where
<filename>%APPDATA%</filename> 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
+ <literal>servicefile</literal> key word in a libpq connection string or by
setting the environment variable <envar>PGSERVICEFILE</envar>.
The system-wide file is named <filename>pg_service.conf</filename>.
By default it is sought in the <filename>etc</filename> directory
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 8f7d8758ca0..e55f144f9a0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -4610,6 +4610,15 @@ bar
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-variables-servicefile">
+ <term><varname>SERVICEFILE</varname></term>
+ <listitem>
+ <para>
+ The service file name, if applicable.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-variables-shell-error">
<term><varname>SHELL_ERROR</varname></term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 81a5ba844ba..4305e398b34 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -4487,6 +4487,7 @@ SyncVariables(void)
SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
SetVariable(pset.vars, "SERVICE", PQservice(pset.db));
+ SetVariable(pset.vars, "SERVICEFILE", PQserviceFile(pset.db));
SetVariable(pset.vars, "USER", PQuser(pset.db));
SetVariable(pset.vars, "HOST", PQhost(pset.db));
SetVariable(pset.vars, "PORT", PQport(pset.db));
@@ -4521,6 +4522,7 @@ UnsyncVariables(void)
{
SetVariable(pset.vars, "DBNAME", NULL);
SetVariable(pset.vars, "SERVICE", NULL);
+ SetVariable(pset.vars, "SERVICEFILE", NULL);
SetVariable(pset.vars, "USER", NULL);
SetVariable(pset.vars, "HOST", NULL);
SetVariable(pset.vars, "PORT", NULL);
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 0625cf39e9a..72b6584e795 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -206,8 +206,9 @@ PQsocketPoll 203
PQsetChunkedRowsMode 204
PQgetCurrentTimeUSec 205
PQservice 206
-PQsetAuthDataHook 207
-PQgetAuthDataHook 208
-PQdefaultAuthDataHook 209
-PQfullProtocolVersion 210
-appendPQExpBufferVA 211
+PQserviceFile 207
+PQsetAuthDataHook 208
+PQgetAuthDataHook 209
+PQdefaultAuthDataHook 210
+PQfullProtocolVersion 211
+appendPQExpBufferVA 212
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index ccb01aad361..2ac783e5e00 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -201,6 +201,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Database-Service", "", 20,
offsetof(struct pg_conn, pgservice)},
+ {"servicefile", "PGSERVICEFILE", NULL, NULL,
+ "Database-Service-File", "", 64,
+ offsetof(struct pg_conn, pgservicefile)},
+
{"user", "PGUSER", NULL, NULL,
"Database-User", "", 20,
offsetof(struct pg_conn, pguser)},
@@ -5062,6 +5066,7 @@ freePGconn(PGconn *conn)
free(conn->dbName);
free(conn->replication);
free(conn->pgservice);
+ free(conn->pgservicefile);
free(conn->pguser);
if (conn->pgpass)
{
@@ -5914,6 +5919,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;
@@ -5933,11 +5939,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];
@@ -6092,7 +6105,17 @@ parseServiceFile(const char *serviceFile,
if (strcmp(key, "service") == 0)
{
libpq_append_error(errorMessage,
- "nested service specifications not supported in service file \"%s\", line %d",
+ "nested \"service\" specifications not supported in service file \"%s\", line %d",
+ serviceFile,
+ linenr);
+ result = 3;
+ 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;
@@ -7469,6 +7492,14 @@ PQservice(const PGconn *conn)
return conn->pgservice;
}
+char *
+PQserviceFile(const PGconn *conn)
+{
+ if (!conn)
+ return NULL;
+ return conn->pgservicefile;
+}
+
char *
PQuser(const PGconn *conn)
{
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 7d3a9df6fd5..c7c443531c2 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -401,6 +401,7 @@ extern int PQrequestCancel(PGconn *conn);
/* Accessor functions for PGconn objects */
extern char *PQdb(const PGconn *conn);
extern char *PQservice(const PGconn *conn);
+extern char *PQserviceFile(const PGconn *conn);
extern char *PQuser(const PGconn *conn);
extern char *PQpass(const PGconn *conn);
extern char *PQhost(const PGconn *conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index a6cfd7f5c9d..5ae4e88f0b7 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -389,6 +389,7 @@ struct pg_conn
char *dbName; /* database name */
char *replication; /* connect as the replication standby? */
char *pgservice; /* Postgres service, if any */
+ char *pgservicefile; /* path to a service file containing service(s) */
char *pguser; /* Postgres username and password, if any */
char *pgpass;
char *pgpassfile; /* path to a file containing password(s) */
diff --git a/src/interfaces/libpq/t/006_service.pl b/src/interfaces/libpq/t/006_service.pl
index 4fe5adc5c2a..b27b3c17d13 100644
--- a/src/interfaces/libpq/t/006_service.pl
+++ b/src/interfaces/libpq/t/006_service.pl
@@ -47,6 +47,21 @@ my $srvfile_default = "$td/pg_service.conf";
# Missing service file.
my $srvfile_missing = "$td/pg_service_missing.conf";
+# "service" param included service file (invalid)
+# including contents of pg_service_valid.conf and a nested service option
+my $srvfile_service_nested = "$td/pg_service_service_nested.conf";
+copy($srvfile_valid, $srvfile_service_nested) or
+ die "Could not copy $srvfile_valid to $srvfile_service_nested: $!";
+append_to_file($srvfile_service_nested, 'service=tmp_srv' . $newline);
+
+# "servicefile" param included service file (invalid)
+# including contents of pg_service_valid.conf and a nested servicefile option
+my $srvfile_servicefile_nested = "$td/pg_service_servicefile_nested.conf";
+copy($srvfile_valid, $srvfile_servicefile_nested) or
+ die "Could not copy $srvfile_valid to $srvfile_servicefile_nested: $!";
+append_to_file($srvfile_servicefile_nested, 'servicefile=' . $srvfile_default . $newline);
+
+
# Set the fallback directory lookup of the service file to the temporary
# directory of this test. PGSYSCONFDIR is used if the service file
# defined in PGSERVICEFILE cannot be found, or when a service file is
@@ -146,6 +161,87 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
unlink($srvfile_default);
}
+# Use correct escaped path for Windows.
+my $srvfile_win_cared = $srvfile_valid;
+$srvfile_win_cared =~ s/\\/\\\\/g;
+
+# Check that servicefile option works as expected
+{
+ $dummy_node->connect_ok(
+ q{service=my_srv servicefile='} . $srvfile_win_cared . q{'},
+ 'service=my_srv servicefile=...',
+ sql => "SELECT 'connect3'",
+ expected_stdout => qr/connect3/
+ );
+
+ # Encode slashes and backslash
+ my $encoded_srvfile = $srvfile_valid =~ s{([\\/])}{
+ $1 eq '/' ? '%2F' : '%5C'
+ }ger;
+
+ # Additionally encode a colon in servicefile path of Windows
+ $encoded_srvfile =~ s/:/%3A/g;
+
+ $dummy_node->connect_ok(
+ 'postgresql:///?service=my_srv&servicefile=' . $encoded_srvfile,
+ 'postgresql:///?service=my_srv&servicefile=...',
+ sql => "SELECT 'connect4'",
+ expected_stdout => qr/connect4/
+ );
+
+ local $ENV{PGSERVICE} = 'my_srv';
+ $dummy_node->connect_ok(
+ q{servicefile='} . $srvfile_win_cared . q{'},
+ 'envvar: PGSERVICE=my_srv + servicefile=...',
+ sql => "SELECT 'connect5'",
+ expected_stdout => qr/connect5/
+ );
+
+ $dummy_node->connect_ok(
+ 'postgresql://?servicefile=' . $encoded_srvfile,
+ 'envvar: PGSERVICE=my_srv + postgresql://?servicefile=...',
+ sql => "SELECT 'connect6'",
+ expected_stdout => qr/connect6/
+ );
+}
+
+# Check that servicefile option takes precedence over PGSERVICEFILE environment variable
+{
+ local $ENV{PGSERVICEFILE} = 'non-existent-file.conf';
+
+ $dummy_node->connect_fails(
+ 'service=my_srv',
+ 'service=... fails with wrong PGSERVICEFILE',
+ expected_stderr => qr/service file "non-existent-file\.conf" not found/
+ );
+
+ $dummy_node->connect_ok(
+ q{service=my_srv servicefile='} . $srvfile_win_cared . q{'},
+ 'servicefile= takes precedence over PGSERVICEFILE',
+ sql => "SELECT 'connect7'",
+ expected_stdout => qr/connect7/
+ );
+}
+
+# Check that service file which contains nested service and servicefile options fails
+{
+ local $ENV{PGSERVICEFILE} = $srvfile_service_nested;
+
+ $dummy_node->connect_fails(
+ 'service=my_srv',
+ 'service=... fails with nested service option in service file',
+ expected_stderr => qr/nested "service" specifications not supported in service file/
+ );
+
+ local $ENV{PGSERVICEFILE} = $srvfile_servicefile_nested;
+
+ $dummy_node->connect_fails(
+ 'service=my_srv',
+ 'servicefile=... fails with nested service option in service file',
+ expected_stderr => qr/nested "servicefile" specifications not supported in service file/
+ );
+}
+
$node->teardown_node;
done_testing();
--
2.45.1.windows.1