- 后端
【免费下载链接】prql
PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement
本指南围绕 PRQL 仓库中的 Elixir 语言绑定(位于 prqlc/bindings/elixir/README.md,并被文档站点收录于 web/book/src/project/bindings/elixir.md)展开,介绍如何在 Elixir 项目中安装、调用PRQL.compile/2将 PRQL 管道式查询编译为 SQL,并结合仓库源码剖析其 Rustler NIF 桥接层的实现原理、参数语义与开发测试流程。读完本文,你将能够在自己的 Elixir 应用中直接驱动prqlc编译器,并理解其编译管线的每一层(PRQL → PL AST → RQ → SQL)对应的 Elixir API。
一、什么是 PRQL 的 Elixir 绑定
PRQL(Pipelined Relational Query Language)是一种面向数据转换的现代查询语言,目标是成为 SQL 的管道式替代品。PRQL 编译器的核心是 Rust crateprqlc,位于本仓库的 prqlc/prqlc 目录。为了让 Elixir 生态也能使用这一编译器,仓库维护了一套独立的 Elixir 绑定工程,其入口即 prqlc/bindings/elixir/README.md。
该绑定基于Rustler实现:Rustler 是 Elixir 社区中编写 Rust NIF(Native Implemented Function,原生函数)的标准库,它把prqlc的编译能力以原生函数的形式暴露给 Erlang VM,Elixir 侧则通过一个PRQL模块封装调用。从绑定分级来看,Elixir 目前被归类为Unsupported(非官方支持)层级——功能可用,但尚未达到需要维护者、完整测试覆盖、发布到官方包仓库等"Supported"标准(详见 web/book/src/project/bindings/README.md 中的分级说明)。
二、安装与依赖
2.1 在 mix 项目中引入依赖
在mix.exs的deps/0中添加依赖:
def deps do [ {:prql, "~> 0.1.0"} ] end随后执行mix deps.get拉取依赖。当前绑定工程的版本为0.1.0,对 Elixir 的要求是~> 1.15(见 prqlc/bindings/elixir/mix.exs 中project/0的声明)。
2.2 需要 Rust 工具链
需要特别注意的是:在写本文时,该绑定尚未发布预编译产物。README 明确说明,当前在 Elixir 项目中使用该绑定,需要从本仓库源码编译 Rust crate(prqlc/bindings/elixir/README.md的 "Development" 一节)。也就是说,开发/运行环境必须具备 Rust 工具链,且mix compile时会触发对 NIF 的本地编译。
绑定侧的依赖声明位于 prqlc/bindings/elixir/native/prql/Cargo.toml:
prqlc:以path = "../../../../prqlc"指向仓库内的编译器本体(版本对应0.13.15),并关闭默认特性(default-features = false);rustler = "0.38.0":NIF 桥接库;- crate 类型为
cdylib,即编译为可供 Erlang VM 动态加载的共享库。
Elixir 侧的构建依赖同样由 mix.exs 声明:{:rustler, "~> 0.38.0"}与仅开发期使用的{:ex_doc, "~> 0.21"}。
三、基本用法:把 PRQL 编译为 SQL
绑定对外暴露的入口是PRQL.compile/2。以下两个示例直接取自绑定 README,也是PRQL模块的 doctest 用例(见 prqlc/bindings/elixir/lib/prql.ex 中的@doc与 prqlc/bindings/elixir/test/prql_test.exs):
iex> PRQL.compile("from customers", signature_comment: false) {:ok, "SELECT\n *\nFROM\n customers\n"} iex> PRQL.compile("from customers\ntake 10", target: :mssql, signature_comment: false) {:ok, "SELECT\n *\nFROM\n customers\nORDER BY\n (\n SELECT\n NULL\n ) OFFSET 0 ROWS\nFETCH FIRST\n 10 ROWS ONLY\n"}可以观察到两点:
- 返回值是
{:ok, sql_string}元组:成功时第二个元素就是可直接交给 SQL 驱动执行的 SQL 文本; - 方言参数影响输出:
take 10在默认的:generic方言与:mssql方言下会生成不同的分页语法——MSSQL 使用OFFSET 0 ROWS ... FETCH FIRST 10 ROWS ONLY表达取前 10 行,这正体现了 PRQL 面向多种 SQL 方言编译的能力。
四、compile/2 的完整参数语义
PRQL.compile(prql_query, opts \\ [])接受两个参数:待编译的 PRQL 查询字符串,以及关键字选项列表。其类型约束为when is_binary(prql_query) and is_list(opts)(见 lib/prql.ex),并通过struct(CompileOptions, opts)将选项结构化为 NIF 侧期望的 struct。
4.1 target:目标 SQL 方言
target决定生成的 SQL 面向哪种数据库方言,支持以下 12 个原子值:
| 原子值 | 对应 prqlc 方言 | 典型场景 |
|---|---|---|
:generic | Generic(默认) | 通用 SQL |
:mssql | MsSql | Microsoft SQL Server |
:mysql | MySql | MySQL |
:postgres | Postgres | PostgreSQL |
:ansi | Ansi | ANSI 标准 SQL |
:bigquery | BigQuery | Google BigQuery |
:clickhouse | ClickHouse | ClickHouse |
:duckdb | DuckDb | DuckDB |
:oracle | Oracle | Oracle |
:redshift | Redshift | Amazon Redshift |
:sqlite | SQLite | SQLite |
:snowflake | Snowflake | Snowflake |
该参数默认值为:generic。有两个值得注意的语义:
- 未知原子回退到
:generic:Rust 侧的target_from_atom(见 native/prql/src/lib.rs)会逐一分发各方言原子,落入else分支时统一使用Generic; - 方言始终优先于查询头:
@doc中明确说明"这里的方言总是胜过查询头中的target:sql.…参数,且没有可以回退到查询头的值"——即无法通过查询内声明覆盖compile/2传入的target。
4.2 format:SQL 格式化开关
format为布尔值,控制是否将生成的 SQL 交给格式化器进行多行拆分与缩进美化,默认值为true。README 与 doctest 中大量使用signature_comment: false正是为了让输出更简洁、便于断言。
4.3 signature_comment:签名注释开关
signature_comment为布尔值,控制是否在生成的 SQL 后附加编译器签名注释,默认值为true。
以上三个选项的默认值可以在 NIF 侧的 struct 定义中确认(native/prql/src/lib.rs):
defstruct target: :generic, format: true, signature_comment: true而在 Elixir 侧,对应的类型声明位于 lib/prql/native.ex 的PRQL.Native.CompileOptions,其t/0类型包含target: target(), format: boolean(), signature_comment: boolean()三个字段。
Rust 侧将CompileOptions转换为prqlc::Options的实现(impl From<CompileOptions> for prqlc::Options)进一步印证了选项的传递链路:format、target、signature_comment直接映射到prqlc::Options对应字段,同时固定设置display: prqlc::DisplayOptions::Plain(即不做额外的展示层格式化)。
五、错误处理与 compile!/2
5.1 错误返回结构
当查询无法编译时,compile/2返回{:error, reason},其中reason是一个JSON 字符串。测试用例 prql_test.exs 给出了完整的错误 JSON 结构示例——对于查询invalid,返回的错误 JSON 包含inner数组,每个元素含kind、code、reason、hints、span、display(带源码标注的可读错误信息)与location(起始/结束行列坐标)等字段。这得益于 Rust 侧to_result_tuple的实现:prqlc::ErrorMessages会被序列化为 JSON(e.to_json())作为{:error, json}元组的第二个元素返回。
在应用侧,你可以用任意 JSON 解码库(如Jason)解析该错误字符串,从中提取span(如1:0-7)与display字段做进一步展示或定位。
5.2 断言式 API:compile!/2
如果希望出错时直接抛出异常而非返回元组,可以使用compile!/2:
@spec compile!(binary(), [compile_opts()]) :: binary() def compile!(prql_query, opts \\ []) do case compile(prql_query, opts) do {:ok, result} -> result {:error, reason} -> raise PRQL.PRQLError, reason end endcompile!/2成功时直接返回 SQL 字符串,失败时抛出PRQL.PRQLError异常。异常模块定义在 lib/prql/errors.ex:它基于defexception [:message, :error],exception/1回调将message固定为"Error compiling PRQL query",原始错误 JSON 存放在:error字段中。
六、编译器管线的逐层 API
除了端到端的compile/2,绑定还暴露了 PRQL 编译器内部三条管线的逐步调用函数(对应prqlc库的核心编译流程):
| Elixir 函数 | 阶段 | 输入 | 输出 |
|---|---|---|---|
prql_to_pl/1 | PRQL → PL AST(JSON) | PRQL 查询字符串 | {:ok, pl_json}或{:error, json} |
pl_to_rq/1 | PL AST(JSON) → RQ(JSON) | PL 的 JSON 表示 | {:ok, rq_json}或{:error, json} |
rq_to_sql/1 | RQ(JSON) → SQL | RQ 的 JSON 表示 | {:ok, sql}或{:error, json} |
每个函数都有对应的!版本(prql_to_pl!/1、pl_to_rq!/1、rq_to_sql!/1),语义与compile!/2一致:成功返回字符串,失败抛出PRQL.PRQLError。
Rust 侧实现(native/prql/src/lib.rs)揭示了这些函数与prqlc库的对应关系:
prql_to_pl:prqlc::prql_to_pl解析出 PL AST 后,再经prqlc::json::from_pl序列化为 JSON;pl_to_rq:先prqlc::json::to_pl反序列化 PL JSON,经prqlc::pl_to_rq生成 RQ,再由prqlc::json::from_rq输出 JSON;rq_to_sql:先prqlc::json::to_rq反序列化 RQ JSON,再调用prqlc::rq_to_sql生成 SQL——代码注释也指出此处"目前只使用默认 Options"。
这套分层 API 对需要自定义编译管线(例如在 PL/RQ 层做分析或转换)的 Elixir 开发者非常有用,可以嵌入自己的中间处理步骤。
七、架构原理:Elixir 如何调用 Rust 编译器
绑定采用标准的 Rustler NIF 架构,整体数据流如下:
- Elixir 调用层:lib/prql.ex 的
PRQL模块将用户参数结构化为PRQL.Native.CompileOptions,调用PRQL.Native.compile/2等 NIF 函数; - NIF 占位层:lib/prql/native.ex 使用
use Rustler, otp_app: :prql声明 NIF 宿主模块。未加载 NIF 时,各函数返回:erlang.nif_error(:nif_not_loaded),保证模块在无原生库环境下仍可加载; - Rust 实现层:native/prql/src/lib.rs 通过
#[rustler::nif]标注四个导出函数,并以rustler::init!("Elixir.PRQL.Native")注册到名为Elixir.PRQL.Native的模块; - 返回值编码:Rust 侧把
Result<String, prqlc::ErrorMessages>统一转换为Response(一个NifTuple,包含:ok/:error原子与结果字符串),映射为 Elixir 的{:ok, binary()} | {:error, binary()}元组。
值得注意的实现细节:Rust 侧CompileOptions通过#[derive(NifStruct)]与#[module = "PRQL.Native.CompileOptions"]声明与 Elixir struct 的映射关系,因此 Elixir 侧struct(CompileOptions, opts)生成的 struct 可以直接作为 NIF 参数传递。另外,该 crate 在wasm目标下被显式排除(#![cfg(not(target_family = "wasm"))]),表明它面向原生环境运行。
八、开发与测试流程
README 的 "Development" 一节给出了本地开发的三个标准步骤:
mix deps.get # 安装 Elixir 依赖 mix compile # 编译工程(含 Rust NIF) mix test # 运行测试测试工程包含 test/prql_test.exs 与 test/test_helper.exs。其中测试覆盖了两类核心场景:
- 成功路径:
PRQL.compile("from customers", signature_comment: false)断言其输出与预期 SQL 完全一致; - 错误路径:
PRQL.compile("invalid", ...)断言返回的{:error, json}经Jason.decode后与预期的错误结构逐字段相等。
此外,prql_test.exs中的doctest PRQL会直接执行PRQL模块@doc中的 doctest 示例,确保文档中的用法示例持续可验证。NIF 的构建说明见 native/prql/README.md:NIF 会随项目一起编译,并自动加载进PRQL.Native模块。
九、当前状态与未来规划
绑定 README 明确标注"我们正处于 Elixir 绑定的早期开发阶段"("We are in the early stages of developing Elixir bindings")。从 web/book/src/project/bindings/README.md 的分级看,Elixir 绑定当前属于Unsupported层级:功能可正常工作,但尚未满足 Supported 所需的条件(有维护者、覆盖核心编译函数及对应测试、发布到语言官方包仓库、在Taskfile.yaml提供开发环境引导脚本等)。
README 提及的未来工作包括发布预编译产物,届时 Elixir 项目将无需本地 Rust 工具链即可直接使用 PRQL 编译能力。在此之前,任何使用该绑定的 Elixir 项目都依赖 Rust 工具链与仓库内prqlccrate 的本地编译。
十、小结
本文围绕 PRQL 的 Elixir 绑定,完整覆盖了从依赖安装、PRQL.compile/2基本用法、三大编译选项(target/format/signature_comment)语义、错误处理与compile!/2、编译器逐层 API,到 Rustler NIF 桥接层源码剖析与开发测试流程的全部内容。你可以基于 prqlc/bindings/elixir/lib/prql.ex 的公开 API 直接集成,也可以参考 native/prql/src/lib.rs 理解底层调用链,从而在 Elixir 项目中获得完整的 PRQL 编译能力。
- 后端
【免费下载链接】prql
PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement
相关推荐
PRQL Elixir Bindings:在 Elixir 中编译 PRQL 查询为 SQL 的完整指南
PRQL Elixir Bindings:在 Elixir 中编译 PRQL 查询为 SQL 的完整指南 PRQL(Pipelined Relational Q
后端使用 prql-php:通过 PHP FFI 调用 PRQL 编译器将 PRQL 查询编译为 SQL
使用 prql php:通过 PHP FFI 调用 PRQL 编译器将 PRQL 查询编译为 SQL PRQL(Pipelined Relational Que
后端PRQL PHP 绑定指南:使用 prql-php 通过 FFI 将 PRQL 编译为 SQL
PRQL PHP 绑定指南:使用 prql php 通过 FFI 将 PRQL 编译为 SQL prql php 是 PRQL 编译器在 PHP 生态中的官方绑
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考