PRQL Target 与 Version 编译目标与版本机制详解:从 SQL 方言选择到版本控制
2026/9/24 16:58:43 网站建设 项目流程
  • 后端

【免费下载链接】prql

PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement

项目地址:https://gitcode.com/gh_mirrors/pr/prql
点击查看免费下载

导读

PRQL(Pipelined Relational Query Language)是一门面向数据转换的现代语言,它最终会被编译为 SQL。为了让同一份 PRQL 查询能够适配不同的数据库,PRQL 在查询头部提供了prql target:指令用于指定 SQL 方言,同时通过prql version:指令声明查询所使用的语言版本。本文将基于官方文档 web/book/src/project/target.md 并结合 prqlc 编译器源码,系统讲解 Target(编译目标)与 Version(版本)两大机制:如何声明方言、哪些方言受支持、编译器参数与查询头部的优先级关系,以及版本声明如何保护你的查询免受编译器升级的影响。

Target:查询头部的方言声明

PRQL 允许在查询的最顶部声明目标方言(target dialect),编译器会据此生成对应数据库特有的 SQL 风味。声明语法是prql target:sql.<dialect>,位于查询第一行,例如:

prql target:sql.postgres from employees sort age take 10

上述查询会被编译为 PostgreSQL 风格的 SQL。同样的查询,只需把方言换成 SQL Server:

prql target:sql.mssql from employees sort age take 10

编译器就会输出适配 MSSQL 的SELECT TOP 10之类的写法。

从源码看 Target 的解析链路

从源码层面看,target:是查询头部(query header)的一部分,由 prqlc-parser/src/parser/stmt.rs 中的keyword("prql")解析得到,随后在语义分析阶段被解析进 PL AST 的def节点。在 semantic/mod.rs 的测试用例中可以看到,prql target:sql.mssql version:"0"会被解析为:

def: version: ^0 other: target: sql.mssql

targetversion属于同一头部(header)的两个字段,二者语法结构相同,都形如prql <字段>:<值>

编译器的Target类型定义在 prqlc/src/lib.rs:

pub enum Target { /// 当为 None 时,方言从查询头部的 target 字段中提取 Sql(Option<sql::Dialect>), }

TargetFromStr实现(lib.rs)会识别sql.前缀,sql.any被转换为Sql(None),其余字符串则尝试匹配Dialect枚举,匹配失败会抛出Reason::NotFound错误(命名空间为target)。这也意味着:方言名拼写错误会在编译期直接报错,而不是静默地回退到通用 SQL。

方言支持矩阵

根据 target.md 的说明,方言分为"受支持"(Supported)与"不受支持"(Unsupported)两个等级,这一分级在 prqlc/src/sql/dialect.rs 的Dialect::support_level()方法中有直接对应。

受支持的方言(Supported)

受支持方言尽可能支持全部 PRQL 语言特性,每次提交都会运行测试,项目组会尽力修复 bug。当前列表为:

  • sql.clickhouse
  • sql.duckdb
  • sql.generic—— 文档脚注特别说明:虽然不存在一个名为 "generic" 的真实数据库来测试它,但它仍被视作受支持方言。它对应 dialect.rs 中与Ansi共用一个GenericDialecthandler 的Generic枚举项,也是Dialect#[default]项。
  • sql.mysql
  • sql.postgres
  • sql.redshift
  • sql.sqlite

Dialect枚举(dialect.rs)可以看到,Generic被标注为默认值,这与Options::default()target: Target::Sql(None)(lib.rs)一致——当查询头部和编译器参数都没有指定方言时,PRQL 默认走通用 SQL 路径。

不受支持的方言(Unsupported)

不受支持方言在编译器中已有实现,但测试覆盖很少甚至没有,部分功能可能存在缺口,项目欢迎社区贡献补齐测试或新增方言:

