目录结构
注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:
1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成
深入理解PostgreSQL数据库之连接服务文件的使用和实现
- 文章快速说明索引
- 功能使用背景说明
- 功能实现源码解析

文章快速说明索引
学习目标:
做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。
学习内容:(详见目录)
1、连接服务文件的使用和实现
学习时间:
2025年03月20日 20:00:54
学习产出:
1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习
注:下面我们所有的学习环境是Centos8+PostgreSQL master+Oracle19C+MySQL8.0
postgres=# select version();version
---------------------------------------------------------------------------------PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.1.0, 64-bit
(1 row)postgres=##-----------------------------------------------------------------------------#SQL> select * from v$version; BANNER Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production
BANNER_FULL Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production
CON_ID 0#-----------------------------------------------------------------------------#mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27 |
+-----------+
1 row in set (0.06 sec)mysql>
功能使用背景说明
以下内容来自于PostgreSQL中文手册:
- 34.17. 连接服务文件,点击前往
连接服务文件允许 libpq 连接参数与一个单一服务名称关联:
- 那个服务名称可以在一个 libpq 连接字符串里面指定,与其相关的设置将被使用。
- 这允许在不重新编译 libpq-using 应用的前提下修改连接参数。
- 服务名称也可以被使用
PGSERVICE
环境变量来指定。服务名称可以在每个用户的服务文件或系统范围的文件中定义:
- 如果同一个服务名称存在于用户文件和系统文件中,则用户文件优先。默认情况下,每个用户的服务文件名为
~/.pg_service.conf
。- 在Microsoft Windows上,它的名称为
%APPDATA%\postgresql\.pg_service.conf
(其中%APPDATA%
指用户配置文件夹中的应用数据子目录)。- 可以通过设置环境变量
PGSERVICEFILE
来指定不同的文件名。系统范围的文件名为pg_service.conf
。- 默认情况下,在PostgreSQL安装的
etc
目录中寻找 (使用pg_config --sysconfdir
来准确识别此目录)。可以通过设置环境变量PGSYSCONFDIR
来指定另一个目录,但不能指定不同的文件名。或者服务文件使用一种
INI 文件
格式,其中小节名是服务名并且参数是连接参数。列表见中文手册:第 34.1.2 节,点击前往。在PostgreSQL安装的share/pg_service.conf.sample
中提供了一个例子文件:
[postgres@localhost:~/test/share]$ pwd
/home/postgres/test/share
[postgres@localhost:~/test/share]$ cat pg_service.conf.sample
#
# Connection configuration file
#
# A service is a set of named connection parameters. You may specify
# multiple services in this file. Each starts with a service name in
# brackets. Subsequent lines have connection configuration parameters of
# the pattern "param=value" or LDAP URLs starting with "ldap://"
# to look up such parameters. A sample configuration for postgres is
# included in this file. Lines beginning with '#' are comments.
#
# Copy this to your sysconf directory (typically /usr/local/pgsql/etc) and
# rename it pg_service.conf.
#
#
#[postgres]
#dbname=postgres
#user=postgres
[postgres@localhost:~/test/share]$
从服务文件中获取的连接参数与从其他来源获取的参数相结合(这个很重要,下面我们对这三种设置进行调试一下):
- 服务文件设置覆盖相应的环境变量
- 然后反过来可以由连接字符串中直接给出的值覆盖
# comment
[mydb]
host=somehost
port=5433
user=admin
例如,使用上面的服务文件,连接字符串service=mydb port=5434
将使用主机somehost
,端口5434
, 用户admin
,以及由环境变量或内置默认所设置的其他参数。
下面来看一个使用的案例,使用psql
如下:
[postgres@localhost:~/test/bin]$ cat ~/.pg_service.conf
#
# Connection configuration file
#
# A service is a set of named connection parameters. You may specify
# multiple services in this file. Each starts with a service name in
# brackets. Subsequent lines have connection configuration parameters of
# the pattern "param=value" or LDAP URLs starting with "ldap://"
# to look up such parameters. A sample configuration for postgres is
# included in this file. Lines beginning with '#' are comments.
#
# Copy this to your sysconf directory (typically /usr/local/pgsql/etc) and
# rename it pg_service.conf.
#
#
[sname01]
dbname=postgres
user=postgres
port=5432[sname02]
dbname=postgres
user=postgres
port=5432[postgres@localhost:~/test/bin]$ ./psql
psql (18devel)
Type "help" for help.postgres=# \q
[postgres@localhost:~/test/bin]$ ./psql service=sname01
psql (18devel)
Type "help" for help.postgres=# \q
[postgres@localhost:~/test/bin]$ ./psql service=sname02
psql (18devel)
Type "help" for help.postgres=# \q
[postgres@localhost:~/test/bin]$
注:我这里修改一下PostgreSQL的源码,如下:
[postgres@localhost:~/postgres_gitee → master]$ git diff src/interfaces/libpq/exports.txt src/interfaces/libpq/fe-connect.c src/interfaces/libpq/libpq-fe.h > ./1.patch
[postgres@localhost:~/postgres_gitee → master]$
[postgres@localhost:~/postgres_gitee → master]$ cat 1.patch
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..8188b8fe13e 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207PQgetAuthDataHook 208PQdefaultAuthDataHook 209PQfullProtocolVersion 210
+PQapplicationname 211
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d5051f5e820..c42d44af6bc 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -7393,6 +7393,14 @@ PQservice(const PGconn *conn)return conn->pgservice;}+char *
+PQapplicationname(const PGconn *conn)
+{
+ if (!conn)
+ return NULL;
+ return conn->appname;
+}
+char *PQuser(const PGconn *conn){
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 34ddfdb1831..0279c95101b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -397,6 +397,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 *PQapplicationname(const PGconn *conn);extern char *PQuser(const PGconn *conn);extern char *PQpass(const PGconn *conn);extern char *PQhost(const PGconn *conn);
[postgres@localhost:~/postgres_gitee → master]$
使用libpq的接口,如下:
#include <iostream>#include "libpq-fe.h"using namespace std;int main()
{// const char*conninfo="host=localhost port=5432 user=postgres dbname=postgres password=1";const char *conninfo = "service=sname02";const char *createTableCmd = "create temp table t1(id int, name text);";PGconn *conn = PQconnectdb(conninfo);if (PQstatus(conn) == CONNECTION_OK){PGresult *result = NULL;cout << "连接PostgreSQL数据库 成功!" << endl;result = PQexec(conn, createTableCmd);if (result != NULL && PQresultStatus(result) == PGRES_COMMAND_OK){cout << "创建表成功" << endl;}else{cout << "创建表失败, errorMessage是" << PQerrorMessage(conn);}char *service = PQservice(conn);char *appname = PQapplicationname(conn);cout << "PQservice:" << (service == NULL ? "NULL" : service) << endl;cout << "PQapplicationname:" << (appname == NULL ? "NULL" : appname) << endl;PQfinish(conn);cout << "与PostgreSQL数据库连接 关闭!" << endl;}else{cout << "连接失败!" << endl;}return 0;
}/* g++ libpqtest.cpp -lpq -L/home/postgres/test/lib -I/home/postgres/test/include -o main -g -O0 */
/* export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PG_HOME/lib */
编译执行该libpq应用,如下:
[postgres@localhost:~]$ g++ libpqtest.cpp -lpq -L/home/postgres/test/lib -I/home/postgres/test/include -o main -g -O0
[postgres@localhost:~]$
[postgres@localhost:~]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PG_HOME/lib
[postgres@localhost:~]$
[postgres@localhost:~]$ ./main
连接PostgreSQL数据库 成功!
创建表成功
PQservice:sname02
PQapplicationname:NULL
与PostgreSQL数据库连接 关闭!
[postgres@localhost:~]$
于是如上,这就允许了:在不重新编译 libpq 应用的前提下修改连接参数,如下:
或者,如下:
功能实现源码解析
在开始之前,我们修改一下相关内容:
修改1:
postgres=# create user u1 with superuser password '1';
CREATE ROLE
postgres=# \duList of rolesRole name | Attributes
-----------+------------------------------------------------------------postgres | Superuser, Create role, Create DB, Replication, Bypass RLSu1 | Superuserpostgres=# \c postgres u1
You are now connected to database "postgres" as user "u1".
postgres=#
修改2:
修改3:
[postgres@localhost:~/postgres_gitee → master]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PG_HOME/lib
[postgres@localhost:~/postgres_gitee → master]$ export PGAPPNAME=song01
[postgres@localhost:~/postgres_gitee → master]$
开始调试,如下:
输入的连接字符串,如下:
// conninfo0x400ba0 "service=sname02 user=u1"
解释一下:
- 在解析上面连接字符串中得到了
service
,因为我这里不设置PGSERVICE
和PGSERVICEFILE
因此找的service文件就是家目录下的 - 大家在使用时可以根据自己的需要自行去配置
接下来就是在函数parseServiceFile
中解析service文件,来匹配对应的service:
然后就开始解析该service的具体配置,如下:
继续后面的user
、port
和application_name
!
这里需要注意的是:因为在连接字符串中已经指定了user
,所以这里的服务文件中的值不能覆盖,如下:
在处理service的逻辑parseServiceInfo()->parseServiceFile()
之后,就要进入到下面for循环处理尚未指定的option:
// src/interfaces/libpq/fe-connect.c/** Add the default values for any unspecified options to the connection* options array.* 将任何未指定选项的默认值添加到连接选项数组。** Defaults are obtained from a service file, environment variables, etc.* 默认值从服务文件、环境变量等获取。** Returns true if successful, otherwise false; errorMessage, if supplied,* is filled in upon failure. Note that failure to locate a default value* is not an error condition here --- we just leave the option's value as* NULL.* 如果成功则返回 true,否则返回 false;* 如果提供了 errorMessage,则在失败时填写。* 请注意,无法找到默认值在这里不属于错误情况 --- 我们只是将选项的值保留为 NULL。*/
static bool
conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
{.../** If there's a service spec, use it to obtain any not-explicitly-given* parameters. Ignore error if no error message buffer is passed because* there is no way to pass back the failure message.* 如果有服务规范,则使用它来获取任何未明确给出的参数。* 如果没有传递错误消息缓冲区,则忽略错误,因为没有办法传回失败消息。*/if (parseServiceInfo(options, errorMessage) != 0 && errorMessage)return false;.../** Get the fallback resources for parameters not specified in the conninfo* string nor the service.* 获取 conninfo 字符串或服务中未指定的参数的后备资源。*/for (option = options; option->keyword != NULL; option++){...}...
}
这里说明一下各相关option的值:
user: u1 // 来源于conninfo
dbname: postgres // 来源于service文件
port: 5432 // 来源于service文件
application_name: whatever // 来源于service文件
从上面可以看待配置的环境变量PGAPPNAME
并没有起作用,因为该option.val已经从service文件中获取到了。下面我们将service文件中的对应行删掉,(这次在上面for循环的处理)如下:
其结果如下: