Daft SQL 标识符(Identifiers)大小写语义全解析:三种 Identifier Mode 的语法、配置与源码实现
【免费下载链接】DaftHigh-performance data engine for AI and multimodal workloads. Process images, audio, video, and structured data at any scale项目地址: https://gitcode.com/GitHub_Trending/da/Daft
Daft 的 SQL 标识符默认区分大小写,但内置了insensitive(不敏感)、sensitive(敏感)与normalize(归一化)三种解析模式,可通过SessionOptions在创建会话时配置。本指南以 docs/sql/identifiers.md 为主线,结合 daft/session.py、src/daft-session/src/options.rs、src/daft-catalog/src/bindings.rs 与 src/daft-sql/src/planner.rs 等源码,系统讲解 Daft 中标识符的书写语法、三种模式的行为差异、与主流引擎的兼容性对比,以及底层查找与归一化的实现原理,帮助你在跨目录、跨系统场景下正确选择标识符大小写策略。
Daft SQL 标识符的基本概念
在 Daft 中,标识符(Identifier)用于引用 SQL 语句中的各类对象——附加目录(attached catalogs)、附加表(attached tables)、列(columns)等。Daft 的标识符解析遵循以下核心原则:
- 标识符默认区分大小写(case-sensitive)。
- 解析行为受会话(Session)级配置的Identifier Mode控制,可切换为大小写不敏感或大小写归一化模式。
- 无论何种模式,标识符本身都会被保留原始大小写(case-preserved),模式只影响“匹配”的方式:即用户写下的大小写与已注册名称如何对应。
这一设计在 src/daft-session/src/options.rs 中有直接对应:IdentifierMode枚举的三个变体Insensitive、Sensitive(默认)、Normalize,其文档注释明确说明:
Insensitive:对ident AS alias中的ident做大小写不敏感查找,绑定到保留大小写的alias;Sensitive:对ident做大小写敏感查找,绑定到保留大小写的alias(默认值,标有#[default]);Normalize:对ident做大小写敏感查找,但绑定到小写化的alias。
这些模式在解析附加目录、附加表以及通过会话解析的列时统一生效,是理解 Daft SQL 大小写行为的总入口。
一个重要的前提警告
!!! warning "注意"
诸如 Iceberg、Unity 等目录(Catalog)拥有**互不兼容的解析规则**,Daft 无法保证所有目录实现之间的一致性。这意味着标识符大小写语义无法跨目录完全统一。官方建议:在不同系统间协作时,为命名空间和表统一使用小写名称,并搭配identifier_mode = 'normalize',可以获得最一致的体验。
标识符的书写语法
Daft 支持三种标识符书写形式,规则与 SQL 标准一致:
普通标识符(regular identifier)——不加引号:
abc带引号的定界标识符(delimited identifier)——使用双引号:
"abc"限定标识符(qualified identifier)——用点号连接多个部分:
abc.xyz混合限定标识符——各段可以分别选择是否加引号:
a."b".c含特殊字符的定界标识符——双引号内可以容纳 emoji、空格、保留字等:
SELECT "🍺" FROM "🍻"标识符规则
- 标识符可以是不加引号的普通标识符,也可以是双引号包裹的定界标识符。
- 当文本是关键字(keyword)或包含特殊字符时,必须使用双引号包裹。
- 普通标识符必须以字母字符或下划线
'_'开头(字母字符按 Unicode 派生核心属性判定)。 - 普通标识符只能包含字母数字、
'$'与'_'字符。
从源码角度看,定界标识符只接受双引号。在 src/daft-sql/src/planner.rs 的normalize函数中,SQL parser 解析出的每个ObjectNamePart会按其quote_style分派:
Some('"'):双引号定界标识符,原样保留(ident.value.clone()),不经过归一化;None:普通标识符,送入会话的normalizer处理;Some(c):其他引号风格(如反引号、单引号),直接抛出unsupported_sql_err,提示 “Daft only supports delimited identifiers with double-quotes”。
也就是说,Daft 目前只支持双引号作为定界符,SELECT \col`或SELECT 'col'` 这类写法会报错。
三种 Identifier Mode 的行为与兼容性
Daft 提供三种标识符模式,各自的行为与主流引擎兼容性总结如下:
| 模式 | 行为 | 兼容性 |
|---|---|---|
insensitive | 所有标识符按大小写不敏感匹配,名称保留原始大小写 | duckdb、spark、unity |
sensitive | 所有标识符按大小写敏感匹配,名称保留原始大小写 | python、iceberg |
normalize | 不加引号的普通标识符归一化为小写;双引号定界标识符保留原始大小写 | trino、postgres、datafusion |
三种模式的底层差异可以直接映射到 src/daft-session/src/session.rs 的normalizer()实现:
Insensitive与Sensitive模式:normalizer返回恒等函数str::to_string,即不修改任何大小写;Normalize模式:normalizer返回str::to_lowercase,即把普通标识符统一小写化。
模式的底层实现:Bindings 的双重映射
大小写匹配的核心数据结构是 src/daft-catalog/src/bindings.rs 中的Bindings<T>,它维护了两套映射:
bindings: HashMap<String, T>:以原始大小写保存的绑定(lvalue,即对象被注册时的名称);aliases: HashMap<String, HashSet<String>>:以小写化别名(name.to_lowercase(),即alias()函数)为键,指向所有匹配该别名的原始名称。
查找(rvalue,即用户写下的标识符)时依据LookupMode分派(见 bindings.rs):
LookupMode::Insensitive:通过小写别名集合查找到所有候选绑定并全部返回;LookupMode::Sensitive:直接在bindings中按精确大小写查找。
而LookupMode与IdentifierMode的映射关系定义在 options.rs:
match self.identifier_mode { IdentifierMode::Insensitive => LookupMode::Insensitive, IdentifierMode::Sensitive => LookupMode::Sensitive, IdentifierMode::Normalize => LookupMode::Sensitive, // 归一化在 SQL 规划阶段完成 }注意:Normalize模式的查找本身仍是大小写敏感的,小写归一化发生在 SQL 规划阶段——即 planner.rs 的normalize()先把普通标识符转成小写,再以这个已小写的名字去执行敏感查找。正因为如此,normalize模式下双引号定界标识符会被跳过归一化(见上文),从而实现“普通标识符小写化、定界标识符保留大小写”的混合语义。
查找结果唯一性:歧义处理
由于insensitive模式可能命中多个大小写不同的绑定,src/daft-session/src/session.rs 中对目录、Provider、表的查找都做了统一处理:
- 命中 0 个:返回
None(未找到); - 命中 1 个:直接返回该对象;
- 命中多个:抛出
ambiguous_identifier_err!歧义错误。
例如同一命名空间下同时注册了Orders与orders两张表,在insensitive模式下查询orders会因命中两个候选而报歧义错误。一个值得注意的例外是函数名:get_function与get_aggregate_function(见 session.rs)始终使用LookupMode::Insensitive查找,注释明确说明 “Functions always use case-insensitive lookup (SQL convention)”,即 SQL 函数名大小写不敏感是 Daft 的固定约定,不受 Identifier Mode 影响。
配置方式与当前默认值
标识符模式在会话(Session)级别配置,当前版本默认使用case-sensitive(大小写敏感)模式。
!!! note "未来配置"
用于配置标识符模式的 Python API 计划在后续版本提供;目前统一使用默认的大小写敏感模式。也就是说,当前仓库代码(options.rs 中Sensitive带有#[default]属性)中,会话创建时Options派生Default,identifier_mode即为Sensitive;SessionOptions中可配置标识符模式的能力已经为未来版本预留(src/daft-session/src/options.rs 顶部还有 “TODO make env variables” 的注释,暗示未来可能通过环境变量开放配置)。
如果你需要了解 Daft SQL 会话与目录的完整用法,可以参考 会话与目录指南;Session类的 Python 接口定义在 daft/session.py。
多系统协作的实践建议
结合三种模式的语义与兼容性,官方给出了跨系统协作的最佳实践:
- 首选
normalize模式:当需要同时对接 Trino、PostgreSQL、DataFusion 等“小写归一化”生态,或者需要保证多系统行为一致时,identifier_mode = 'normalize'是最稳妥的选择; - 命名空间与表统一小写:在跨多个系统的场景下,为命名空间(namespace)和表(table)统一使用全小写名称,能获得最一致的体验;
- 按生态选模式:如果主要对接 DuckDB / Spark / Unity Catalog,
insensitive更贴近其行为;如果主要使用 Python 数据类库或 Iceberg,默认的sensitive与其语义一致; - 注意目录差异:Iceberg、Unity 等目录有自己的解析规则,与 Daft 会话级模式无法保证完全一致,因此“模式只是起点,目录实现才是最终裁决者”。
同时可以留意:因为insensitive模式下大小写不同的重名对象会触发歧义错误,在开启该模式前应检查是否已存在仅大小写区分的同名对象。
总结
Daft 的标识符体系围绕“大小写保留 + 模式化匹配”设计:sensitive(默认)做精确匹配,insensitive借助小写别名做不敏感匹配,normalize在 SQL 规划阶段把普通标识符统一小写、同时尊重双引号定界标识符。从 bindings.rs 的双映射结构,到 options.rs 的模式默认值,再到 planner.rs 的归一化分派,这套机制贯穿了 Daft SQL 从解析到绑定的完整链路。理解三种模式的差异与适用生态,是你在多目录、多引擎场景下写出行为可预期的 SQL 的关键一步。
【免费下载链接】DaftHigh-performance data engine for AI and multimodal workloads. Process images, audio, video, and structured data at any scale项目地址: https://gitcode.com/GitHub_Trending/da/Daft
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考