# PostgreSQL C 教程

> 原文： [http://zetcode.com/db/postgresqlc/](http://zetcode.com/db/postgresqlc/)

这是 PostgreSQL 数据库的 C 编程教程。 它涵盖了使用 C API 进行 PostgreSQL 编程的基础。

## 关于 PostgreSQL 数据库

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

PostgreSQL 具有复杂的功能，例如多版本并发控制（MVCC），时间点恢复，表空间，异步复制，嵌套事务（保存点），联机/热备份，复杂的查询计划程序/优化程序以及用于容错的预写日志记录 。 它支持国际字符集，多字节字符编码，Unicode，并且对于排序，区分大小写和格式具有区域设置意识。

## 安装

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

```c
$ sudo apt-get install postgresql

```

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

```c
$ 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 数据库的所有系统启动链接。

```c
$ sudo apt-get install libpq-dev

```

为了能够编译 C 示例，我们需要安装 PostgreSQL C 开发库。 上面的行显示了我们如何在基于 Debian 的 Linux 上做到这一点。

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

postgres=# \password postgres

```

我们为`postgres`用户设置了密码。

## 启动和停止 PostgreSQL

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

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

```

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

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

```

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

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

```

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

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

```

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

## 创建用户和数据库

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

```c
$ sudo -u postgres createuser janbodnar

```

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

```c
$ 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 通常在本地连接上使用信任或对等认证策略。 在信任认证策略的情况下，PostgreSQL 假定可以连接到服务器的任何人都可以使用他们指定的任何数据库用户名（甚至超级用户名）访问数据库。 与数据库建立连接时，不需要密码。 （在数据库和用户列中所做的限制仍然适用。）信任认证对于单用户工作站上的本地连接是适当的，并且非常方便。 它通常不适用于多用户计算机。 如果使用对等认证策略，则数据库用户名必须与操作系统用户名匹配。

```c
$ sudo -u postgres createdb testdb --owner janbodnar

```

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

## C99

本教程使用 C99。 对于 GNU C 编译器，我们需要添加`-std=c99`选项。 对于 Windows 用户，强烈建议使用 Pelles C IDE。 （MSVC 不支持 C99。）

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

```

在 C99 中，我们可以将声明与代码混合使用。 在较早的 C 程序中，我们需要将这一行分成两行。

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

```

我们还可以将初始化用于循环声明。

## libpq 库

`libpq`库是 PostgreSQL 的 C 接口。 它是一组库函数，允许客户端程序与 PostgreSQL 交互。 它也是其他 PostgreSQL 应用接口的基础引擎，包括为 C++ ，Perl，PHP，Ruby，Python 和 Tcl 编写的接口。

`lib_version.c`

```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`库的版本。

```c
#include <libpq-fe.h>

```

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

```c
int lib_ver = PQlibVersion();

```

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

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

```

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

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

```

使用上面的命令，我们编译程序。

```c
$ ./lib_version 
Version of libpq: 90309

```

该库的版本是 9.3.9。

## 服务器版本

在下面的示例中，我们找到了 PostgreSQL 数据库的版本。

`server_version.c`

```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()`函数，打印版本，关闭连接，然后清除。

```c
...
# TYPE  DATABASE        USER            ADDRESS                 METHOD

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

```

在`pg_hba.conf`中，我们具有`peer`默认认证方法。 在这种方法中，数据库用户名必须与操作系统用户名匹配。 无需密码即可建立连接。

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

```

使用`PQconnectdb()`功能，我们可以连接数据库。 在连接字符串中，我们提供用户名和数据库名。

```c
if (PQstatus(conn) == CONNECTION_BAD) {

    fprintf(stderr, "Connection to database failed: %s",
        PQerrorMessage(conn));
    do_exit(conn);
}

```

`PQstatus()`功能返回连接状态。 如果连接成功，则返回`CONNECTION_OK`； 对于不成功的连接，返回`CONNECTION_BAD`。 `PQerrorMessage()`返回最近生成的错误消息。

```c
int ver = PQserverVersion(conn);

```

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

```c
PQfinish(conn);

```

`PQfinish()`功能关闭与服务器的连接，并释放`PGconn`对象使用的内存。

```c
$ ./server_version 
Server version: 90309

```

运行程序，我们获得数据库服务器版本。

## 密码认证

接下来，我们将使用密码对数据库服务器进行认证。 在本教程的所有其他示例中，我们假设使用`peer`或`trust`认证模式。 我们将`pg_hba.conf`文件中本地连接的认证类型更改为`md5`。

```c
$ sudo service postgresql restart

```

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

`password_authentication.c`

```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;
}

```

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

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

```

在连接字符串中，我们添加了密码选项。

```c
char *user = PQuser(conn);

```

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

```c
char *db_name = PQdb(conn);

```

`PQdb()`函数返回连接的数据库名称。

```c
char *pswd = PQpass(conn);

```

`PQpass()`功能返回连接的密码。

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

```

该程序将打印数据库用户，数据库名称和使用的密码。

## 创建数据库表

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

`create_table.c`

```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，汽车名称及其价格。

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

```

`PQexec()`函数将 SQL 命令提交到服务器并等待结果。 `PGresult`封装了查询结果。 如果表已经存在，我们的 SQL 命令将删除该表。

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

```

应该调用`PQresultStatus()`函数以检查返回值是否有任何错误。 如果命令已正确执行且未返回数据，则返回`PGRES_COMMAND_OK`。

```c
PQclear(res);

```

当不再需要每个命令结果时，应使用`PQclear()`函数将其释放。

```c
$ ./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`

```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;
}

