核心概念:libpq
PostgreSQL 为 C 语言提供了一个官方的客户端库,叫做 libpq,它是与 PostgreSQL 服务器交互的标准接口,你所有的 C 程序,无论是简单的命令行工具还是复杂的应用程序,都将通过这个库来连接和操作数据库。
libpq 提供了一系列以 PQ 为前缀的函数,PQconnectdb, PQexec, PQgetvalue 等。
环境准备
在开始编码之前,你需要确保你的系统上安装了 PostgreSQL 开发包。
在 Linux (Ubuntu/Debian) 上:
# 安装 PostgreSQL 服务器和客户端开发库 sudo apt-get update sudo apt-get install postgresql libpq-dev
postgresql 包包含服务器和客户端工具,libpq-dev 包含了编译 C 程序所需的头文件(.h)和链接库(.a / .so)。
在 macOS 上 (使用 Homebrew):
# 安装 PostgreSQL brew install postgresql # Homebrew 通常会自动配置好链接器,你不需要额外安装开发包
在 Windows 上:
在 Windows 上,最简单的方式是使用 PostgreSQL 官方安装程序,安装程序会提供一个名为 pgAdmin 的图形界面工具,并且会自动将 libpq 的头文件和库文件路径配置好,方便 Visual Studio 或 MinGW 等开发环境使用。
一个完整的示例程序
下面是一个从头到尾的完整示例,它会连接到 PostgreSQL,执行一个查询,并打印结果。
步骤 1: 创建数据库和表
你需要一个可以连接的数据库,使用 psql 命令行工具创建一个测试数据库和表。
# 切换到 postgres 用户(如果需要)
sudo -u postgres psql
-- 在 psql shell 中执行以下命令
CREATE DATABASE mytestdb;
\c mytestdb;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(50) UNIQUE
);
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
\q
步骤 2: 编写 C 代码
创建一个名为 main.c 的文件,并粘贴以下代码。
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h> // PostgreSQL 客户端头文件
// 错误处理函数,打印 PQerrorMessage 并退出
void exit_nicely(PGconn *conn) {
PQfinish(conn);
exit(1);
}
int main() {
// 1. 定义连接字符串
// 格式: "host=localhost dbname=mytestdb user=postgres password=yourpassword"
// 注意:在生产环境中,不要在代码中硬编码密码!
const char *conninfo = "host=localhost dbname=mytestdb user=postgres";
// 2. 建立连接
PGconn *conn = PQconnectdb(conninfo);
// 检查连接状态
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
exit_nicely(conn);
}
printf("Connection to database successful.\n");
// 3. 执行 SQL 查询
const char *query = "SELECT id, name, email FROM users;";
PGresult *res = PQexec(conn, query);
// 检查查询结果
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "Query failed: %s", PQerrorMessage(conn));
PQclear(res);
exit_nicely(conn);
}
printf("Query executed successfully.\n");
// 4. 处理查询结果
int nfields = PQnfields(res); // 获取列数
int ntuples = PQntuples(res); // 获取行数
// 打印列名
for (int i = 0; i < nfields; i++) {
printf("%-15s", PQfname(res, i));
}
printf("\n");
// 打印每一行的数据
for (int i = 0; i < ntuples; i++) {
for (int j = 0; j < nfields; j++) {
// PQgetvalue(res, row_num, col_num)
printf("%-15s", PQgetvalue(res, i, j));
}
printf("\n");
}
// 5. 释放资源
PQclear(res); // 释放 PGresult 对象
PQfinish(conn); // 关闭连接并释放 PGconn 对象
return 0;
}
步骤 3: 编译和链接
这是最关键的一步,你需要告诉编译器去哪里找 libpq 的头文件和库文件。
使用 gcc (Linux/macOS):
# -I 指定头文件路径,-L 指定库文件路径,-l 指定要链接的库 # 对于系统默认安装,-I 和 -L 通常可以省略 gcc -o pg_example main.c -I/usr/include/postgresql -L/usr/lib/postgresql -lpq
更简单的方式 (libpq-dev 已正确安装):
# gcc 通常能自动找到 gcc -o pg_example main.c -lpq
在 Windows (Visual Studio) 中:
- 在项目属性中,配置 "附加包含目录" 指向 PostgreSQL 的
include目录(C:\Program Files\PostgreSQL\15\include)。 - 配置 "附加库目录" 指向
lib目录(C:\Program Files\PostgreSQL\15\lib)。 - 在 "链接器" -> "输入" -> "附加依赖项" 中添加
libpq.lib。
步骤 4: 运行程序
./pg_example
预期输出:
Connection to database successful.
Query executed successfully.
id name email
1 Alice alice@example.com
2 Bob bob@example.com
3 Charlie charlie@example.com
关键 libpq 函数详解
| 函数 | 描述 |
|---|---|
PQconnectdb(conninfo) |
使用连接字符串建立连接,返回一个 PGconn* 指针。 |
PQstatus(conn) |
检查连接状态,返回 CONNECTION_OK 或 CONNECTION_BAD。 |
PQerrorMessage(conn) |
返回一个描述上次错误(连接或查询)的字符串。 |
PQfinish(conn) |
关闭与数据库的连接并释放 PGconn 结构体及相关资源。必须调用! |
PQexec(conn, query) |
在连接上执行一个 SQL 命令,返回一个 PGresult* 指针。 |
PQresultStatus(res) |
检查查询结果的状态,常用值:PGRES_COMMAND_OK (INSERT/UPDATE/DELETE 成功), PGRES_TUPLES_OK (SELECT 成功)。 |
PQclear(res) |
释放 PGresult 结构体。每次使用 PQexec 后都必须调用! |
PQntuples(res) |
返回结果集中的行数。 |
PQnfields(res) |
返回结果集中的列数。 |
PQfname(res, column_number) |
获取指定列的名称。 |
PQgetvalue(res, row_number, column_number) |
获取指定行和列的值(以 C 字符串形式返回)。 |
重要注意事项
1 安全性:不要硬编码密码
在上面的例子中,为了方便,我们在连接字符串中直接写了密码,这是一个严重的安全风险。
正确做法:
-
使用
~/.pgpass文件(推荐)。- 在你的用户主目录下创建一个名为
.pgpass的文件。 - 格式为:
hostname:port:database:username:password localhost:5432:mytestdb:postgres:yourpassword- 设置文件权限为仅当前用户可读写:
chmod 600 ~/.pgpass - 你的程序可以不写密码连接了:
const char *conninfo = "host=localhost dbname=mytestdb user=postgres";
- 在你的用户主目录下创建一个名为
-
使用环境变量
PGPASSWORD。- 在运行程序前设置环境变量:
export PGPASSWORD=yourpassword - 程序连接时,
libpq会自动读取这个变量。
- 在运行程序前设置环境变量:
2 预处理语句(防止 SQL 注入)
对于动态的 SQL 查询(用户输入作为查询条件),直接拼接字符串是非常危险的,会导致 SQL 注入攻击。
libpq 提供了预处理语句来安全地处理这种情况。
示例:
// ... 连接代码同上 ...
const char *paramValues[1];
int paramLengths[1];
int paramFormats[1];
Oid paramTypes[1];
// 假设从用户那里获取了名字
const char *user_input_name = "Bob";
// 1. 准备语句
PGresult *res_prepare = PQprepare(conn, "get_user_by_name", "SELECT id, name, email FROM users WHERE name = $1;", 1, NULL);
if (PQresultStatus(res_prepare) != PGRES_COMMAND_OK) {
fprintf(stderr, "Prepare statement failed: %s", PQerrorMessage(conn));
PQclear(res_prepare);
exit_nicely(conn);
}
PQclear(res_prepare);
// 2. 绑定参数
paramValues[0] = user_input_name;
paramLengths[0] = strlen(user_input_name);
paramFormats[0] = 0; // 文本格式
// 3. 执行语句
PGresult *res_execute = PQexecPrepared(conn, "get_user_by_name", 1, paramValues, paramLengths, paramFormats, 0);
if (PQresultStatus(res_execute) != PGRES_TUPLES_OK) {
fprintf(stderr, "Execute prepared statement failed: %s", PQerrorMessage(conn));
PQclear(res_execute);
exit_nicely(conn);
}
// ... 处理 res_execute 结果 ...
PQclear(res_execute);
// ... 关闭连接 ...
在这个例子中,$1 是一个占位符。libpq 会确保 user_input_name 的值被正确地转义和处理,从而防止 SQL 注入。
使用 C 语言操作 PostgreSQL 的核心流程是:
- 包含头文件:
#include <libpq-fe.h> - 连接数据库:
PQconnectdb()-> 检查PQstatus() - 执行命令:
PQexec()-> 检查PQresultStatus() - 处理结果:使用
PQntuples(),PQnfields(),PQgetvalue()等 - 释放资源:
PQclear()释放结果,PQfinish()关闭连接 - 注意安全:使用
.pgpass或环境变量管理密码,使用预处理语句处理用户输入。
libpq 功能非常强大,除了上述功能,还支持异步操作、复制、通知/监听等高级特性,你可以查阅 PostgreSQL 官方文档 获取更全面的信息。
