KaiwuDBkaiwudb logo

KaiwuDB 技术博客专区

libpq SDK 发送 SQL 和解析结果

2023-05-29

在先前的文章中,我们介绍了 libpq 与 PostgreSQL 建立连接的流程与逻辑。当用户拥有了 PGconn 连接对象后,即可通过 PGconn 与 PostgreSQL 进行交互,发送业务请求。



本次技术贴将详细讲解:当用户建立连接后,如何发送 SQL 语句、获取结果对象、查看错误信息等。




一、PGconn 对象





当用户通过 PQconnectdb, PQconnectdbParams, PQsetdbLogin 尝试与 PostgreSQL 服务器建立连接后,无论成功与否,libpq 会返回一个 PGconn 对象给用户,该对象封装了连接的信息,比如:
  • dbName — 数据库名称
  • pguser — 用户名
  • status — 连接状态
  • errorMessage — 错误信息

用户可以通过 PQstatus (<PGconn对象>) 获取到连接状态,如果状态为 CONNECTION_OK ,即说明已经成功连接数据库,并且状态健康;若连接失败,可以通过 PQerrorMessage (<PGconn对象>) 获取到错误的具体信息。

在之后发送 SQL Command 等操作中,都需要将 PGconn 对象作为入参才可以进行。


二、命令执行函数





在成功连接数据库并且获取到 PGconn 对象后,用户即可使用 PQexec 上传命令并且等待结果:
PGresult *PQexec(PGconn *conn, const char *command)

通过 PQexec 返回来的 PGresult 对象和 PGconn 是类似的逻辑:PGresult 里面封装了单个 SQL 命令的查询结果,比如:
  • Tuples — 元组
  • resultStatus — 结果状态
  • errMsg — 错误信息

用户可以通过 PQresultStatus (<PGresult 对象>) 获取到结果状态,如果状态为 PGRES_COMMAND_OK,说明已经成功执行了命令,但是没有返回任何的值;如果状态为 PGRES_TUPLES_OK,说明成功执行命令,并且返回值已经存在了 tuples 中。

if (PQresultStatus(*pgres) != PGRES_COMMAND_OK) {
    //error handling
  }


三、SQL 注入 PQExecParam






当数据库服务器被错误地引导,将查询的动态参数视为查询文本的一部分时,就会发生 SQL 注入。比如查询文本内容本身就是“DROP TABLE STUFENTS”,可能会产生 SQL 注入。

为避免这种情况,PostgreSQL 通过协议将动态参数作为单独的实体发送,PQExecParam 就是其中一种方法:


PGresult *PQexecParams(PGconn *conn,
                       const char *command,
                       int nParams,                       
                       const Oid *paramTypes,                       
                       const char *const *paramValues,                       
                       const int *paramLengths,                       
                       const int *paramFormats,                       
                       int resultFormat);

PQExecParam 与 PQExec 很像,但提供了额外的功能:参数值可以与命令字符串本身分开指定,查询的结果也可以被指定为文本或者二进制。
  • conn:PGconn 对象
  • command: SQL 字符串命令
  • nParams: 提供的参数数量
  • paramTypes: 参数符号的数据类型
  • paramValues: 指定参数的实际值
  • paramLengths: 指定二进制格式参数的实际数据长度
  • paramFormats: 指定参数是文本还是二进制
  • resultFormat: 获取文本格式还是二进制格式结果

除了避免容易出错的引用和转义之外,PQExecParam 只允许在字符串中有最多一个 SQL 命令。


四、错误处理






当出现错误的时候,除了上文提到的 PQerrorMessage,也可以通过 PQresultErrorField 获取到 PGresult 的相应错误信息:
char *PQresultErrorField(const PGresult *res, int fieldcode)

fieldcode 是 libpq 定义的错误消息字段的标识符:
#define PG_DIAG_SEVERITY    'S'
#define PG_DIAG_SQLSTATE    'C'
#define PG_DIAG_MESSAGE_PRIMARY 'M'
#define PG_DIAG_MESSAGE_DETAIL  'D'
#define PG_DIAG_MESSAGE_HINT  'H'
#define PG_DIAG_STATEMENT_POSITION 'P'
#define PG_DIAG_INTERNAL_POSITION 'p'
#define PG_DIAG_INTERNAL_QUERY  'q'
#define PG_DIAG_CONTEXT      'W'
#define PG_DIAG_SCHEMA_NAME    's'
#define PG_DIAG_TABLE_NAME    't'
#define PG_DIAG_COLUMN_NAME    'c'
#define PG_DIAG_DATATYPE_NAME  'd'
#define PG_DIAG_CONSTRAINT_NAME 'n'
#define PG_DIAG_SOURCE_FILE    'F'
#define PG_DIAG_SOURCE_LINE    'L'
#define PG_DIAG_SOURCE_FUNCTION 'R'

比如用户可以通过 PQresultErrorField (pqres, PG_DIAG_SQLSTATE) 获取到返回的 PostgreSQL error code。


五、获取结果






如果获取到的结果对象状态良好,用户即可使用 PQntuples 获取列数,PQnfields 获取行数, PQfname 获取列名,PQgetvalue 获取某一行某一列的结果。

void printPGresult(PGresult *res) {
  std::cout << PQntuples(res) << "tuples, " << PQnfields(res) << " fields" 
             << std::endl;  
  // print column name  
  for (int i = 0; i < PQnfields(res); i++) {
      std::cout << PQfname(res, i) << "\t";    
  }    
  std::cout << std::endl;    
  // print column values    
  for (int i = 0; i < PQntuples(res); i++) {
      for (int j = 0; j < PQnfields(res); j++) {   
         std::cout << PQgetvalue(res, i, j) << "\t";        
      }        
      std::cout << std::endl;
    }
}


在此次 PGresult 操作结束之后,无论是否成功获取到结果,都需要使用 PQclear (<PGresult对象>) 清理结果,以免出现内存泄露的问题。


六、总结







通过 libpq 与 PostgreSQL 建立连接之后,可以通过 libpq 所在的 client 端发送 SQL 接收结果,或获取错误信息。为了避免 SQL 注入的问题,用户可以使用相应的协议或者是函数避免错误的产生。



免费体验 KaiwuDB 全新功能

立即体验

关于我们
联系我们

KaiwuDB B站

KaiwuDB
B站

KaiwuDB 微信公众号

KaiwuDB
微信公众号

© 上海沄熹科技有限公司 Shanghai Yunxi Technology Co., Ltd.    沪ICP备2023002175号-1
400-624-5688-7
1V1 方案咨询
marketing@kaiwudb.org.cn