  • sql.mssql
  • sql.ansi
  • sql.bigquery
  • sql.snowflake
  • sql.oracle—— 非常早期;目前只保证标识符加引号以适应 Oracle 的大小写折叠规则,将take编译为OFFSET ... FETCH FIRST而非LIMITtext.contains使用||而非CONCAT。其余大多数语言特性回退到通用 SQL,在 Oracle 上可能无法正确执行。

上述源码注释中关于 Oracle 的描述与文档一致:OracleDialect实现了use_fetch(用OFFSET n ROWS FETCH FIRST n ROWS ONLY做行数限制)、IdentQuotingStyle::AlwaysQuoted(始终加引号)以及table_alias_uses_as() -> false(Oracle 的表别名不允许AS关键字),见 dialect.rs。

方言底层实现:DialectHandler 特性

方言差异在编译器中通过DialectHandlertrait 来抽象(dialect.rs)。Dialect枚举通过handler()方法(dialect.rs)分派到具体的 handler 结构体,例如MsSqlDialectPostgresDialectOracleDialect等,AnsiGeneric共用GenericDialect

该 trait 暴露了一系列可覆写的行为钩子,决定了每个方言的 SQL 生成差异,例如:

钩子方法默认行为典型覆写
use_fetch()false(使用LIMITMSSQL、Oracle 返回true,改用FETCH子句
ident_quote()"(双引号)MySQL、ClickHouse 用反引号`
ident_quoting_style()条件引号Snowflake、Oracle 用AlwaysQuoted(始终引号)
has_concat_function()true(使用CONCATRedshift、SQLite 返回false,回退到||运算符
set_ops_distinct()trueSQLite、MSSQL、Snowflake 返回false
except_all()/intersect_all()trueSQLite、MSSQL、DuckDB 返回false
supports_distinct_on()falsePostgres、ClickHouse、DuckDB 返回true
stars_in_group()trueSQLite 返回false
translate_chrono_item()默认报错(日期格式化需要方言支持)Postgres/MySQL/MSSQL/ClickHouse/DuckDB/BigQuery/Redshift 各自实现
table_alias_uses_as()trueOracle 返回false
requires_order_by_in_window_function()falseSnowflake 返回trueROW_NUMBER()等排名函数要求 ORDER BY)

文件头注释(dialect.rs)说明了设计原则:优先面向通用方言生成,只有通用方言不支持(如 MSSQL 没有LIMIT)或方言专属实现性能更优时才引入方言差异;相应地,生成的 SQL 可能偏冗长,但换来了更简单的翻译器。例如chrono_item_to_strftime(dialect.rs)负责把 chrono 日期格式串转回 strftime 表示,Postgres 的translate_chrono_item(dialect.rs)则把 PRQL 的%Y%m等规格映射到 PostgreSQL 的YYYYMM等格式,并处理字面量转义。每个方言 handler 的具体差异都可以在上述源码中找到对应实现,是理解"为什么同一 PRQL 在不同数据库输出不同 SQL"的最佳入口。

Target 优先级:编译器参数 > 查询头部

一个查询的编译目标由两个来源决定:查询头部的prql target:声明,以及传给编译器的 target 参数。编译器参数优先于查询头部声明

例如下面的 shell 命令,查询内部声明了sql.generic,但prqlc compile命令通过--target选项指定了sql.duckdb,此时sql.duckdb胜出,输出的 SQL 基于 DuckDB 方言:

echo 'prql target:sql.generic from foo' | prqlc compile --target sql.duckdb

在 prqlc/src/cli/mod.rs 中,compile子命令的--target参数定义如下:

/// Target to compile to #[arg(short, long, default_value = "sql.any", env = "PRQLC_TARGET")] target: String,

默认值正是文档中提到的特殊 targetsql.any,同时支持通过环境变量PRQLC_TARGET注入(对 CI 场景很实用)。

sql.any:让查询头部说了算

如果希望编译器尊重查询头部声明的方言,就需要在编译器选项中显式传入特殊值sql.any

echo 'prql target:sql.generic from foo' | prqlc compile --target sql.any

从 lib.rs 的FromStr实现可以看到,sql.any会被解析为Target::Sql(None),而Sql(None)的语义正是"方言从查询头部提取"。换言之:sql.any不是某种"任意数据库",而是"由查询决定"的占位符。由于--target的默认值就是sql.any,所以默认情况下查询头部总是生效的;只有当你显式传入某个具体方言时,它才会覆盖查询头部。

Target::names()(lib.rs)会生成sql.any加上所有sql.<dialect>组成的列表,CLI 的prqlc list-targets命令即用于展示所有可用编译目标名(见 cli/mod.rs),可以通过它确认当前版本支持的全部方言。

各语言绑定中的 Target 设置

除 CLI 外,各语言绑定也把 Target 作为公开 API 暴露。例如 Python 绑定 prqlc/bindings/prqlc-python/src/lib.rs 与 C 绑定 prqlc/bindings/prqlc-c/src/lib.rs 中都出现sql.any/ 方言字符串的处理;Rust 侧则可以直接用类型安全的枚举,参考 lib.rs 的 doctest:

use prqlc::{compile, Options, Target, sql::Dialect}; let prql = "from employees | select {name,age}"; let opts = Options::default() .with_target(Target::Sql(Some(Dialect::SQLite))) .with_signature_comment(false) .with_format(false); let sql = compile(&prql, &opts).unwrap();

Options的默认值为format: truetarget: Target::Sql(None)signature_comment: true(lib.rs),with_target链式方法可覆盖目标方言。

Version:查询版本声明

PRQL 允许在查询头部声明语言版本:

prql version:"0.13.14" from employees

这个版本声明有两个作用,其中第一个已经实现,第二个是 PRQL 1.0 的门槛特性:

  1. 编译器版本下限检查(已实现):如果编译器版本比查询声明的版本更旧,编译器会直接报错。这避免了"查询用了语言新特性、而编译器尚未升级"时产生的令人困惑的错误——与其等编译结果莫名其妙地出错,不如一开始就明确提示版本不匹配。
  2. 按主版本编译(规划中,未实现):编译器将为查询的主版本编译。这允许语言持续演进而不破坏存量查询,也无需用户同时安装多个版本的编译器。这是 PRQL 1.0 的 gating 特性。

关于版本检查,从语义分析层的测试可以印证:在 semantic/mod.rs 中,version:foo(非合法版本字符串)和version:"25"这类会被拒绝的用例,以及未知方言target:sql.yah的用例,都会在parse_resolve_and_lower阶段直接返回错误,说明 header 中的非法值与非法方言一样会触发编译期报错。

在 PRQL 中查询编译器版本

当前正在使用的编译器版本,可以通过 PRQL 标准库中的特殊函数std.prql.version获取。将函数结果放进一个数组字面量即可查询:

[{version = prql.version}]

这个用法在集成测试 prqlc/tests/integration/sql.rs 中有直接验证,测试还展示了它的派生用法derive y = std.prql.version。注意std.prql.version返回的是编译器版本,与查询头部声明的version:是两回事:前者是运行时读取当前编译器,后者是查询作者声明的"最低可用版本"契约。

实战:完整使用组合

综合以上机制,一个典型的跨数据库工作流是:在查询头部写清楚它依赖的方言与版本(作为查询的"元数据"),然后在编译时通过 CLI 参数覆盖目标数据库。例如:

# 查询头部声明 generic,但实际编译给 DuckDB 执行 cat <<'EOF' | prqlc compile --target sql.duckdb prql target:sql.generic version:"0.13.14" from employees sort age take 10 EOF # 列出当前编译器支持的所有 target prqlc list-targets # 通过环境变量指定 target(适合脚本/CI) PRQLC_TARGET=sql.postgres prqlc compile query.prql

注意事项:

  • --target未指定(默认sql.any),以查询头部的prql target:为准;若指定了具体方言,则以参数为准。
  • 方言名必须与 dialect.rs 中的Dialect枚举(ansibigqueryclickhouseduckdbgenericmssqlmysqloraclepostgresredshiftsqlitesnowflake)一致,拼写错误会在编译期报错。
  • 使用std.prql.version可以读取当前编译器版本,用于诊断版本不匹配问题。
  • 生成 SQL 时会附带一行包含 PRQL 编译器版本信息的签名注释(Options::signature_comment默认为true,可用--hide-signature-comment关闭,见 cli/mod.rs)。

相关资源

  • 本文主题的官方文档:web/book/src/project/target.md
  • 方言枚举与DialectHandler特性定义:prqlc/prqlc/src/sql/dialect.rs
  • TargetOptionscompile入口:prqlc/prqlc/src/lib.rs
  • CLIcompile子命令与--target参数:prqlc/prqlc/src/cli/mod.rs
  • 查询头部解析(prql关键字):prqlc/prqlc-parser/src/parser/stmt.rs
  • Header 解析与版本校验测试:prqlc/prqlc/src/semantic/mod.rs
  • std.prql.version使用示例:prqlc/prqlc/tests/integration/sql.rs
  • 各语言绑定对 target 的封装:prqlc/bindings
  • 后端

【免费下载链接】prql

PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement

项目地址:https://gitcode.com/gh_mirrors/pr/prql
点击查看免费下载
上一篇:网盘下载总是慢半拍?聊聊直链下载助手这个免费小工具
下一篇:D3KeyHelper暗黑3技能连点器零基础实战指南:三十分钟,让我的法师自己转起来

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询