dlt 1.21.2 破坏性变更详解:复合提示(Compound Hints)的优先级与替换规则
【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy 🛠️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt
在 dlt 中,primary_key、merge_key、cluster、partition这几类“作用于多列的列级属性”被统一称为复合提示(compound hints),它们直接决定增量加载去重、merge 写入以及目标端分区/聚簇等关键行为。自 v1.21.2 起(对应 PR #3431),dlt 为复合提示引入了显式优先级与替换规则:直接传入的primary_key/merge_key覆盖列级定义,重新定义提示时不再累加而是整体替换。阅读本文可以帮你:理解这三条破坏性变更的具体语义、掌握在资源与apply_hints两种场景下的正确写法,并结合源码确认底层“替换”是如何实现的,从而安全地从旧版本迁移。
一、变更背景:为什么 v1.21.2 要改复合提示的解析方式
在 v1.21.2 之前,所有复合提示都是以累加(additive)方式合并的:同一列上的旧提示与新提示会被同时保留。这种“只加不减”的行为在跨运行(run-to-run)修改键定义时会带来意外结果——例如把partition从col_1挪到col_2后,两列会同时被标记为分区列,与用户直觉相悖。
v1.21.2 将解析规则调整为三条明确的语义,分别针对“同一定义内多层级冲突”与“跨运行重新定义”两类场景。理解这组规则的前提,是区分提示的两个传入层级:
- 直接(资源级)提示:
primary_key="col_1"、merge_key=("a","b"),直接作用在表上; - 列级提示:
columns={"col_2": {"primary_key": True}},把属性写死在某一列上。
复合提示目前支持的四种属性为:primary_key、merge_key、cluster、partition,详见 Schema 文档 Compound hints 小节。
二、变更一:直接primary_key/merge_key优先于列级提示
当你在同一资源定义里同时给出直接键提示和列级键提示时,直接提示获胜,列级提示被忽略。
import dlt pipeline = dlt.pipeline( pipeline_name="my_pipeline", destination="duckdb", dataset_name="my_data", ) @dlt.resource( name="my_table", primary_key="col_1", # 直接提示 columns={"col_2": {"primary_key": True}}, # 列级提示 ) def my_resource(): yield {"col_1": 1, "col_2": 2} pipeline.run(my_resource) # 结果:只有 col_1 是主键,col_2 的列级 primary_key 被忽略这一优先级规则同样适用于merge_key,以及通过apply_hints同时传入直接键与列级键的情形:
my_resource.apply_hints( merge_key="col_1", columns={"col_2": {"merge_key": True}}, ) # 结果:只有 col_1 是 merge key源码印证:表级提示覆盖列级设置
该优先级在解析阶段由DltResourceHints._merge_keys实现。它按primary_key、merge_key的顺序把表级键提示下放到列,并在下放前先清除列上已存在的同名复合属性:
@staticmethod def _merge_keys(dict_: TResourceHints) -> None: """Applies primary_key and merge_key hints to columns. Table-level hints override column-level settings.""" if "primary_key" in dict_: DltResourceHints._merge_key("primary_key", dict_.pop("primary_key"), dict_) if "merge_key" in dict_: DltResourceHints._merge_key("merge_key", dict_.pop("merge_key"), dict_)见 hints.py。其内部调用_merge_key,后者第一步就执行remove_compound_props(partial["columns"], {hint}),把该属性从所有列上移除,再按表级指定的键重新打上标记——这正是“直接提示优先”的落地机制(见 hints.py)。
三、变更二:重新定义复合提示会整体替换旧配置
这是对增量管道影响最大的一条。若某个资源已经提取过(schema 已落盘),再次以新的复合提示定义它时,新的属性会完全替换旧配置,而不是与之合并。
import dlt pipeline = dlt.pipeline( pipeline_name="my_pipeline1", destination="duckdb", dataset_name="my_data", ) # 第一次:partition 在 col_2 @dlt.resource(name="my_table", columns={"col_2": {"partition": True}}) def my_resource(): yield {"col_1": 1, "col_2": 2} pipeline.run(my_resource) # 第二次:改为 partition 在 col_1 @dlt.resource(name="my_table", columns={"col_1": {"partition": True}}) # type: ignore[no-redef] def my_resource(): yield {"col_1": 1, "col_2": 2} pipeline.run(my_resource) # 只有 col_1 保留 partition,col_2 失去了该属性 assert ( pipeline.default_schema.tables["my_table"]["columns"]["col_1"].get("partition") is True ) assert not pipeline.default_schema.tables["my_table"]["columns"]["col_2"].get("partition")源码印证:merge_compound_props=False的替换路径
“替换而非合并”的核心在 schema 合并工具中通过merge_compound_props开关控制。当该开关为False时,merge_columns会先调用_collect_and_remove_compound_props,把目标列上出现的复合属性全部清掉,再以新值为准写入:
def merge_columns( columns_a, columns_b, merge_compound_props: bool = True, ) -> TTableSchemaColumns: if not merge_compound_props: _collect_and_remove_compound_props(columns_b, columns_a) # ... 逐列合并,columns_b 的非默认值覆盖 columns_a见 utils.py 与 utils.py。在提取器把资源新提示合并进已存储 schema 时,dlt 正是传入了merge_compound_props=False,让新定义具有权威(authoritative)地位:
auth_schema, normalize_identifiers=True, merge_compound_props=False见 extractors.py 与 extractors.py。merge_table的文档字符串也明确说明了两种取值语义:True时复合属性“以累加方式合并”,False时“partial_table中存在的复合属性为权威,table中存在但partial_table中缺失的复合属性会被移除”(见 utils.py)。
注意事项(来自 schema.md 的警告):直接
primary_key/merge_key会默认给对应列设置nullable=False(除非显式指定nullable=True)。当你之后重新定义键提示时,原先属于键的列会保留其既有可空性,而不会自动被重置回nullable=True。此外,把已提取资源的primary_key/merge_key重新定义为空值并不会清空 schema 中已有的键属性——旧键会原样保留。
四、变更三:通过apply_hints传入的直接键提示是权威的
当调用apply_hints时,直接键提示与列级提示遵循不同的合并策略:
- 直接键提示(如
primary_key="col_1")——替换现有键配置,不合并; - 列级复合提示(如
columns={"col_1": {"primary_key": True}})——合并进现有 schema。
# 直接键:替换旧键 my_resource.apply_hints(primary_key="col_1") # 替换之前在 col_2 上的主键 # 列级:与新列键合并共存 my_resource.apply_hints(columns={"col_1": {"primary_key": True}}) # 若 col_2 已是主键,则 col_1 与 col_2 同时成为主键源码印证:apply_hints中键与列的不同分支
在DltResourceHints.apply_hints里,直接键与列级提示走的是完全不同的代码分支,恰好对应“替换”与“合并”两种语义:
if columns is not None: # ... t["columns"] = merge_columns(t["columns"], columns) # 列级:合并 ... if primary_key is not None: t["primary_key"] = primary_key # 直接键:整体覆盖 if merge_key is not None: t["merge_key"] = merge_key # 直接键:整体覆盖见 hints.py。columns分支调用merge_columns做累加合并;而primary_key/merge_key分支则是对提示模板的直接赋值(覆盖),随后在 schema 落盘时由第二节的替换路径执行“先清后写”。
五、迁移建议与适用边界
- 从旧版本升级时:若你的管道依赖“复合提示只加不减”的旧行为(例如跨运行不断叠加分区列),需改为显式声明期望的完整键集合,因为新版本会按最后定义整体替换。
- 同一定义内避免冲突:不要同时写
primary_key="col_1"与columns={"col_2": {"primary_key": True}},直接提示会静默覆盖列级提示。 apply_hints分层使用:需要“增量加列/加属性”时用列级columns;需要“重新指定表主键/merge 键”时用直接键提示。- 适用前提:以上语义与优先级规则适用于 v1.21.2 及以后版本;仓库内 schema.md 中的示例均基于 DuckDB 目标端验证,可复现断言结果。若你仍在使用 v1.21.2 之前的版本,旧行为下复合提示以累加方式合并,迁移前请先核对已落盘的 schema 键属性。
相关变更在后续版本说明中亦有提及,见 release-notes/1.22.md。
【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy 🛠️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考