ARTICLE DETAIL

资讯详情

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

PRQL 官方 minimal-cpp 示例解析:在 C++ 中通过 prqlc-c FFI 编译 PRQL 查询

PRQL 官方 minimal-cpp 示例解析:在 C++ 中通过 prqlc-c FFI 编译 PRQL 查询 后端【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址https://gitcode.com/gh_mirrors/pr/prql点击查看免费下载PRQLPipelined Relational Query Language是一种现代化的数据转换语言旨在成为 SQL 的简单、强大的流水线式替代品。本指南以仓库中 minimal-cpp 示例 为核心完整讲解如何在 C 程序中通过prqlc-c这一 C/C FFI 绑定把 PRQL 查询编译为 SQL。读完本文你将掌握从环境准备、Makefile 链接配置到compile/result_destroy等关键 API 调用的完整实战链路并理解其底层编译流水线与内存管理约定。一、示例概述一条命令跑通 C 调 PRQLminimal-cpp 示例 是 PRQL 仓库中面向 C 开发者的最小可用示例。它本身只有三部分内容一个 C 源文件 main.cpp、一份 Makefile 以及一段极其精简的使用说明A minimal example for using prqlc-c with gcc and make. ## How to run make run也就是说官方给出的运行方式只有一行命令make run。其背后的完整工作流是Makefile 先通过 Cargo 构建 Rust 侧静态库libprqlc_c.a再用g编译链接示例程序并直接执行。下面各节将逐层拆解这条命令背后发生的每一件事。二、main.cpp 代码逐行解读完整源码位于 main.cpp全文仅 24 行核心逻辑如下#include cstring #include iostream #include prqlc.hpp using namespace prqlc; void print_result(CompileResult res) { if (strcmp(res.output, ) 0) { std::cout Output: empty\n\n; } else { std::cout Output:\n\n res.output; } } int main() { const auto prql_query from albums | select {album_id, title} | take 3; CompileResult res compile(prql_query, nullptr); print_result(res); result_destroy(res); return 0; }可以提炼出调用 FFI 的四个标准步骤引入头文件通过#include prqlc.hpp引入由 cbindgen 自动生成的 C 头文件using namespace prqlc;使其所有类型可直接使用。构造 PRQL 查询字符串示例使用from albums | select {album_id, title} | take 3即从albums表选取album_id与title两列并取前 3 行——一个典型的 PRQL 流水线。调用compileCompileResult res compile(prql_query, nullptr);。第二个参数传nullptr表示使用编译器的默认选项。释放结果result_destroy(res);归还 FFI 侧分配的堆内存。输出判断用strcmp(res.output, ) 0来识别空输出编译失败时output为空字符串详细错误在messages中。注意最小示例只打印输出、不打印错误消息更完整的错误处理写法见后文第六节。三、Makefile 深度拆解链接静态库的完整参数Makefile 是这个示例中信息量最大的部分它展示了C prqlc-c的标准构建与链接方式PRQL_PROJECT../../../../.. run: build ./main.out 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 ifeq ($(UNAME_S),Darwin) LD_FLAGS : $(LD_FLAGS) -framework CoreFoundation endif build: main.cpp build-prql g main.cpp -o main.out \ -I${PRQL_PROJECT}/prqlc/bindings/prqlc-c \ $(LD_FLAGS) valgrind: build valgrind ./main.out逐项说明PRQL_PROJECT../../../../..从prqlc/bindings/prqlc-c/examples/minimal-cpp/向上回溯 5 级到仓库根目录作为定位 Rust 构建产物和头文件的基准路径。cargo build --package prqlc-c --release以 release 模式构建prqlc-ccrate。根据 prqlc-c/Cargo.toml 中的crate-type [staticlib, cdylib]一次构建会同时产出静态库libprqlc_c.a与动态库libprqlc_c.somacOS 上为.dylib产物位于仓库根目录的target/release/下。-L${PRQL_PROJECT}/target/release把静态库所在目录加入链接器搜索路径。${PRQL_PROJECT}/target/release/libprqlc_c.a直接以完整路径显式链接静态库绕开-l的命名查找规则。-I${PRQL_PROJECT}/prqlc/bindings/prqlc-c头文件搜索路径使#include prqlc.hpp能够命中 prqlc.hpp。macOS 特殊处理ifeq ($(UNAME_S),Darwin)时追加-framework CoreFoundation。这是因为 prqlc 依赖 Rust 的core-foundation系系统库macOS 上链接静态库必须显式带上该 framework。valgrind目标在构建完成后用 Valgrind 运行./main.out用于检测内存泄漏——这与下文第五节的内存管理要求直接呼应。在 Linux 上完整的等价命令可还原为假定仓库根目录为$PRQL_PROJECTcargo build --package prqlc-c --release g main.cpp -o main.out \ -I${PRQL_PROJECT}/prqlc/bindings/prqlc-c \ -L${PRQL_PROJECT}/target/release \ ${PRQL_PROJECT}/target/release/libprqlc_c.a ./main.out四、FFI 核心 API五个导出函数与三个数据结构prqlc-c的全部 FFI 表面由 src/lib.rs 定义并通过 cbindgen 同步生成 C/C 两个头文件 prqlc.h 与 prqlc.hpp。C 侧可见的 API 全部位于namespace prqlc中共有 5 个导出函数函数签名C 侧作用compileCompileResult compile(const char *prql_query, const Options *options)一步完成 PRQL → SQL 的完整编译options 可传nullptrprql_to_plCompileResult prql_to_pl(const char *prql_query)PRQL 源码 → PL ASTJSON 序列化输出pl_to_rqCompileResult pl_to_rq(const char *pl_json)PL JSON → RQ ASTJSON 序列化输出rq_to_sqlCompileResult rq_to_sql(const char *rq_json, const Options *options)RQ JSON → SQL 字符串result_destroyvoid result_destroy(CompileResult res)释放CompileResult占用的全部堆内存从 lib.rs 中compile的实现 可以看到它本质上是后三个阶段的无 JSON 中转封装let result options .and_then(|opts| { Ok(prql_query.as_str()) .and_then(prqlc::prql_to_pl) .and_then(prqlc::pl_to_rq) .and_then(|rq| prqlc::rq_to_sql(rq, opts.unwrap_or_default())) }) .map_err(|e| e.composed(prql_query.into()));即一条调用链prql_to_pl → pl_to_rq → rq_to_sql对应 PRQL 编译器经典的 PL流水线 AST→ RQ关系代数 AST→ SQL 三阶段设计。4.1 Options编译选项结构体Options是唯一需要 C 侧手动填充的结构体其三个字段及默认值prqlc.hpp 中的文档注释为struct Options { bool format; // 是否对生成的 SQL 做美化格式化多行、缩进、间距默认 true char *target; // 目标 SQL 方言默认 sql.any由查询头决定方言 bool signature_comment; // 是否在生成的 SQL 尾部附加编译器签名注释默认 true };对照 lib.rs 中convert_options的实现可以发现两个细节target传nullptr或空字符串时会被归一化为sql.anyOptions各字段会透传给prqlc::Options因此传入nullptr与传入全默认字段的Options行为等价。target可取如sql.mssql、sql.duckdb、sql.postgres等方言标识可用于跨数据库方言的编译输出。4.2 CompileResult编译结果结构体struct CompileResult { const char *output; // 编译输出成功时为 SQL或 PL/RQ 的 JSON失败时为空字符串 const Message *messages; // 错误/警告消息数组无消息时为 nullptr size_t messages_len; // 消息条数 };编译成功时output持有结果、messages_len为 0编译失败时output为空、messages指向Message数组见 result_into_c_str 实现。Message结构体还包含机器可读错误码code、纯文本reason、修复建议hint、带上下文的display、字符偏移span以及行列位置location可用于构造友好的错误报告。五、内存管理约定为什么必须调用 result_destroy这是使用 prqlc-c 最容易踩坑的地方官方在头文件与源码中反复强调。所有返回CompileResult的函数compile、prql_to_pl、pl_to_rq、rq_to_sql都会在 Rust 侧堆上分配内存字符串、消息数组、Options 等因此每个CompileResult必须且只能调用一次result_destroy且不得手动free其任何字段见 prqlc.hpp 的 Safety 说明输入字符串要求是0 结尾的 C 字符串Rust 侧通过CStr::from_ptr读取lib.rs从 result_destroy 的实现 可见它会递归释放每条消息的code、reason、hint、span、display、location再释放消息数组和output字符串。因此print_result必须在result_destroy之前完成对res.output的读取——这正是最小示例的调用顺序。示例 Makefile 中内置的valgrind目标valgrind ./main.out即用于验证这类内存生命周期是否正确。在编写自己的代码时建议像 C 的 RAII 习惯那样把result_destroy放入析构/延迟释放逻辑例如 Zig 示例中的defer prql.result_destroy(result)见 minimal-zig 的 main.zig确保所有返回路径都会释放。六、从最小示例到生产用法错误处理与自定义选项minimal-cpp 只演示了最简路径同目录的 minimal-c/main.cC 语言版则补齐了另外三个关键用法C 侧可完全照搬同样的模式1. 遍历并打印错误消息for (size_t i 0; i res.messages_len; i) { Message const *e res.messages[i]; if (e-display ! NULL) { printf(%s, *e-display); } else if (e-code ! NULL) { printf([%s] Error: %s\n, *e-code, e-reason); } else { printf(Error: %s, e-reason); } }e-code与e-display是指向指针的指针const char *const *指向 C 字符串指针数组因此需要解引用一层再按字符串打印。2. 自定义 Options以 SQL Server 方言为例Options opts; opts.format false; // 关闭格式化 opts.signature_comment false; // 关闭签名注释 opts.target sql.mssql; // 输出 SQL Server 方言 res compile(prql_query, opts);注意 C 侧Options.target类型为char *传字符串字面量时需要自行保证其生命周期覆盖compile调用期间。3. 使用分阶段 API 查看中间 ASTres prql_to_pl(prql_query); // 得到 PL JSON res2 pl_to_rq(res.output); // 将 PL JSON 转 RQ JSON rq_to_sql(res2.output, NULL); // 最终转 SQL先result_destroy(res)再复用可避免提前释放被下游消费的output缓冲区。这套分阶段接口非常适合调试编译管线或在 JSON 层做自定义处理。七、prqlc.hpp 从哪来cbindgen 与头文件再生成prqlc.hpp 与 prqlc.h 均以/* This file is autogenerated. Do not modify this file manually. */开头由 cbindgen 从 src/lib.rs 自动生成。若需在修改 FFI 后重新生成头文件官方 READMEprqlc-c/README.md与根目录 Taskfile.yaml 给出了标准做法task build-prqlc-c-header对应 Taskfile 中的实际命令是cbindgen --crate prqlc-c --output prqlc.h cbindgen --crate prqlc-c --lang C --output prqlc.hpp日常开发中无需手动生成直接使用仓库内已有的头文件即可。八、迁移到其他构建系统与语言prqlc-c不只服务于 C/C。由于它编译为标准的 C ABI 静态/动态库任何支持 FFI 的语言都可复用同一套 API。prqlc-c/README.md 给出了在其他构建系统中链接静态库的通用配方CGO_LDFLAGS-L/path/to/target/release -lprqlc_c -pthread -ldl -lm go build即链接时除-lprqlc_c外还需带上其系统依赖-pthread -ldl -lmmacOS 上再加-framework CoreFoundation。仓库内还提供了另外两个对照实现minimal-c功能最全的 C 示例错误处理、自定义 Options、分阶段 API 全覆盖minimal-zig通过 Zig 的cImport直接引用prqlc.h。如果你的应用需要嵌入 PRQL 编译能力推荐以 minimal-cpp 为起点跑通构建 → 链接 → 调用 → 释放全流程再逐步引入第六节中的错误处理与自定义方言选项。九、小结掌握的最小可用链路回顾整条链路make run一次执行背后是Cargo 构建静态库 → g 编译链接 → 运行可执行文件三个步骤程序内部则是compile一行调用 → 三阶段编译流水线 →print_result读取输出 →result_destroy释放内存。理解这四点即可把 PRQL 的编译能力稳定嵌入到任意 C 项目中并在此基础上平滑迁移到错误处理、自定义方言乃至分阶段 AST 调试等进阶用法。赞分享后端【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址https://gitcode.com/gh_mirrors/pr/prql点击查看免费下载相关推荐使用 prql-php通过 PHP FFI 调用 PRQL 编译器将 PRQL 查询编译为 SQL使用 prql php通过 PHP FFI 调用 PRQL 编译器将 PRQL 查询编译为 SQL PRQLPipelined Relational Que后端在 PostgreSQL 中用 PRQL 编写查询PL/PRQL 扩展与 prqlc 方言支持实战指南在 PostgreSQL 中用 PRQL 编写查询PL/PRQL 扩展与 prqlc 方言支持实战指南 本篇技术指南围绕 PRQL 项目官方文档中 Postg后端PRQL Elixir Bindings在 Elixir 中编译 PRQL 查询为 SQL 的完整指南PRQL Elixir Bindings在 Elixir 中编译 PRQL 查询为 SQL 的完整指南 PRQLPipelined Relational Q后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表