ZetCode

PostgreSQL C

最后修改于 2020 年 7 月 6 日

这是一个针对 PostgreSQL 数据库的 C 语言编程教程。它涵盖了使用 C API 进行 PostgreSQL 编程的基础知识。

关于 PostgreSQL 数据库

PostgreSQL 是一个功能强大、开源的对象关系型数据库系统。它是一个多用户数据库管理系统,可在包括 Linux、FreeBSD、Solaris、Microsoft Windows 和 Mac OS X 在内的多个平台上运行。PostgreSQL 由 PostgreSQL 全球开发小组开发。

PostgreSQL 具有许多高级特性,如多版本并发控制 (MVCC)、时间点恢复、表空间、异步复制、嵌套事务(保存点)、在线/热备份、复杂的查询规划器/优化器以及用于容错的预写日志。它支持国际字符集、多字节字符编码、Unicode,并且在排序、大小写敏感性和格式化方面具有区域感知能力。

安装

我们将安装 PostgreSQL 数据库和 C 开发库。

$ sudo apt-get install postgresql

在基于 Debian 的系统上,我们可以使用上述命令从软件包中安装 PostgreSQL 数据库。

$ sudo update-rc.d -f postgresql remove
 Removing any system startup links for /etc/init.d/postgresql ...
   /etc/rc0.d/K21postgresql
   /etc/rc1.d/K21postgresql
   /etc/rc2.d/S19postgresql
   /etc/rc3.d/S19postgresql
   /etc/rc4.d/S19postgresql
   /etc/rc5.d/S19postgresql
   /etc/rc6.d/K21postgresql

如果我们从软件包安装 PostgreSQL 数据库,它会自动添加到操作系统的启动脚本中。如果我们只是学习使用数据库,那么每次启动系统时都启动数据库是不必要的。上述命令会删除 PostgreSQL 数据库的任何系统启动链接。

$ sudo apt-get install libpq-dev

为了能够编译 C 示例,我们需要安装 PostgreSQL C 开发库。上面这行命令展示了如何在基于 Debian 的 Linux 系统上进行安装。

$ sudo -u postgres psql postgres
psql (9.3.9)
Type "help" for help.

postgres=# \password postgres

我们为 postgres 用户设置一个密码。

启动和停止 PostgreSQL

在下一节中,我们将展示如何启动、停止 PostgreSQL 数据库以及查询其状态。

$ sudo service postgresql start
 * Starting PostgreSQL 9.3 database server     [ OK ]

在基于 Debian 的 Linux 系统上,我们可以使用 service postgresql start 命令来启动服务器。

$ sudo service postgresql status
9.3/main (port 5432): online

我们使用 service postgresql status 命令来检查 PostgreSQL 是否正在运行。

$ sudo service postgresql stop
 * Stopping PostgreSQL 9.3 database server     [ OK ]

我们使用 service postgresql stop 命令来停止 PostgreSQL。

$ service postgresql status
9.3/main (port 5432): down

此时,service postgresql status 命令会报告 PostgreSQL 数据库已关闭。

创建用户和数据库

在以下步骤中,我们将创建一个新的数据库用户和数据库。

$ sudo -u postgres createuser janbodnar

我们在 PostgreSQL 系统中创建一个新角色。我们允许它具有创建新数据库的能力。在数据库世界中,角色就是一个用户。角色与操作系统用户是分开的。

$ sudo -u postgres psql postgres
psql (9.3.9)
Type "help" for help.

postgres=# ALTER USER janbodnar WITH password 'pswd37';
ALTER ROLE
postgres=# \q

我们使用 psql 命令为新用户添加一个密码。

PostgreSQL 在本地连接上通常使用 trustpeer 身份验证策略。在使用 trust 身份验证策略的情况下,PostgreSQL 假定任何能连接到服务器的人都有权使用他们指定的任何数据库用户名(甚至是超级用户名)访问数据库。建立数据库连接时不需要密码。(数据库和用户列中的限制仍然适用。)trust 身份验证对于单用户工作站上的本地连接是合适的且非常方便。但它通常不适用于多用户机器。在使用 peer 身份验证策略的情况下,数据库用户名必须与操作系统用户名匹配。

$ sudo -u postgres createdb testdb --owner janbodnar

我们使用 createdb 命令创建一个名为 testdb 的新数据库。它的所有者是新的数据库用户。

