结构语义
虽然 PackStream 定义了 结构 的外观,但它没有定义其含义。结构的语义与 Bolt 协议版本绑定。
下表列出了所有当前存在的 Bolt 协议版本中 PackStream 指定的结构及其代码和标记字节。
结构
结构名称 | 代码 | 标记字节 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
旧版结构 |
||
|
|
|
|
|
节点
图数据库中节点的快照。
element_id
字段在 5.0 版本中添加,在早期版本中不存在。
标记字节:4E
字段数:4 (5.0 版本之前为 3)
Node::Structure( id::Integer, labels::List<String>, properties::Dictionary, element_id::String, )
Node( id = 3, labels = ["Example", "Node"], properties = {"name": "example"}, element_id = "abc123", )
B4 4E ...
关系
图数据库中关系的快照。
element_id
、start_node_element_id
和 end_node_element_id
字段在 5.0 版本中添加,在早期版本中不存在。
标记字节:52
字段数:8 (5.0 版本之前为 5)
Relationship::Structure( id::Integer, startNodeId::Integer, endNodeId::Integer, type::String, properties::Dictionary, element_id::String, start_node_element_id::String, end_node_element_id::String, )
Relationship( id = 11, startNodeId = 2, endNodeId = 3, type = "KNOWS", properties = {"name": "example"}, element_id = "abc123", start_node_element_id = "def456", end_node_element_id = "ghi789", )
B8 52 ...
未绑定关系
没有起点或终点节点 ID 的关系。它在内部用于路径序列化。
element_id
字段在 5.0 版本中添加,在早期版本中不存在。
标记字节:72
字段数:4 (5.0 版本之前为 3)
UnboundRelationship::Structure( id::Integer, type::String, properties::Dictionary, element_id::String, )
UnboundRelationship( id = 17, type = "KNOWS", properties = {"name": "example"}, element_id = "foo" )
B4 72 ...
路径
节点和关系的交替序列。
标记字节:50
字段数 3
Path::Structure( nodes::List<Node>, rels::List<UnboundRelationship>, indices::List<Integer>, )
其中 nodes
字段包含节点列表,rels
字段是未绑定关系的列表。indices
是一个整数列表,描述如何从 nodes
和 rels
构建路径。nodes
中的第一个节点始终是路径中的第一个节点,并且在 indices
中没有引用。indices
始终具有偶数个条目。indices
中的第 1、3、… 条目引用 rels
中的条目(从 1 开始索引),例如,3
将引用 rels
的第 3 个元素。数字也可以为负数,应将其视为正数等效项,但表示关系的反方向除外。数字永远不会是 0
。indices
中的第 2、4、… 条目引用 nodes
中的条目(从 0 开始索引),例如,3
将引用 nodes
的第 2 个元素。数字始终 ≥ 0
。
<节点>
和 <未绑定关系>
的简化表示法)Path::Structure( nodes: [Node::Structure(42, ...), Node::Structure(69, ...), Node::Structure(1, ...)], rels: [UnboundRelationship::Structure(1000, ...), UnboundRelationship::Structure(1001, ...)], indices: [1, 1, 1, 0, -2, 2],
这表示路径 (42)-[1000]→(69)-[1000]→(42)←[1001]-(1)
,其中 (n)
表示 id 为 n
的节点,[n]
表示 id 为 n
的关系(→
或 ←
表示每个关系的方向)。
日期
ISO-8601 日历系统中没有时区的日期,例如 2007-12-03
。
标记字节:44
字段数 1
Date::Structure( days::Integer, )
其中 days
是自 Unix 纪元 以来经过的天数。例如,0
表示 1970-01-01,而 1
表示 1970-01-02。
时间
捕获一天中的时间和时区的瞬间,但不捕获日期。
标记字节:54
字段数 2
Time::Structure( nanoseconds::Integer, tz_offset_seconds::Integer, )
其中 nanoseconds
是自午夜以来的纳秒数(此时间不是 UTC),tz_offset_seconds
是相对于 UTC 的秒偏移量。
本地时间
捕获一天中的时间,但既不捕获日期也不捕获时区的瞬间。
标记字节:74
字段数 1
LocalTime::Structure( nanoseconds::Integer, )
其中 nanoseconds
是自午夜以来的纳秒数。
日期时间
捕获日期、时间和时区的瞬间。时区信息使用时区偏移量指定。
标记字节:49
字段数 3
DateTime::Structure( seconds::Integer, nanoseconds::Integer, tz_offset_seconds::Integer, )
-
seconds
和nanoseconds
是自 Unix 纪元 以来经过的时间,通常称为 Unix 时间戳。 -
nanoseconds
的数量范围从0
到999_999_999
(此处以及后面添加_
分隔符以提高清晰度)。 -
tz_offset_seconds
指定相对于 UTC 的秒偏移量。
例如,表示为 1970-01-01T02:15:00.000000042+01:00
的时间点的序列化可以如下实现
-
计算 UTC 时间,即
1970-01-01T01:15:00.000000042Z
(Z
表示 UTC)。 -
计算该 UTC 时间与 Unix 纪元之间的差值,即 1 小时 15 分,即
4_500
秒。
因此,生成的 DateTime
实例如下所示
{ seconds: 4500 nanoseconds: 42, tz_offset_seconds: 3600 }
此类 DateTime
结构的反序列化预期会反向进行
-
基于该 Unix 时间戳(
4500
秒和42
纳秒)实例化DateTime
的惯用等效项,得到1970-01-01T01:15:00.000000042Z
-
将生成的 UTC
DateTime
定位到指定偏移量的时区,得到1970-01-01T02:15:00.000000042+0100
带时区日期时间
捕获日期、时间和时区的瞬间。时区信息使用时区标识符指定。
标记字节:69
字段数 3
DateTimeZoneId::Structure( seconds::Integer, nanoseconds::Integer, tz_id::String, )
例如,表示为 1970-01-01T02:15:00.000000042+0100[Europe/Paris]
的时间点的序列化可以如下实现
-
检索该时间点命名时区的偏移量,此处为 +1 小时,即
3_600
秒。 -
计算 UTC 时间,即
1970-01-01T01:15:00.000000042Z
(Z
表示 UTC)。 -
计算该 UTC 时间与 Unix 纪元之间的差值,即 1 小时 15 分,即
4_500
秒。
因此,生成的 DateTime
实例如下所示
{ seconds: 4500 nanoseconds: 42, tz_id: "Europe/Paris" }
此类 DateTime
结构的反序列化会反向进行
-
基于该 Unix 时间戳(
4500
秒和42
纳秒)实例化DateTime
的惯用等效项,得到1970-01-01T01:15:00.000000042Z
-
将生成的 UTC
DateTime
定位到tz_id
指定的时区,得到1970-01-01T02:15:00.000000042+0100[Europe/Paris]
本地日期时间
捕获日期和时间但没有时区的瞬间。
标记字节:64
字段数 2
LocalDateTime::Structure( seconds::Integer, nanoseconds::Integer, )
其中 seconds
是自 Unix 纪元以来的秒数。
持续时间
时间量。这捕获了两个瞬间之间的时间差。它仅捕获两个瞬间之间的时间量,不捕获开始时间和结束时间。捕获开始时间和结束时间的单元将是 时间间隔
,并且不在本提案的范围内。
持续时间可以为负数。
标签字节: 45
字段数 4
Duration::Structure( months::Integer, days::Integer, seconds::Integer, nanoseconds::Integer, )
旧版结构
Legacy DateTime
捕获日期、时间和时区的瞬间。时区信息使用时区偏移量指定。
此结构在5.0版本中被移除,取而代之的是DateTime
。
标签字节: 46
字段数 3
DateTime::Structure( seconds::Integer, nanoseconds::Integer, tz_offset_seconds::Integer, )
-
tz_offset_seconds
指定相对于 UTC 的秒偏移量。 -
自Unix纪元开始经过的
seconds
秒数,通常称为Unix时间戳,加上上述偏移量。 -
nanoseconds
是DateTime
最后1秒剩余的纳秒数。nanoseconds
的取值范围为0
到999_999_999
(此处添加_
分隔符以提高清晰度)。
例如,表示时间点1970-01-01T02:15:00+01:00
(以及42
纳秒)的序列化可以如下实现
-
计算UTC时间,即
1970-01-01T01:15:00Z
(Z
表示UTC)。 -
计算该UTC时间与Unix纪元之间的差值,为1小时15分钟,即
4500
秒。 -
将+1小时的偏移量,即
3600
秒,添加到上述差值中,得到8100
(4500
+3600
)。
因此,生成的 DateTime
实例如下所示
{ seconds: 8100 nanoseconds: 42, tz_offset_seconds: 3600 }
此类 DateTime
结构的反序列化预期会反向进行
-
从
seconds
字段中移除偏移量,此处得到8100
-
基于该Unix时间戳实例化
DateTime
的等效表示,得到1970-01-01T01:15:00Z
-
将得到的UTC
DateTime
本地化为指定偏移量的时区,得到1970-01-01T02:15:00+0100
Legacy DateTimeZoneId
捕获日期、时间和时区的瞬间。时区信息使用时区标识符指定。
此结构在5.0版本中被移除,取而代之的是DateTimeZoneId
。
标签字节: 66
字段数 3
DateTimeZoneId::Structure( seconds::Integer, nanoseconds::Integer, tz_id::String, )
例如,表示时间点1970-01-01T02:15:00+0100[Europe/Paris]
(以及42
纳秒)的序列化可以如下实现
-
检索该时间点命名时区的偏移量,此处为+1小时,即
3600
秒。 -
计算UTC时间,即
1970-01-01T01:15:00Z
(Z
表示UTC)。 -
计算该UTC时间与Unix纪元之间的差值,为1小时15分钟,即
4500
秒。 -
将解析出的+1小时偏移量,即
3600
秒,添加到上述差值中,得到8100
(4500
+3600
)。
因此,生成的 DateTime
实例如下所示
{ seconds: 8100 nanoseconds: 42, tz_id: "Europe/Paris" }
此类DateTime
结构的反序列化如下
-
实例化
DateTime
的等效表示,假设秒数表示Unix时间戳,得到1970-01-01T02:15:00Z
。 -
设置结果实例的时区,而不更改日期/时间组件,得到
1970-01-01T02:15:00+0100[Europe/Paris]
(这可能导致歧义,有关更多信息,请参阅下面的“已知限制”部分)。
已知限制
精度
给定时区名称和时间点的偏移量分辨率受底层 时区数据库 的准确性限制。特别是,1970 年之前 的时区没有那么明确的规范。此外,偏移量分辨率可能在 Bolt 客户端和 Bolt 服务器端同时发生。它们各自依赖于不同的时区数据库。如果这些副本不同步,则可能导致不希望出现的差异。在这种情况下,服务器或客户端可能会
-
拒绝另一方认为有效的时区名称。
-
为同一个时区和
DateTimeZoneId
解析不同的偏移量。
时间偏移
注意:这些问题已通过在5.0版本中引入DateTimeZoneId
得到解决。
并非所有DateTimeZoneId
实例都映射到单个有效时间点。
-
在时间偏移期间,例如在给定的日期和时区内从凌晨2点到凌晨3点,凌晨2:30例如不会发生。
-
同样,在给定的日期和时区内从凌晨3点到凌晨2点,凌晨2:30会发生两次。
在第一种情况下,指定凌晨2点到凌晨3点之间的时间的DateTimeZoneId
不对应于该时区的任何实际时间点,因此无效。
在第二种情况下,凌晨2点到凌晨3点之间的时间段内的所有时间点都出现两次,但偏移量不同。因此,仅使用时区名称不足以解决歧义,还需要时区偏移量。由于DateTimeZoneId
不包含时区偏移量,因此这些特定日期时间的解析是未定义的行为。
各版本更改摘要
以下各节列出了结构语义在发生更改的版本中的更改。另请检查Bolt消息中的更改。
5.0版本
-
element_id
字段已添加到Node
中。 -
element_id
、start_node_element_id
和end_node_element_id
字段已添加到Relationship
中。 -
element_id
字段已添加到UnboundRelationship
中。 -
分别用
DateTime
和DateTimeZoneId
替换了Legacy DateTime
和Legacy DateTimeZoneId
。