DataScript 中的复合元组(Composite Tuples)详解
什么是复合元组?
在 DataScript 数据库中,复合元组是一种特殊类型的属性,它由多个标量值组合而成,在内存中表现为 Clojure 向量。复合元组的主要用途包括:
- 当领域实体具有多属性组成的键时
- 优化需要同时连接同一实体上多个高基数属性的查询
复合元组的工作原理
复合元组完全由 DataScript 自动管理,开发者不需要手动断言或撤销它们。当您断言或撤销构成复合元组的任何一个属性时,DataScript 会自动填充复合值。如果实体的当前值不包含复合元组的所有属性,缺失的属性将被设为 nil。
使用规则
在使用复合元组时,需要遵守以下规则:
- 元组属性必须是基数为一(cardinality one)的属性
- 元组属性不能引用基数为多(cardinality many)的属性
- 元组属性不能引用其他元组属性
- 元组属性默认会被索引
实际示例
假设我们有一个包含属性 :a
、:b
和 :c
的领域模型,并定义了一个复合元组 :a+b+c
:
{:a+b+c {:db/tupleAttrs [:a :b :c]}}
当我们执行如下事务时:
[{:db/id 1, :a "a", :b "b"}]
实体 1 会自动获得复合属性 :a+b+c
:
(d/pull db '[*] 1)
; => {:db/id 1, :a "a", :b "b", :a+b+c ["a" "b" nil]}
如果修改属性,元组值会自动更新:
(d/transact! conn
[{:db/id 1, :a "A", :b "B", :c "c"}])
; => {:db/id 1, :a "A", :b "B", :c "c", :a+b+c ["A" "B" "c"]}
如果移除构成元组的所有属性,元组属性也会被撤销:
(d/transact! conn
[[:db/add 1 :d "d"]
[:db/retract 1 :a]
[:db/retract 1 :b]
[:db/retract 1 :c]])
; => {:db/id 1, :d "d"}
重要限制
不能直接修改元组属性:
(d/transact! conn
[{:db/id 1, :a+b+c ["A" "B" "c"]}])
; => 抛出异常:不能直接修改元组属性
索引特性
元组属性会自动建立索引,支持范围查询:
(d/index-range db :a+b+c ["A" "B" "C"] ["a" "b" "c"])
; => [#db/Datom 1 :a+b+c ["a" "b" "c"]]
唯一性约束
可以将元组属性标记为 :db/unique :db.unique/value
,实现复合键的唯一性约束:
(def conn (d/create-conn {:a+b {:db/tupleAttrs [:a :b]
:db/unique :db.unique/value}}))
这样,虽然单个属性 :a
或 :b
可能不唯一,但它们的组合必须是唯一的:
(d/transact! conn
[{:db/id 5, :a "A", :b "B"}])
; => 抛出唯一性约束异常
作为标识符使用
如果将元组属性标记为 :db/unique :db.unique/identity
,还可以在查找引用中使用:
(def conn (d/create-conn {:a+b {:db/tupleAttrs [:a :b]
:db/unique :db.unique/identity}}))
(d/entity (d/db conn) [:a+b ["a" "b"]])
; => 返回对应实体
总结
复合元组在 DataScript 中是一种强大的特性,可以:
- 自动维护多属性组合值
- 提供高效的复合键查询
- 支持唯一性约束
- 可以作为实体标识符使用
它们的行为与普通属性类似,可以在查找引用、upsert、查询和索引访问等场景中自由使用。理解并合理使用复合元组,可以大大简化某些复杂的数据模型设计。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考