C99

本教程使用 C99。对于 GNU C 编译器,我们需要添加 -std=c99 选项。对于 Windows 用户,强烈推荐使用 Pelles C IDE。(MSVC 不支持 C99。)

PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

在 C99 中,我们可以将声明与代码混合在一起。在旧的 C 程序中,我们需要将这行代码分成两行。

for(int i=0; i<rows; i++) {

我们也可以在 for 循环中进行初始声明。

libpq 库

libpq 库是 PostgreSQL 的 C 语言接口。它是一组库函数,允许客户端程序与 PostgreSQL 交互。它也是其他几个 PostgreSQL 应用程序接口的底层引擎,包括为 C++、Perl、PHP、Ruby、Python 和 Tcl 编写的接口。

lib_version.c
#include <stdio.h>
#include <libpq-fe.h>

int main() {
    
    int lib_ver = PQlibVersion();

    printf("Version of libpq: %d\n", lib_ver);
    
    return 0;
}

该程序打印 libpq 库的版本。

#include <libpq-fe.h>

libpq-fe.h 文件包含了 C 编程接口的枚举、结构体、常量和函数的定义。

int lib_ver = PQlibVersion();

PQlibVersion 函数返回正在使用的 libpq 的版本。

$ pg_config --includedir
/usr/include/postgresql
$ pg_config --libdir
/usr/lib

pg_config 工具用于查找 C 头文件和目标代码库的位置。

$ gcc -o lib_version lib_version.c -I/usr/include/postgresql -lpq -std=c99

我们使用上述命令编译程序。

$ ./lib_version 
Version of libpq: 90309

库的版本是 9.3.9。

服务器版本

在下面的示例中,我们找出 PostgreSQL 数据库的版本。

server_version.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    int ver = PQserverVersion(conn);

    printf("Server version: %d\n", ver);
    
    PQfinish(conn);

    return 0;
}

该示例连接到 PostgreSQL 数据库,执行一个 PQserverVersion 函数,打印版本,关闭连接,并进行清理。

...
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
...

pg_hba.conf 文件中,我们有默认的 peer 身份验证方法。在这种方法中,数据库用户名必须与操作系统用户名匹配。建立连接不需要密码。

PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

我们使用 PQconnectdb 函数建立到数据库的连接。在连接字符串中,我们提供了用户名和数据库名。

if (PQstatus(conn) == CONNECTION_BAD) {
    
    fprintf(stderr, "Connection to database failed: %s",
        PQerrorMessage(conn));
    do_exit(conn);
}

PQstatus 函数返回连接的状态。如果连接成功,返回 CONNECTION_OK;如果连接不成功,返回 CONNECTION_BADPQerrorMessage 返回最近生成的错误消息。

int ver = PQserverVersion(conn);

PQserverVersion 函数返回一个表示 PostgreSQL 数据库版本的整数。它以连接对象作为参数。

PQfinish(conn);

PQfinish 函数关闭到服务器的连接并释放 PGconn 对象使用的内存。

$ ./server_version 
Server version: 90309

运行该程序,我们得到数据库服务器的版本。

使用密码进行身份验证

接下来,我们将使用密码向数据库服务器进行身份验证。在本教程的所有其他示例中,我们都假设使用 peertrust 身份验证模式。我们将 pg_hba.conf 文件中本地连接的身份验证类型更改为 md5

$ sudo service postgresql restart

要应用这些更改,必须重新启动数据库服务器。

password_authentication.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar password=pswd37 dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }
    
    char *user = PQuser(conn);
    char *db_name = PQdb(conn);
    char *pswd = PQpass(conn);
    
    printf("User: %s\n", user);
    printf("Database name: %s\n", db_name);
    printf("Password: %s\n", pswd);
    
    PQfinish(conn);

    return 0;
}

在该示例中,我们使用密码连接到数据库。我们打印当前数据库连接的用户名、数据库名和密码。

PGconn *conn = PQconnectdb("user=janbodnar password=pswd37 dbname=testdb");

在连接字符串中,我们添加了 password 选项。

char *user = PQuser(conn);

PQuser 函数返回连接的用户名。

char *db_name = PQdb(conn);

PQdb 函数返回连接的数据库名。

char *pswd = PQpass(conn);

PQpass 函数返回连接的密码。

