# PostgreSQL Deft 自带原生 PostgreSQL 客户端——无需 `libpq`,也无需安装 驱动。`pg/*` 标准库函数直接使用 PostgreSQL 线路 协议(v3),在脚本二进制、ANSI 二进制和完整 TUI 中 行为完全一致。默认使用 SCRAM-SHA-256 认证, 并支持 MD5 和明文回退;TLS 通过 HTTP 客户端所用的 同一 mbedTLS 栈进行带内升级。 与 `keyval/*` 一样,每个查询都返回一个 Future——`[await ...]` 会挂起协程直到回复到达,因此整条流水线 可以在一个连接上运行而不阻塞事件循环。 注意:postgres 的 Zig 代码取自 Bun 3.14。 ## 连接 三种连接形式,完全等价:
# URL (bun/libpq style)
def db [pg/connect "postgres://user:pass@localhost:5432/mydb?sslmode=require"]

# libpq kv-string
def db [pg/connect "host=localhost port=5432 user=deft password=secret dbname=mydb sslmode=disable"]

# Positional + opts map
def db [pg/connect "localhost" 5432 %{ :user "deft" :password "secret" :database "mydb" :sslmode "disable" }]
连接是惰性拨号的——第一条命令才触发 TCP 连接,因此 `pg/connect` 本身从不阻塞。在任何命令之前 调用 `pg/info` 会报告 `:state :disconnected`。 ### 选项 map | 选项 | 含义 | |---|---| | `:host` | 服务器主机名或 IP | | `:port` | 服务器端口(默认 5432) | | `:user` / `:username` | 角色名 | | `:password` | 密码 | | `:database` / `:db` | 数据库名(默认为用户名) | | `:sslmode` | `"disable"` `"prefer"` `"require"` `"verify-ca"` `"verify-full"` | | `:ssl` | 布尔简写——`true` = `"require"`,`false` = `"disable"` | | `:application-name` | 在 `pg_stat_activity` 中向服务器报告 | | `:timeout` | 命令截止时间(毫秒)(默认 60000;`0` 表示禁用) | 任何未设置的值都会应用环境变量回退:`PGHOST`、`PGPORT`、`PGUSER`、 `PGPASSWORD`、`PGDATABASE`、`PGSSLMODE`、`PGAPPNAME`。 ### SSL 模式 | 模式 | 行为 | |---|---| | `disable` | 仅明文 | | `prefer` | 尝试 TLS;如果服务器拒绝或握手失败则回退到明文 | | `require` | 需要 TLS,不验证证书(仅加密,libpq 语义) | | `verify-ca` | TLS + 证书链验证 | | `verify-full` | TLS + 证书链 + 主机名验证 | ## Exec——DDL 和 DML `pg/exec` 运行不返回行的 SQL。不带参数时 使用简单查询协议,因此多语句字符串可用—— 便于模式迁移。带参数时则切换到 扩展协议(`$1..$n` 占位符)。
set r [await [pg/exec $db `
CREATE TABLE IF NOT EXISTS items (
    id    serial PRIMARY KEY,
    name  text,
    price numeric
)
`]]
# %{:tag "CREATE TABLE" :command "CREATE" :rows 0}

set r [await [pg/exec $db "INSERT INTO items (name, price) VALUES ($1, $2)" "widget" 9.5]]
# %{:tag "INSERT 0 1" :command "INSERT" :rows 1}
结果 map 原样携带命令标签(`:tag`)、 命令词(`:command`)和受影响行数(`:rows`)。 ## 查询——SELECT
def rows [await [pg/query $db "SELECT id, name, price FROM items ORDER BY id"]]
for row $rows {
    echo "$row~name (price $row~price)"
}

def one [await [pg/query-one $db "SELECT * FROM items WHERE name = $1" "widget"]]
# first row as a map, or nil

