ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 C/C++ 与 Zig 中嵌入 PRQL 编译器:prqlc-c FFI 库集成实战指南

在 C/C++ 与 Zig 中嵌入 PRQL 编译器:prqlc-c FFI 库集成实战指南 后端【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址https://gitcode.com/gh_mirrors/pr/prql点击查看免费下载导读prqlc-c 是 PRQL 项目提供的 C 语言 FFI外部函数接口绑定库它将用 Rust 编写的prqlc编译器编译为静态库.a与动态库.so使任何支持 C ABI 的语言——例如 Golang、C、C、Zig——都可以直接调用 PRQL 编译器把 PRQL 查询编译为 SQL。本指南以prqlc-c模块的官方 README 为骨架结合仓库内头文件、示例与构建脚本完整讲解库的定位、链接方式、FFI 函数面compile、prql_to_pl、pl_to_rq、rq_to_sql、result_destroy、自定义Options、错误处理、头文件生成流程并给出 C、C、Zig、Golang 四种语言的实战示例。读完本文你将能够在自己项目的构建系统中正确链接 prqlc-c并把「PRQL → SQL」编译能力嵌入到非 Rust 的宿主程序中。一、prqlc-c 是什么把 PRQL 编译器做成 C 可调用库PRQLPipelined Relational Query Language是一种现代的数据转换语言定位为 SQL 的可管道化替代品。其核心编译器prqlc以 Rust crate 形式存在于 prqlc/prqlc 目录中。为了让 Rust 之外的生态Golang、C、C、Zig、PHP、Python 等也能使用编译器能力仓库在 prqlc/bindings 下维护了多语言绑定而 prqlc-c 正是其中最底层的 C 绑定——其它很多绑定如 PHP、Python、Java最终也都是通过它或cdylib形式调用 Rust 核心。从 Cargo.toml 可以看到该 crate 的产物类型[lib] crate-type [staticlib, cdylib]也就是说cargo build会同时产出libprqlc_c.a静态库和libprqlc_c.so动态库Windows 上为.dll。依赖上只引入libc、prqlc与serde_json且prqlc使用default-features false以保持产物精简该 crate 关闭了 bench、doc、doctest 与 test纯粹作为 FFI 分发包存在publish false。静态库形态尤其适合嵌入到不支持运行时加载共享库的部署环境这也是 README 中强调「embedding in languages that support FFI — for example, Golang」的原因。二、链接最小化链接参数与跨平台注意点README 明确指出链接参数以 examples/minimal-c/Makefile 为权威参考。该 Makefile 的关键片段如下PRQL_PROJECT../../../../.. build-prql: cargo build --package prqlc-c --release UNAME_S : $(shell uname -s) LD_FLAGS -L${PRQL_PROJECT}/target/release \ ${PRQL_PROJECT}/target/release/libprqlc_c.a \ -pthread -ldl -lm ifeq ($(UNAME_S),Darwin) LD_FLAGS : $(LD_FLAGS) -framework CoreFoundation endif build: main.c build-prql gcc main.c -o main.out \ -I${PRQL_PROJECT}/prqlc/bindings/prqlc-c \ $(LD_FLAGS)要点拆解先用 Cargo 编译库本身cargo build --package prqlc-c --release产物位于仓库根目录的target/release/。静态链接完整依赖链libprqlc_c.a内部还需要系统库-pthread -ldl -lm。这是因为 Rust 标准库与编译器运行时会依赖这些系统组件线程、动态加载、数学函数。头文件路径编译 C 源码时通过-I${PRQL_PROJECT}/prqlc/bindings/prqlc-c找到prqlc.h。macOS 专属Darwin 上必须额外追加-framework CoreFoundation否则链接会因缺少 CoreFoundation 符号而失败。README 中给出的其他构建系统写法例如 Golang 使用 cgo与 Makefile 完全对应CGO_LDFLAGS-L/path/to/target/release -lprqlc_c -pthread -ldl -lm go build在 macOS 上再追加-framework CoreFoundation。核心思路不变-L指向target/release-lprqlc_c链接静态库随后补齐系统依赖。三、FFI 函数面全景五个公开入口完整的 FFI 表面在 prqlc.h 中以内联文档形式给出C 对应 prqlc.hpp其 Rust 实现位于 src/lib.rs。共五个#[no_mangle] extern C导出函数函数签名作用compilestruct CompileResult compile(const char *prql_query, const struct Options *options)一步完成 PRQL 字符串 → SQL 字符串prql_to_plstruct CompileResult prql_to_pl(const char *prql_query)PRQL 源码 → PL ASTJSONpl_to_rqstruct CompileResult pl_to_rq(const char *pl_json)PL JSON → RQ ASTJSONrq_to_sqlstruct CompileResult rq_to_sql(const char *rq_json, const struct Options *options)RQ JSON → SQL 字符串result_destroyvoid result_destroy(struct CompileResult res)释放所有由前四个函数分配的堆内存3.1 compile一键编译入口compile是最高层的封装。从 lib.rs 的注释与实现可以看出它是prql_to_pl、pl_to_rq、rq_to_sql三者的串联且在每一步之间不经过 JSON 序列化只是通过Options控制最终的 SQL 生成参数。这意味着如果只需要最终 SQLcompile是开销最低、最直接的选择如果要做中间表示PL/RQ的检查、调试或二次加工则使用后三个分阶段函数。3.2 分阶段入口prql_to_pl / pl_to_rq / rq_to_sqlPRQL 编译管线分为三个明确的阶段对应prqlccrate 的prql_to_pl、pl_to_rq、rq_to_sqlPLPipeline Language解析 PRQL 源码得到的管道语言 AST。prql_to_pl返回 PL 的 JSON 表示。RQRelational Query完成变量引用解析、函数调用校验、帧frame判定后的关系查询 IR。pl_to_rq接收 PL JSON、输出 RQ JSON。SQLrq_to_sql将 RQ 翻译为具体方言的 SQL 字符串。从 lib.rs 的实现看这三个函数都用prqlc::json::to_pl / from_pl / to_rq / from_rq完成 Rust 对象与 JSON 的双向转换边界清晰、便于宿主语言逐步调试。README 中把它们称作 staged entry points典型用法是「先跑prql_to_pl看解析结果再喂给pl_to_rq」这种递进式排查。3.3 内存所有权与 result_destroy这是 FFI 使用中最重要的约定。CompileResult结构包含三个字段typedef struct CompileResult { const char *output; const struct Message *messages; size_t messages_len; } CompileResult;所有由编译函数分配的字符串与数组都位于 Rust 堆上宿主语言不得手动 free 任何字段必须且只能调用一次result_destroy。从 lib.rs 的实现可以看到result_destroy会递归释放messages数组中每条Message内部的code、reason、hint、span、display、location指针以及output字符串。README 与头文件都反复强调「expects to be called exactly once」漏调会导致内存泄漏重复调用则是未定义行为double-free。四、自定义编译选项 Options 详解Options结构体C 定义见 prqlc.h是compile与rq_to_sql的可选参数传NULL表示全部使用默认值。三个字段及其默认值字段类型默认值说明formatbooltrue是否对生成的 SQL 进行格式化多行拆分、缩进与间距美化targetchar *sql.any目标 SQL 方言sql.any表示由查询头header中的target参数决定方言signature_commentbooltrue是否在生成的 SQL 之后追加编译器签名注释底层映射逻辑位于 lib.rs 的 convert_optionstarget为空指针或空字符串时回退到sql.any再通过Target::from_str解析为prqlc::Target枚举最终构造成prqlc::OptionsOk(prqlc::Options::default() .with_format(o.format) .with_target(target) .with_signature_comment(o.signature_comment))注意target字段是非 const 的char *但在当前实现中只读不写宿主传入字符串字面量即可C 示例与 Zig 示例都是这样做的。target的取值与prqlc支持的方言一致sql.any、sql.duckdb、sql.postgres、sql.sqlite、sql.mssql、sql.clickhouse等具体在prqlccrate 的Target枚举与 std.sql.prql 中定义。Zig 示例main.zig就使用了sql.mssql来验证方言切换。五、实战示例C、C、Zig 与 Golang5.1 C 最小示例gcc makeexamples/minimal-c/main.c 是官方最小示例覆盖了 README 提到的全部能力默认编译、自定义 Options、错误处理、分阶段入口。核心调用流程#include stdio.h #include prqlc.h // 默认选项编译 CompileResult res compile(prql_query, NULL); // 检查 messages_len 判断是否出错输出 res.output result_destroy(res); // 自定义选项编译 Options opts; opts.format false; opts.signature_comment false; opts.target sql.mssql; res compile(prql_query, opts); result_destroy(res); // 分阶段调用PL - RQ res prql_to_pl(prql_query); res2 pl_to_rq(res.output); result_destroy(res); result_destroy(res2);示例使用的查询为from albums | select {album_id, title} | take 3其print_result函数演示了错误输出的两种形态有display时打印带标注的代码片段*e-display否则退回到[code] Error: reason或纯reason。运行方式cd prqlc/bindings/prqlc-c/examples/minimal-c make run5.2 C 示例examples/minimal-cpp/main.cpp 与 C 版流程相同但包含自动生成的 prqlc.hpp并放入prqlc命名空间因此可以写出更简洁的调用#include cstring #include iostream #include prqlc.hpp using namespace prqlc; CompileResult res compile(prql_query, nullptr); print_result(res); result_destroy(res);其 Makefile 使用g编译、链接同一个libprqlc_c.a证明 C 绑定只是 C ABI 之上的薄命名空间封装。5.3 Zig 示例cImportexamples/minimal-zig/src/main.zig 展示了 Zig 生态中最自然的接入方式——通过cImport直接吞入prqlc.hconst std import(std); const prql cImport({ cInclude(../c/prqlc.h); }); pub fn main() !void { var target sql.mssql.*; const options prql.Options{ .format false, .signature_comment false, .target target, }; const prql_query from albums | select {album_id, title} | take 3; const result prql.compile(prql_query, options); defer prql.result_destroy(result); std.debug.print(Output:\n\n{s}\n, .{result.output}); }这里defer prql.result_destroy(result)是 Zig 惯用法保证无论提前 return 还是正常结束都会释放内存与 C 示例中的手动result_destroy语义一致。示例还附带了一个 Zig test编译同一查询并断言messages_len 0。5.4 Golangcgo示例README 直接给出了 Golang 场景的链接命令使用 cgo 时必须让链接器找到静态库及其系统依赖CGO_LDFLAGS-L/path/to/target/release -lprqlc_c -pthread -ldl -lm go buildmacOS 下追加-framework CoreFoundation。cgo 会为C包中的声明来自prqlc.h生成可调用绑定之后在 Go 代码中即可直接调用compile、result_destroy等函数。这正是 README 所说「allows embedding in languages that support FFI」的典型落地把编译能力作为构建期或运行期组件内嵌进 Go 服务。六、错误处理模型Message 结构与诊断信息编译失败时CompileResult.output为空字符串messages_len大于 0messages指向Message数组。Message的完整定义prqlc.h对应 Rust 侧prqlc::ErrorMessagetypedef struct Message { enum MessageKind kind; // 目前仅 Error 被实现 const char *const *code; // 机器可读的错误标识 const char *reason; // 错误纯文本 const char *const *hint; // 修复建议列表换行拼接 const struct Span *span; // 错误在源码中的字符偏移区间 const char *const *display; // 带 cause/hints 标注的代码片段 const struct SourceLocation *location; // 起始/结束行列号 } Message;配套的两个定位结构typedef struct Span { size_t start; // 字符偏移起点 size_t end; // 字符偏移终点 } Span; typedef struct SourceLocation { size_t start_line, start_col; size_t end_line, end_col; } SourceLocation;从 lib.rs 的 result_into_c_str 可以看到错误路径的实现Vecprqlc::ErrorMessage被逐一转换为 C 结构的Message数组hints用换行符拼接成一个字符串code/hint/display/span/location均为可选字段——为NULL时表示不存在。MessageKind枚举虽然声明了Error、Warning、Lint三值但当前编译器只产生Error。C 示例中的两个错误用例值得借鉴// 列在 select 中重复引用title 在第一次 select 后已被投影掉 compile(from album | select {album_id} | select {title}, NULL); // let 绑定的子查询未在最终管线中使用 compile(let a (from album), NULL);print_result优先输出*e-display带行号与标注的源码片段这通常是定位问题最快的方式其次才是[code] Error: reason与reason。七、开发与维护用 cbindgen 重新生成头文件prqlc.h与prqlc.hpp都是自动生成的文件头部有 This file is autogenerated 声明生成工具是 cbindgen。生成配置见 cbindgen.tomlC 语言、命名空间prqlc、自动生成警告、#define FFI_SCOPE PRQL。官方推荐的重新生成命令task build-prqlc-c-header该任务定义在仓库根目录 Taskfile.yaml 中等价于在prqlc/bindings/prqlc-c目录下执行两条 cbindgen 命令cbindgen --crate prqlc-c --output prqlc.h cbindgen --crate prqlc-c --lang C --output prqlc.hpp这条开发流程意味着当 src/lib.rs 中新增或修改了#[no_mangle]导出结构、函数签名后需要重新生成两个头文件才能让 C/C/Zig 宿主看到最新 API。对使用者而言直接使用仓库中现成的prqlc.h/prqlc.hpp即可无需自行运行 cbindgen只有维护绑定本身时才需要这条命令。八、构建与验证路径速查以下命令均以仓库根目录为基准方便你快速验证本文内容# 1) 编译静态库与动态库 cargo build --package prqlc-c --release # 产物target/release/libprqlc_c.a 与 libprqlc_c.so # 2) 运行官方 C 最小示例内部会先执行上面的 cargo build cd prqlc/bindings/prqlc-c/examples/minimal-c make run # 3) 运行官方 C 示例 cd prqlc/bindings/prqlc-c/examples/minimal-cpp make run # 4) 用 valgrind 检查内存释放是否完整示例自带该 target cd prqlc/bindings/prqlc-c/examples/minimal-c make valgrindC 与 C 示例的 Makefile 中都预置了valgrindtarget这是验证「每个CompileResult恰好调用一次result_destroy」最直接的实践工具。九、总结与适用范围适用范围需要把「PRQL → SQL」编译能力嵌入 C/C、Golang、Zig 等支持 C ABI 的语言或工具链的场景需要跨进程、跨语言共享编译器能力的场景。核心约定字符串输入必须是零终止的 C 字符串CompileResult的内存只能通过result_destroy释放且只释放一次Options可传NULL使用默认值target支持sql.any及各类方言。能力边界当前MessageKind只实现了Error编译选项仅包含format、target、signature_comment三项产物不含 WebAssembly 目标lib.rs 顶部#![cfg(not(target_family wasm))]明确排除了 wasm。对于希望进一步深入源码的读者建议依次阅读 src/lib.rsFFI 实现、prqlc.h完整 API 文档、examples/minimal-c/main.c最全调用范式以及上层prqlccrate 中对应的prql_to_pl/pl_to_rq/rq_to_sql与Options定义即可打通从 C 调用到 Rust 编译管线的完整链路。赞分享后端【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址https://gitcode.com/gh_mirrors/pr/prql点击查看免费下载相关推荐PRQL 官方 minimal-cpp 示例解析在 C 中通过 prqlc-c FFI 编译 PRQL 查询PRQL 官方 minimal cpp 示例解析在 C 中通过 prqlc c FFI 编译 PRQL 查询 PRQLPipelined Relatio后端PRQL 编译器 prqlc 实战指南从 CLI 管道编译到 Rust 库集成PRQL 编译器 prqlc 实战指南从 CLI 管道编译到 Rust 库集成 prqlc 是 PRQLPipelined Relational Query后端QStudio 中使用 PRQL 查询基于 prqlc 编译器的 SQL GUI 集成实战指南QStudio 中使用 PRQL 查询基于 prqlc 编译器的 SQL GUI 集成实战指南 QStudio 是一款跨平台 SQL 图形界面工具支持浏览数后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表