$ ./password_authentication 
User: janbodnar
Database name: testdb
Password: pswd37

该程序打印出数据库用户、数据库名和所使用的密码。

创建数据库表

在本节中,我们创建一个数据库表并用数据填充它。

create_table.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn, PGresult *res) {
    
    fprintf(stderr, "%s\n", PQerrorMessage(conn));    

    PQclear(res);
    PQfinish(conn);    
    
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
            
        PQfinish(conn);
        exit(1);
    }

    PGresult *res = PQexec(conn, "DROP TABLE IF EXISTS Cars");
    
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);
    }
    
    PQclear(res);
    
    res = PQexec(conn, "CREATE TABLE Cars(Id INTEGER PRIMARY KEY," \
        "Name VARCHAR(20), Price INT)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res); 
    }
    
    PQclear(res);
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(1,'Audi',52642)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) 
        do_exit(conn, res);     
    
    PQclear(res);    
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(2,'Mercedes',57127)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);    
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(3,'Skoda',9000)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);  
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(4,'Volvo',29000)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);      
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(5,'Bentley',350000)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);  
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(6,'Citroen',21000)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);  
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(7,'Hummer',41400)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);  
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(8,'Volkswagen',21600)");
        
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        do_exit(conn, res);   
    }
    
    PQclear(res);  
    PQfinish(conn);

    return 0;
}

创建的表名为 Cars,它有三列:Id、汽车名称及其价格。

PGresult *res = PQexec(conn, "DROP TABLE IF EXISTS Cars");

PQexec 函数向服务器提交一个 SQL 命令并等待结果。PGresult 封装了查询的结果。我们的 SQL 命令会删除一个已经存在的表(如果存在的话)。

if (PQresultStatus(res) != PGRES_COMMAND_OK) {
    do_exit(conn, res);
}

应该调用 PQresultStatus 函数来检查返回值是否有任何错误。如果命令被正确执行并且不返回数据,则返回 PGRES_COMMAND_OK

PQclear(res);

每个命令结果在不再需要时都应使用 PQclear 函数释放。

$ ./create_table 
$ psql testdb
psql (9.3.9)
Type "help" for help.

testdb=> SELECT * FROM Cars;
 id |    name    | price  
----+------------+--------
  1 | Audi       |  52642
  2 | Mercedes   |  57127
  3 | Skoda      |   9000
  4 | Volvo      |  29000
  5 | Bentley    | 350000
  6 | Citroen    |  21000
  7 | Hummer     |  41400
  8 | Volkswagen |  21600
(8 rows)

我们执行该程序,并使用 psql 工具验证创建的表。

简单查询

在本节中,我们执行一个简单的查询命令。

query_version.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    PGresult *res = PQexec(conn, "SELECT VERSION()");    
    
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {

        printf("No data retrieved\n");        
        PQclear(res);
        do_exit(conn);
    }    

    printf("%s\n", PQgetvalue(res, 0, 0));

    PQclear(res);
    PQfinish(conn);

    return 0;
}

该示例获取数据库服务器的版本。

PGresult *res = PQexec(conn, "SELECT VERSION()");

SELECT VERSION SQL 语句检索数据库的版本。

if (PQresultStatus(res) != PGRES_TUPLES_OK) {

    printf("No data retrieved\n");        
    PQclear(res);
    do_exit(conn);
}    

对于返回数据的查询,PQresultStatus 函数会返回 PGRES_TUPLES_OK

printf("%s\n", PQgetvalue(res, 0, 0));

PQgetvalue 函数返回 PGresult 中某一行的单个字段值。

$ ./query_version 
PostgreSQL 9.3.9 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4, 64-bit

程序打印出此输出。

检索多行数据

下面的示例执行一个返回多行数据的查询。

multiple_rows.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    PGresult *res = PQexec(conn, "SELECT * FROM Cars LIMIT 5");    
    
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {

        printf("No data retrieved\n");        
        PQclear(res);
        do_exit(conn);
    }    
    
    int rows = PQntuples(res);
    
    for(int i=0; i<rows; i++) {
        
        printf("%s %s %s\n", PQgetvalue(res, i, 0), 
            PQgetvalue(res, i, 1), PQgetvalue(res, i, 2));
    }    

    PQclear(res);
    PQfinish(conn);

    return 0;
}

该程序打印 Cars 表前五行的数据。