def n [await [pg/scalar $db "SELECT count(*) FROM items"]]
# first column of the first row, or nil
行是以关键字列名为键的 map(`$row~name`)。列 顺序可通过 `pg/columns` 获取(行 map 按哈希排序)。 ### 惰性序列 `pg/query-seq` 返回由游标支持的惰性序列——行字节在 迭代时解码,因此 `for`、`map`、`filter` 和流水线运行时 无需先物化整个结果:
set seq [await [pg/query-seq $db "SELECT id, name FROM items ORDER BY id"]]
set names $seq |> map {|row| $row~name} |> collect
for row in $seq { echo $row~name }
序列在被实现之前只能迭代一次;之后再迭代 会重放已实现的缓存。 ## 参数 参数为可变参数或单个向量,按顺序对应 `$1..$n`:
[await [pg/exec $db "INSERT INTO items VALUES ($1, $2, $3)" "a" 1 @{ "x" "y" }]]
| Deft 值 | 发送为 | |---|---| | `nil` | SQL NULL | | `true` / `false` | `t` / `f` | | number | 十进制文本(整数不带 `.0`) | | string | 文本 | | keyword | 其名称 | | vector | 数组字面量 `{...}` | | 其他任何值 | 调用时出错 | ## 类型映射 行以文本形式到达;解码由列的类型 OID 驱动: | PostgreSQL 类型 | Deft 值 | |---|---| | `int2` `int4` `int8` `oid` `xid` `cid` | number(f64——超出 ±2^53 的值会丢失精度,与 `sqlite` 相同的取舍) | | `float4` `float8` `numeric` | number | | `bool` | bool | | `text` `varchar` `char` `name` `json` `xml` `uuid` `inet` `cidr` `macaddr` `money` `bit` `varbit` | string | | `date` `time` `timestamp` `timestamptz` `timetz` `interval` | string(服务器的 ISO 文本形式) | | `bytea` | 原始字节字符串(Deft 的二进制约定——NUL 安全,与 `crypto/*` 相同) | | 数组(`int[]`、`text[]` 等) | vector(嵌套数组亦然;`NULL` 元素 → nil) | | NULL | nil | | 未知 OID | 原样字符串 | `bytea` 参数以 `\x` 十六进制文本发送:原始字节字符串 会原样传递,因此 `"\x414243"` 会变成字节 `ABC`。 ## 事务
[await [pg/begin $db]]
[await [pg/exec $db "INSERT INTO items (name, price) VALUES ($1, $2)" "kept" 1]]
[await [pg/commit $db]]

# or
[await [pg/begin $db]]
[await [pg/rollback $db]]
事务内部的错误会在服务器端中止事务:后续 语句会失败,直到你发出 `pg/rollback`(或 `pg/commit`)。 ## LISTEN / NOTIFY 注册回调并通过 `pg/exec` 执行 `LISTEN`;通知 作为推送事件在同一连接上到达:
[pg/on-notify $db {|ev|
    echo "channel $ev~channel: $ev~payload"   # %{ :pid :channel :payload }
}]
[await [pg/exec $db "LISTEN items_changed"]]
[await [pg/exec $db "NOTIFY items_changed, 'row 42 updated'"]]
服务器警告(`NOTICE`、`WARNING` 等)在设置了处理器时 会路由到 `pg/on-notice`,携带 `%{ :message :severity :code }`。 ## 内省
[await [pg/tables $db]]                # @{ "items" "users" ... } — user tables only
[await [pg/columns $db "items"]]       # @{ %{ :name "id" :type "integer" :not_null true :default nil } ... }
## 错误 查询失败会以 `%Err` 拒绝 future,其中携带 SQLSTATE 码和服务器消息字段:
set r [await [pg/query $db "SELECT * FROM missing_table"]]
[err? $r]          # true
$r~message         # relation "missing_table" does not exist
$r~code            # 42P01 (SQLSTATE)
$r~severity        # ERROR
$r~detail          # optional extra detail
$r~hint            # optional hint
$r~position        # optional character offset in the query
出错后连接仍然可用(失败的语句是 唯一的牺牲品——事务除外,见上文)。 ## 连接状态与关闭
[pg/info $db]      # %{ :state :host :port :user :database :sslmode :ssl
                   #    :txn :pending :pid :server-version }
[pg/close $db]     # sends Terminate, rejects pending requests → :ok
`pg/close` 是幂等的;使用已关闭的客户端会出错。 ## 尚不支持 - `COPY` 导入/导出——客户端会以明确的错误拒绝 - Unix 套接字连接(仅 TCP) - 二进制格式的参数/结果(始终使用文本格式) - 语句缓存 / 连接池