使用 prql-php:通过 PHP FFI 调用 PRQL 编译器将 PRQL 查询编译为 SQL
后端【免费下载链接】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。prql-php是 PRQL 项目为 PHP 提供的官方绑定bindings它通过 PHP FFIForeign Function Interface将 Rust 编写的prqlc编译器暴露给 PHP 应用。本文将以 prqlc/bindings/php/README.md 为骨架结合 PHP 绑定源码 与底层 C FFI 实现完整讲解prql-php的环境安装、Compiler类四大编译方法、编译选项与错误消息结构、底层 FFI 调用链以及面向开发者的构建、测试与代码规范流程。读完本文你将能够在自己的 PHP 项目中直接编译 PRQL 查询并获得对应的 SQL并能深入理解这条从 PHP 到 Rust 再到 SQL 的完整链路。prql-php 是什么prql-php提供的是prqlcRust crate 的 PHP 绑定。它没有用纯 PHP 重新实现 PRQL 编译器而是通过 PHP 8 内置的 FFI 扩展直接加载prqlc-c编译产物一个共享库从而在 PHP 进程中完成 PRQL → SQL 的编译。从 php.md 的入口该文档通过 mdBook 的{{#include}}指令直接引用了 prqlc/bindings/php/README.md可以看到prql-php对外暴露的核心是一个Compiler类包含四个主要方法compile把 PRQL 字符串一步编译为 SQL 字符串prqlToPL把 PRQL 编译为 PLPipeline Language中间表示输出 JSONplToRQ把 PL JSON 转换为 RQRelational Query中间表示输出 JSONrqToSQL把 RQ JSON 转换为最终的 SQL 字符串。需要说明的是该绑定仍处于早期阶段README 明确标注 Its still at an early stage尚未发布到 Composer 官方仓库且项目欢迎社区贡献。因此在实际使用时需要从本仓库源码自行构建。环境要求与安装启用 PHP FFI 扩展prql-php依赖 PHP 的 FFI 扩展。需要在php.ini配置文件中开启ffi.enable true从 composer.json 可以看到库的运行时依赖为php: ^8.1和ext-ffi: *即要求 PHP 8.1 及以上版本并且必须安装ext-ffi扩展。构建并准备共享库由于prql-php通过 FFI 加载 Rust 编译产物使用前必须先在本地构建prqlc-c动态库。项目根目录的 Taskfile.yaml 中定义了build-php任务它完成三件事cargo build --package prqlc-c --release—— 以 release 模式编译prqlc-ccrate把生成的libprqlc_c.*Linux 下为libprqlc_c.so与prqlc.h头文件复制到prqlc/bindings/php/lib/目录在prqlc/bindings/php下执行composer install安装 PHP 依赖。在 Compiler.php 的构造函数中可以看到库的加载逻辑默认从__DIR__ . /../lib即prqlc/bindings/php/lib查找头文件prqlc.h与共享库并按操作系统区分动态库文件名Windowslibprqlc_c.dllmacOSDarwinlibprqlc_c.dylib其他Linux 等libprqlc_c.so构造函数支持传入?string $lib_path自定义库所在目录方便将库部署到其他位置。快速上手编译第一条 PRQL 查询README 给出了最简用法。在项目内安装依赖并构建好共享库后编写如下 PHP 代码?php use Prql\Compiler\Compiler; $prql new Compiler(); $result $prql-compile(from employees); echo $result-output;compile(from employees)会把这条 PRQL 管道查询编译为 SQL结果对象的output属性保存生成的 SQL 字符串直接输出即可。这是最常用的入口一条 PRQL 查询、一步拿到 SQL。更完整一点配合Options使用?php use Prql\Compiler\Compiler; use Prql\Compiler\Options; $options new Options(); $options-format false; // 不美化 SQL 格式 $options-signature_comment false; // 不在 SQL 后追加编译器签名注释 $options-target sql.mssql; // 指定 SQL Server 方言 $prql new Compiler(); $result $prql-compile(from employees | take 10, $options); if (count($result-messages) 0) { echo $result-output; }这条示例并非凭空捏造——它正是 CompilerTest.php 中testCompileWorks测试用例的复刻测试断言其输出为SELECT * FROM employees ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY可以看到take 10在 SQL Server 方言下被翻译为FETCH FIRST 10 ROWS ONLY。Compiler 类四个编译方法与分层管线一步到位的 compilecompile(string $prql_query, ?Options $options null): Result是最常用的 API。在底层它并不是一个独立的编译流程而是prqlToPL、plToRQ、rqToSQL三段式管线的打包版本。对照 prqlc-c 的 C FFI 层compile的 Rust 实现正是依次调用prqlc::prql_to_pl→prqlc::pl_to_rq→prqlc::rq_to_sql且省去了中间两次 JSON 序列化开销。这解释了为何 PHP 层提供两种使用方式日常编译用compile需要调试中间表示或做二次处理时用后三个方法。分步调试prqlToPL / plToRQ / rqToSQLPRQL 的编译是一条清晰的三段式流水线对应编译器架构中的三层 IR方法输入输出对应 IR 阶段prqlToPLPRQL 源码字符串PL 序列化为 JSON解析与语义分析后的管道语言PLplToRQPL JSONRQ 序列化为 JSON解析变量引用、校验函数调用、确定 frame 后的关系查询RQrqToSQLRQ JSONSQL 字符串面向具体方言的 SQL 生成从 prqlc-c 的 C FFI 层 可以看到三者对应的 C 符号分别为prql_to_pl、pl_to_rq、rq_to_sqlCompiler.php 中的同名方法正是通过 FFI 对这些符号的封装。CompilerTest.php 中的testOtherFunctions给出了完整的分步用法先用prqlToPL得到 PL JSON再用plToRQ得到 RQ JSON接着用rqToSQL得到 SQL并断言该结果与直接用compile得到的结果完全一致assertEquals($via_json, $direct)从测试层面印证了分步调用 一步调用的等价性。Result 与错误消息四个方法统一返回Result对象其结构在 Result.php 中定义包含两个公开属性string $output编译产物SQL 字符串或在 PL/RQ 阶段为 JSON 字符串array $messages编译过程中产生的消息数组元素类型为Message。Message.php 定义了Message的字段kind消息类型、code机器可读的错误标识、reason纯文本错误说明、hint修复建议、span源码中的字符偏移区间、display带注解的代码片段、location行列号。消息类型为 MessageKind.php 中的枚举Error/Warning/Lint其中Error是目前唯一被实际实现并产出的类型见 C 层注释 Currently only Error is implemented。在 Compiler.php 的convertResult/convertMessage中可以看到PHP 层会把 C 结构体逐字段转换为 PHP 对象SpanSpan.php字符偏移起止与SourceLocationSourceLocation.php起止行列在指针为空时映射为null。一个典型的错误处理写法?php use Prql\Compiler\Compiler; $prql new Compiler(); $result $prql-compile(invalid); foreach ($result-messages as $message) { echo $message-reason, PHP_EOL; if ($message-location ! null) { echo at line {$message-location-start_line}, col {$message-location-start_col}, PHP_EOL; } }对应测试 testInvalidQuery 正是断言对invalid这种非法查询会返回恰好 1 条消息。Options 编译选项详解Options类Options.php控制 SQL 生成阶段的三个行为与 C FFI 层 Options 结构体 一一对应属性类型默认值作用formatbooltrue是否对生成的 SQL 进行美化拆分成多行、优化缩进与空格target?stringnull等价于sql.any编译目标方言如sql.mssql、sql.duckdb、sql.postgres等signature_commentbooltrue是否在生成的 SQL 末尾追加编译器签名注释target的语义在 C 层有明确说明默认值sql.any表示根据查询头query header中的target参数自动决定 SQL 方言。从 prqlc-c 的 convert_options 可以看到PHP 传入的target字符串最终会通过Target::from_str解析为 Rust 侧的方言枚举解析失败会直接产生编译错误。PHP 侧的optionsInit方法Compiler.php负责把Options对象拷贝为 C 结构体并在optionsDestroy中释放target字符串占用的 FFI 内存——这是使用 FFI 时需要注意的 C 侧内存管理细节库已经帮你处理妥当。底层原理PHP 到 Rust 的 FFI 调用链prql-php之所以能零成本复用 Rust 编译器靠的是prqlc-c这个 C ABI 中间层。整条链路可以概括为PHP 调用 Compiler::compile() → PHP FFI::cdef() 绑定 libprqlc_c 动态库 → C 符号 compile()Rust #[no_mangle] 导出 → prqlc::prql_to_pl() → prqlc::pl_to_rq() → prqlc::rq_to_sql() → CompileResult 结构体output messages 数组 → PHP convertResult() 转换为 Result 对象几个关键设计点C ABI 导出prqlc-c使用#[no_mangle] pub unsafe extern C fn导出compile、prql_to_pl、pl_to_rq、rq_to_sql、result_destroy等符号见 prqlc-c/src/lib.rs头文件驱动绑定PHP 侧通过\FFI::cdef($header_source, $library)解析 prqlc.h 头文件来声明函数与结构体布局因此库目录中必须同时存在头文件与动态库内存所有权Rust 侧分配的内存由result_destroy统一释放lib.rs#L199-L245PHP 侧在每次调用后立即调用它避免泄漏。文档注释明确要求每个返回CompileResult的函数调用后result_destroy必须恰好被调用一次字符串传递Rust 假设输入是零终止字符串*const c_charPHP 侧由 FFI 自动处理转换。开发指南环境、构建、测试与代码规范搭建开发环境README 推荐使用 nix flake 快速获得带 PHP、ext-ffi与 Composer 的开发环境。首先在 nix 中启用实验性的 flakes 特性mkdir -p ~/.config/nix echo experimental-features nix-command flakes ~/.config/nix/nix.conf然后在prqlc/bindings/php/目录内进入开发 shellnix shell github:loophp/nix-shell#env-php81 --impure该 flake 会自动带入ext-ffi扩展——正如 composer.json 中require声明的ext-ffi: *所要求的那样。当然如果你已经有配置好ext-ffi的 PHP 8.1 环境也可以跳过 nix直接在本地执行composer install。构建与测试项目的 Taskfile.yaml 提供了两个与 PHP 绑定相关的任务task build-php task test-phpbuild-php负责编译 Rust 动态库并把产物与头文件复制到lib/test-php则在prqlc/bindings/php目录下运行vendor/bin/phpunit tests。测试套件CompilerTest.php覆盖了四类断言环境自检extension_loaded(ffi)为真且lib/下存在动态库与prqlc.h头文件testFfiExtensionIsLoaded、testPrqlLibraryFileExists、testPrqlHeaderFileExists错误路径非法查询返回非空messages正确性指定sql.mssql方言编译from employees | take 10得到预期 SQL管线一致性分步调用prqlToPL → plToRQ → rqToSQL的结果与compile一步调用完全相等。代码规范prql-php的 PHP 代码遵循 PSR-12 编码标准通过 PHP_CodeSniffer 检查./vendor/bin/phpcs --standardPSR12 src tests从 composer.json 的require-dev可以看到开发依赖为phpunit/phpunit: ^10与squizlabs/php_codesniffer: ^3.7。库的自动加载遵循 PSR-4命名空间Prql\Compiler\映射到src/目录。现状、限制与适用场景成熟度README 明确说明绑定仍处于早期阶段尚未发布到 Composer 官方仓库因此不能直接composer require prql/compiler需要基于本仓库自行构建并引入平台限制需要 PHP 8.1、ext-ffi扩展以及对应平台的libprqlc_c动态库Linux.so/ macOS.dylib/ Windows.dll典型场景在 PHP 应用中动态生成 SQL例如把业务上易于表达、维护的 PRQL 管道编译成 SQL 交给数据库执行或借助prqlToPL/plToRQ调试、检查 PRQL 的中间表示进一步阅读绑定目录的总体说明见 web/book/src/project/bindings/README.md其他语言的绑定实现C、Python、JS、Java、.NET、Elixir位于 prqlc/bindings 下可作为横向对比参考。总而言之prql-php用不到两百行 PHP 代码通过 FFI 把完整的prqlc编译器带进了 PHP 生态一条 PRQL 管道、一个Compiler实例就能在你的 PHP 应用中产出跨方言的 SQL。赞分享后端【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址https://gitcode.com/gh_mirrors/pr/prql点击查看免费下载相关推荐深入PRQL编译器架构从源码到SQL的魔法转换深入PRQL编译器架构从源码到SQL的魔法转换 PRQL编译器采用分层架构设计将查询转换过程分解为解析、语义分析和SQL生成三个阶段。本文详细解析了PRQL后端PRQL 语言与编译器深度解析基于 prql 仓库的管道式 SQL 替代方案实战指南PRQL 语言与编译器深度解析基于 prql 仓库的管道式 SQL 替代方案实战指南 PRQL P ipelined R elational Q uery后端PRQL性能优化技巧10个方法提升你的查询编译和执行效率PRQL性能优化技巧10个方法提升你的查询编译和执行效率 PRQLPipelined Relational Query Language是一个现代化的数据后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考