```

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

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

```

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

```c
if (PQresultStatus(res) != PGRES_TUPLES_OK) {

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

```

对于通过`PQresultStatus()`函数返回数据的查询，返回`PGRES_TUPLES_OK`。

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

```

`PQgetvalue()`函数返回`PGresult`的一行的单个字段值。

```c
$ ./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`

```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`表的前五行的数据。

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

```

此 SQL 查询返回五行数据。

```c
int rows = PQntuples(res);

```

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

```c
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()`函数获得一行的所有三个字段。

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

```

这是`multiple_rows`程序的输出。

## 预备语句

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

`prepared_statement.c`

```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()`函数创建一个预备语句。

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

```

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

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

```

该值将转换为字符串，然后传递给字符数组。 `paramValues`是`PQexecParams()`功能的参数。

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

```

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

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

```

`PQexecParams()`函数创建一个预备语句并执行它。 第二个参数是 SQL 语句。 第三个参数是传递的参数数。 将`NULL`传递给第四个参数意味着服务器应确定参数类型。 第五个参数是指向包含参数的字符串数组的指针。 接下来的两个参数仅与二进制参数相关。 将 0 传递给最终参数，我们以文本格式获得结果，以二进制格式获得 1。

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

```

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

```c
$ ./prepared_statement 4
4 Volvo 29000

```

这是示例的输出。

## 元数据

元数据是有关数据库中数据的信息。 以下内容属于元数据：有关存储数据的表和列的信息，受 SQL 语句影响的行数或结果集中返回的行和列数。

### 列标题

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

`column_headers.c`

```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;
}

```

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

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

```

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

```c
int ncols = PQnfields(res);

```

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

```c
char *name = PQfname(res, i);

```

`PQfname()`函数返回与给定列号关联的列名。

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

```

这是示例的输出。

### 表的清单

PostgreSQL 的信息模式由一组视图组成，这些视图包含有关当前数据库中定义的对象的信息。 `tables`视图包含当前数据库中定义的所有表和视图。

`list_tables.c`

```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`数据库中的所有表。

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

```

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

```c
int rows = PQntuples(res);

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

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

```

表将打印到控制台。

```c
$ ./list_tables 
cars
authors
books

```

`list_tables`程序在`testdb`数据库中打印可用表。

## 事务

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中的 SQL 语句可以全部提交给数据库，也可以全部回滚。 为了数据安全和完整性，将 SQL 语句放入事务中。

PostgreSQL 在自动提交模式下运行。 每个 SQL 语句都在一个事务中执行：每个单独的语句周围都包含一个隐式`BEGIN`和（如果成功）`COMMIT`。

显式事务以`BEGIN`命令开始，以`COMMIT`命令结束。

`transaction.c`

```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;
}

```

在示例中，我们更新了汽车的价格并插入了新汽车。 这两个操作包含在单个事务中。 这意味着要么执行两个操作，要么不执行任何操作。

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

```

使用`BEGIN`命令开始事务。

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

```

我们使用 ID 8 更新汽车的价格。

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

```

将新车插入`Cars`表。

```c
res = PQexec(conn, "COMMIT");

```

使用`COMMIT`命令提交事务。

[Tweet](https://twitter.com/share) 

这是 PostgreSQL C API 教程。 您可能也对 ZetCode 上的 [PostgreSQL Python 教程](/db/postgresqlpythontutorial/)， [PostgreSQL PHP 教程](/db/postgresqlphp/)， [SQLite C 教程](/db/sqlitec/)或 [MySQL C 教程](/db/mysqlc/)感兴趣。