PGresult *res = PQexec(conn, "SELECT * FROM Cars LIMIT 5");

此 SQL 查询返回五行数据。

int rows = PQntuples(res);

PQntuples 返回查询结果中的行数。

for(int i=0; i<rows; i++) {
    
    printf("%s %s %s\n", PQgetvalue(res, i, 0), 
        PQgetvalue(res, i, 1), PQgetvalue(res, i, 2));
}    

在 for 循环中,我们使用 PQgetvalue 函数获取一行的所有三个字段。

$ ./multiple_rows 
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000

这是 multiple_rows 程序的输出。

预处理语句

预处理语句可以防止 SQL 注入并提高性能。使用预处理语句时,我们使用占位符而不是直接将值写入语句中。

prepared_statement.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main(int argc, char *argv[]) {
    
    const int LEN = 10;
    const char *paramValues[1];
    
    if (argc != 2) {
    
        fprintf(stderr, "Usage: prepared_statement rowId\n");
        exit(1);
    }
    
    int rowId;
    int ret = sscanf(argv[1], "%d", &rowId);
    
    if (ret != 1) {
        fprintf(stderr, "The argument must be an integer\n");
        exit(1);
    }
    
    if (rowId < 0) {
        fprintf(stderr, "Error passing a negative rowId\n");
        exit(1);        
    }
   
    char str[LEN];
    snprintf(str, LEN, "%d", rowId);  
    paramValues[0] = str;  
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }
    
    char *stm = "SELECT * FROM Cars WHERE Id=$1";
    PGresult *res = PQexecParams(conn, stm, 1, NULL, paramValues, 
        NULL, NULL, 0);    
    
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {

        printf("No data retrieved\n");        
        PQclear(res);
        do_exit(conn);
    }    

    printf("%s %s %s\n", PQgetvalue(res, 0, 0), 
        PQgetvalue(res, 0, 1), PQgetvalue(res, 0, 2));    

    PQclear(res);
    PQfinish(conn);

    return 0;
}

该程序接受一个行 Id 作为其参数。它获取指定行的数据并打印出来。由于程序从用户那里获取一个值,而这个值是不可信的,因此我们必须对输入数据进行清理。预处理语句是使用 PQexecParams 函数创建的。

int rowId;
int ret = sscanf(argv[1], "%d", &rowId);

命令行参数存储在 rowId 变量中。我们期望一个整数值。

char str[LEN];
snprintf(str, LEN, "%d", rowId);  
paramValues[0] = str;  

该值被转换为字符串并传递给一个字符数组。paramValuesPQexecParams 函数的一个参数。

char *stm = "SELECT * FROM Cars WHERE Id=$1";

这是我们的 SQL 语句,它返回 Cars 表的一行。$1 是一个占位符,稍后会用实际值填充。

PGresult *res = PQexecParams(conn, stm, 1, NULL, paramValues, 
    NULL, NULL, 0);   

PQexecParams 函数创建并执行一个预处理语句。第二个参数是 SQL 语句。第三个参数是传递的参数数量。向第四个参数传递 NULL 意味着服务器应该自己判断参数类型。第五个参数是一个指向包含参数的字符串数组的指针。接下来的两个参数只与二进制参数相关。向最后一个参数传递 0 我们将获得文本格式的结果,1 则为二进制格式。

printf("%s %s %s\n", PQgetvalue(res, 0, 0), 
    PQgetvalue(res, 0, 1), PQgetvalue(res, 0, 2));

我们打印指定行的三个字段。

$ ./prepared_statement 4
4 Volvo 29000

这是示例的输出。

元数据

元数据是关于数据库中数据的信息。以下内容属于元数据:关于我们存储数据的表和列的信息、受 SQL 语句影响的行数,或者结果集中返回的行数和列数。

列标题

在第一个示例中,我们打印列标题。

column_headers.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }
    
    PGresult *res = PQexec(conn, "SELECT * FROM Cars WHERE Id=0");    
    
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {

        printf("No data retrieved\n");        
        PQclear(res);
        do_exit(conn);
    }       
    
    int ncols = PQnfields(res);
    
    printf("There are %d columns\n", ncols);
    
    printf("The column names are:\n");
    
    for (int i=0; i<ncols; i++) {
        
        char *name = PQfname(res, i);
        printf("%s\n", name);
    }

    PQclear(res);
    PQfinish(conn);

    return 0;
}

