|
| 1 | + |
| 2 | +:sectnums: |
| 3 | +:sectnumlevels: 5 |
| 4 | + |
| 5 | += pg_curl |
| 6 | + |
| 7 | +== 概述 |
| 8 | + |
| 9 | +pg_curl 是一个基于 libcurl 的 PostgreSQL 扩展,它将 cURL 的强大数据传输能力直接引入数据库内部,允许用户通过 SQL 函数调用完成各种网络协议的数据传输操作,无需借助外部程序或中间件。 |
| 10 | + |
| 11 | +与只支持 HTTP 的轻量级扩展不同,pg_curl 全面封装了 libcurl 的 easy interface API,支持 DICT、FILE、FTP、FTPS、GOPHER、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET、TFTP、WS 和 WSS 等二十余种协议,提供了极为丰富的网络交互能力。 |
| 12 | + |
| 13 | +典型应用场景包括:在触发器或存储过程中向外部系统发送 HTTP 通知;直接从数据库发送邮件;通过 FTP/SFTP 上传或下载文件;调用第三方 REST API 并将结果写回数据库表;以及在数据处理流程中与 MQTT 消息代理、LDAP 目录等各类后端服务进行集成。 |
| 14 | + |
| 15 | +pg_curl 采用 MIT 许可证,项目地址:https://github.com/RekGRpth/pg_curl 。 |
| 16 | + |
| 17 | +== 功能特点 |
| 18 | + |
| 19 | +* *多协议支持*:基于 libcurl,支持 HTTP/HTTPS、FTP/FTPS、SMTP/SMTPS、IMAP、POP3、SCP、SFTP、MQTT、LDAP 等二十余种协议,覆盖绝大多数网络集成场景。 |
| 20 | +* *完整的 HTTP 方法支持*:支持 GET、POST(URL 编码、JSON、multipart/form-data)、PUT、DELETE、PATCH 等标准 HTTP 方法,并可通过 `curl_easy_setopt_customrequest` 指定任意自定义方法。 |
| 21 | +* *灵活的请求构造*:提供细粒度的 `curl_easy_setopt_*` 系列函数,可精确控制请求头、认证信息、超时、代理、TLS 证书、Cookie 等各项参数。 |
| 22 | +* *多种认证方式*:支持 Basic Auth(用户名/密码)、Bearer Token、NTLM、Digest、OAuth 等多种认证机制。 |
| 23 | +* *文件传输*:支持通过 FTP/FTPS/SFTP/SCP 上传(`curl_easy_setopt_readdata`)和下载文件,上传数据以 `bytea` 类型直接传入。 |
| 24 | +* *邮件发送*:通过 SMTP/SMTPS 直接在数据库内发送邮件,支持设置发件人、收件人、邮件头及 MIME 正文。 |
| 25 | +* *响应解析*:提供 `curl_easy_getinfo_data_in()`、`curl_easy_getinfo_header_in()` 等函数获取响应体和响应头,并可通过正则表达式将头信息解析为键值表。 |
| 26 | +* *错误处理*:`curl_easy_getinfo_errcode()`、`curl_easy_getinfo_errdesc()` 及 `curl_easy_getinfo_errbuf()` 提供完整的错误码和错误描述,便于调试。 |
| 27 | +* *超时与中断*:通过向 libcurl 注册进度回调并周期性检查 PostgreSQL 的取消标志(`QueryCancelPending`),支持被 `statement_timeout`、`pg_cancel_backend()` 等方式中断,防止长时间请求阻塞连接。 |
| 28 | +* *URL 编码工具*:内置 `curl_easy_escape()` 和 `curl_easy_unescape()` 函数,方便在 SQL 中进行 URL 编码和解码。 |
| 29 | + |
| 30 | +== 安装 |
| 31 | + |
| 32 | +[TIP] |
| 33 | +以下示例环境为 Ubuntu 24.04(x86_64),已安装 IvorySQL 5 及以上版本,安装路径为 `/usr/local/ivorysql/ivorysql-5`。 |
| 34 | + |
| 35 | +=== 安装依赖 |
| 36 | + |
| 37 | +pg_curl 依赖系统的 libcurl 开发库,安装前需确认已安装该依赖: |
| 38 | + |
| 39 | +[source,shell] |
| 40 | +---- |
| 41 | +sudo apt install libcurl4-openssl-dev |
| 42 | +---- |
| 43 | + |
| 44 | +=== 源码编译安装 |
| 45 | + |
| 46 | +从 https://github.com/RekGRpth/pg_curl 获取源码,然后执行编译安装: |
| 47 | + |
| 48 | +[source,shell] |
| 49 | +---- |
| 50 | +git clone https://github.com/RekGRpth/pg_curl.git |
| 51 | +cd pg_curl |
| 52 | +# 确保 pg_config 可访问,或通过 PG_CONFIG 显式指定 |
| 53 | +PG_CONFIG=/usr/local/ivorysql/ivorysql-5/bin/pg_config make |
| 54 | +PG_CONFIG=/usr/local/ivorysql/ivorysql-5/bin/pg_config make install |
| 55 | +---- |
| 56 | + |
| 57 | +安装成功后,`pg_curl.so` 及对应的 SQL 脚本会被放置到 IvorySQL 的扩展目录中。 |
| 58 | + |
| 59 | +== 创建 Extension 并验证版本 |
| 60 | + |
| 61 | +使用 psql 连接到数据库(PG 模式端口 5432 或 Oracle 模式端口 1521 均可),执行如下命令: |
| 62 | + |
| 63 | +[source,sql] |
| 64 | +---- |
| 65 | +CREATE EXTENSION pg_curl; |
| 66 | +
|
| 67 | +SELECT name, default_version, installed_version, comment |
| 68 | + FROM pg_available_extensions |
| 69 | + WHERE name = 'pg_curl'; |
| 70 | +---- |
| 71 | + |
| 72 | +预期输出类似: |
| 73 | + |
| 74 | +[source,text] |
| 75 | +---- |
| 76 | + name | default_version | installed_version | comment |
| 77 | +---------+-----------------+-------------------+---------------------------------------------------------------------------------------------------------------------------------------- |
| 78 | + pg_curl | 2.4 | 2.4 | PostgreSQL cURL allows most curl actions, including data transfer with URL syntax via HTTP, HTTPS, FTP, FTPS, GOPHER, TFTP, SCP, ... |
| 79 | +(1 row) |
| 80 | +---- |
| 81 | + |
| 82 | +== 使用 |
| 83 | + |
| 84 | +pg_curl 采用"先配置、再执行、后取结果"的调用模式,核心流程如下: |
| 85 | + |
| 86 | +. 调用 `curl_easy_reset()` 初始化(或重置)当前 cURL 会话。 |
| 87 | +. 调用各 `curl_easy_setopt_*`、`curl_header_append`、`curl_postfield_append` 等函数配置请求参数。 |
| 88 | +. 调用 `curl_easy_perform()` 执行请求。 |
| 89 | +. 调用 `curl_easy_getinfo_*` 系列函数获取响应结果或错误信息。 |
| 90 | + |
| 91 | +=== HTTP GET |
| 92 | + |
| 93 | +[source,sql] |
| 94 | +---- |
| 95 | +BEGIN; |
| 96 | +SELECT curl_easy_reset(); |
| 97 | +SELECT curl_easy_setopt_url('https://httpbin.org/get?'); |
| 98 | +SELECT curl_url_append('key1', 'value1'); |
| 99 | +SELECT curl_url_append('key2', 'hello world'); -- 自动 URL 编码 |
| 100 | +SELECT curl_easy_perform(); |
| 101 | +SELECT convert_from(curl_easy_getinfo_data_in(), 'utf-8'); |
| 102 | +END; |
| 103 | +---- |
| 104 | + |
| 105 | +=== HTTP POST(JSON) |
| 106 | + |
| 107 | +POST 表单(`curl_postfield_append`)和 multipart(`curl_mime_data`)的用法与此类似,替换数据设置函数即可。 |
| 108 | + |
| 109 | +[source,sql] |
| 110 | +---- |
| 111 | +BEGIN; |
| 112 | +SELECT curl_easy_reset(); |
| 113 | +SELECT curl_easy_setopt_postfields(convert_to('{"name":"IvorySQL"}', 'utf-8')); |
| 114 | +SELECT curl_easy_setopt_url('https://httpbin.org/post'); |
| 115 | +SELECT curl_header_append('Content-Type', 'application/json; charset=utf-8'); |
| 116 | +SELECT curl_easy_perform(); |
| 117 | +SELECT convert_from(curl_easy_getinfo_data_in(), 'utf-8'); |
| 118 | +END; |
| 119 | +---- |
| 120 | + |
| 121 | +=== 认证与请求头 |
| 122 | + |
| 123 | +Basic Auth 使用 `curl_easy_setopt_username` / `curl_easy_setopt_password`;Bearer Token 及其他自定义头均通过 `curl_header_append` 添加: |
| 124 | + |
| 125 | +[source,sql] |
| 126 | +---- |
| 127 | +BEGIN; |
| 128 | +SELECT curl_easy_reset(); |
| 129 | +SELECT curl_easy_setopt_url('https://httpbin.org/bearer'); |
| 130 | +SELECT curl_header_append('Authorization', 'Bearer <your_token>'); |
| 131 | +SELECT curl_easy_perform(); |
| 132 | +SELECT convert_from(curl_easy_getinfo_data_in(), 'utf-8')::jsonb; |
| 133 | +END; |
| 134 | +---- |
| 135 | + |
| 136 | +=== 错误处理与超时 |
| 137 | + |
| 138 | +`curl_easy_perform()` 成功返回后,可通过以下函数检查执行状态(`errcode` 为 0 表示成功): |
| 139 | + |
| 140 | +[source,sql] |
| 141 | +---- |
| 142 | +BEGIN; |
| 143 | +SELECT curl_easy_reset(); |
| 144 | +SELECT curl_easy_setopt_url('https://httpbin.org/get'); |
| 145 | +SELECT curl_easy_perform(); |
| 146 | +SELECT |
| 147 | + curl_easy_getinfo_errcode() AS errcode, |
| 148 | + curl_easy_getinfo_errdesc() AS errdesc; |
| 149 | +END; |
| 150 | +---- |
| 151 | + |
| 152 | +pg_curl 通过 libcurl 进度回调检查 PostgreSQL 取消标志,`statement_timeout` 触发时请求会被立即中止并回滚当前事务: |
| 153 | + |
| 154 | +[source,sql] |
| 155 | +---- |
| 156 | +BEGIN; |
| 157 | +SET LOCAL statement_timeout = '3s'; |
| 158 | +SELECT curl_easy_reset(); |
| 159 | +SELECT curl_easy_setopt_url('https://httpbin.org/delay/10'); |
| 160 | +SELECT curl_easy_perform(); -- 超时后抛出 query_canceled,事务回滚 |
| 161 | +END; |
| 162 | +---- |
| 163 | + |
| 164 | +== IvorySQL 适配说明 |
| 165 | + |
| 166 | +=== 双端口兼容 |
| 167 | + |
| 168 | +IvorySQL 采用双端口架构,同时支持 PostgreSQL 模式(默认端口 *5432*)和 Oracle 兼容模式(默认端口 *1521*): |
| 169 | + |
| 170 | +* 连接 *5432* 端口(PG 模式):接受标准 PostgreSQL SQL 语法。 |
| 171 | +* 连接 *1521* 端口(Oracle 模式):接受 Oracle 兼容 SQL 语法。 |
| 172 | + |
| 173 | +pg_curl 在两种模式下均可正常使用,`CREATE EXTENSION pg_curl` 在 1521 和 5432 端口均能成功执行。 |
| 174 | + |
| 175 | +=== Oracle 模式注意事项 |
| 176 | + |
| 177 | +在 Oracle 模式(1521 端口)下使用 pg_curl 时,以下几点需要注意: |
| 178 | + |
| 179 | +* pg_curl 的所有函数均通过普通 `SELECT` 语句调用(如 `SELECT curl_easy_reset()`),在 Oracle 模式下同样有效;也可以使用 Oracle 惯用的 `SELECT curl_easy_reset() FROM DUAL` 写法。 |
| 180 | +* `ivorysql.enable_emptystring_to_NULL` 参数会影响空字符串的处理行为。回归测试中已设置 `SET ivorysql.enable_emptystring_to_NULL = 'off'` 以确保空值参数(如空表单字段)按预期传递,生产环境中需根据实际需求配置该参数。 |
| 181 | + |
| 182 | +=== 在 PL/iSQL 中使用 |
| 183 | + |
| 184 | +pg_curl 可在 IvorySQL 的 PL/iSQL(Oracle 兼容存储过程语言)中调用,实现数据变更时自动推送外部通知。PL/iSQL 使用 Oracle 风格的 `IS` 关键字声明存储过程,调用返回值的函数时通过赋值给局部变量来丢弃结果: |
| 185 | + |
| 186 | +[source,sql] |
| 187 | +---- |
| 188 | +-- 在 PL/iSQL 存储过程中调用 pg_curl(Oracle 模式,IS 语法) |
| 189 | +CREATE OR REPLACE PROCEDURE notify_external(p_payload IN VARCHAR2) IS |
| 190 | + v_ok BOOLEAN; |
| 191 | +BEGIN |
| 192 | + v_ok := curl_easy_reset(); |
| 193 | + v_ok := curl_easy_setopt_postfields(convert_to(p_payload, 'utf-8')); |
| 194 | + v_ok := curl_easy_setopt_url('https://hooks.example.com/notify'); |
| 195 | + v_ok := curl_header_append('Content-Type', 'application/json'); |
| 196 | + v_ok := curl_easy_perform(); |
| 197 | +END; |
| 198 | +---- |
0 commit comments