该示例将可用列的数量及其名称打印到控制台。

PGresult *res = PQexec(conn, "SELECT * FROM Cars WHERE Id=0");

在 SQL 语句中,我们选择了一行的所有列。

int ncols = PQnfields(res);

PQnfields 函数返回查询结果行中的列数。

char *name = PQfname(res, i);

PQfname 函数返回与给定列号相关联的列名。

$ ./column_headers 
There are 3 columns
The column names are:
id
name
price

这是示例的输出。

列出表

PostgreSQL 的信息模式 (information schema) 由一组视图组成,其中包含有关当前数据库中定义的对象的信息。tables 视图包含当前数据库中定义的所有表和视图。

list_tables.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }
    
    PGresult *res = PQexec(conn, "SELECT table_name FROM information_schema.tables "
                    "WHERE table_schema = 'public'");    
    
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {

        printf("No data retrieved\n");        
        PQclear(res);
        do_exit(conn);
    }       
    
    int rows = PQntuples(res);
    
    for(int i=0; i<rows; i++) {
        
        printf("%s\n", PQgetvalue(res, i, 0));
    }        
    
    PQclear(res);
    PQfinish(conn);

    return 0;
}

该示例打印 testdb 数据库中的所有表。

PGresult *res = PQexec(conn, "SELECT table_name FROM information_schema.tables "
                "WHERE table_schema = 'public'"); 

此 SQL 语句从当前数据库中选择所有表。

int rows = PQntuples(res);

for(int i=0; i<rows; i++) {
    
    printf("%s\n", PQgetvalue(res, i, 0));
}    

这些表被打印到控制台。

$ ./list_tables 
cars
authors
books

list_tables 程序打印出 testdb 数据库中的可用表。

事务

事务是针对一个或多个数据库中的数据执行的数据库操作的原子单元。事务中的 SQL 语句要么全部提交到数据库,要么全部回滚。为了数据安全和完整性,SQL 语句被放入事务中。

PostgreSQL 在自动提交 (autocommit) 模式下运行。每个 SQL 语句都在一个事务中执行:每个单独的语句都有一个隐式的 BEGIN 和(如果成功)COMMIT 包裹着它。

显式事务以 BEGIN 命令开始,并以 COMMIT 命令结束。

transaction.c
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {
    
    PQfinish(conn);
    exit(1);
}

int main() {
    
    PGconn *conn = PQconnectdb("user=janbodnar dbname=testdb");

    if (PQstatus(conn) == CONNECTION_BAD) {
        
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    PGresult *res = PQexec(conn, "BEGIN");    
    
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {

        printf("BEGIN command failed\n");        
        PQclear(res);
        do_exit(conn);
    }    
    
    PQclear(res);   
    
    res = PQexec(conn, "UPDATE Cars SET Price=23700 WHERE Id=8");    
    
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {

        printf("UPDATE command failed\n");        
        PQclear(res);
        do_exit(conn);
    }    
    
    res = PQexec(conn, "INSERT INTO Cars VALUES(9,'Mazda',27770)");    
    
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {

        printf("INSERT command failed\n");        
        PQclear(res);
        do_exit(conn);
    }       
    
    res = PQexec(conn, "COMMIT"); 
    
    if (PQresultStatus(res) != PGRES_COMMAND_OK) {

        printf("COMMIT command failed\n");        
        PQclear(res);
        do_exit(conn);
    }       
    
    PQclear(res);      
    PQfinish(conn);

    return 0;
}

在该示例中,我们更新一辆汽车的价格并插入一辆新车。这两个操作包含在一个事务中。这意味着要么两个操作都执行,要么都不执行。

PGresult *res = PQexec(conn, "BEGIN");

事务以 BEGIN 命令开始。

res = PQexec(conn, "UPDATE Cars SET Price=23700 WHERE Id=8");

我们更新 Id 为 8 的汽车的价格。

res = PQexec(conn, "INSERT INTO Cars VALUES(9,'Mazda',27770)");

一辆新车被插入到 Cars 表中。

res = PQexec(conn, "COMMIT");

事务以 COMMIT 命令提交。

这是 PostgreSQL C API 教程。您可能还对 ZetCode 上的 PostgreSQL PHP 教程SQLite C 教程MySQL C 教程感兴趣。