结构语义

虽然 PackStream 定义了 结构 的外观,但它没有定义其含义。结构的语义与 Bolt 协议版本绑定。

下表列出了所有当前存在的 Bolt 协议版本中 PackStream 指定的结构及其代码和标记字节。

结构

结构名称 代码 标记字节

节点

N

4E

关系

R

52

未绑定关系

r

72

路径

P

50

日期

D

44

时间

T

54

本地时间

t

74

日期时间

I

49

带时区日期时间

i

69

本地日期时间

d

64

持续时间

E

45

二维点

X

58

三维点

Y

59

旧版结构

旧版日期时间

F

46

旧版带时区日期时间

f

66

节点

图数据库中节点的快照。

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_idstart_node_element_idend_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 是一个整数列表,描述如何从 nodesrels 构建路径。nodes 中的第一个节点始终是路径中的第一个节点,并且在 indices 中没有引用。indices 始终具有偶数个条目。indices 中的第 1、3、… 条目引用 rels 中的条目(从 1 开始索引),例如,3 将引用 rels 的第 3 个元素。数字也可以为负数,应将其视为正数等效项,但表示关系的反方向除外。数字永远不会是 0indices 中的第 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 是自午夜以来的纳秒数。

日期时间

捕获日期、时间和时区的瞬间。时区信息使用时区偏移量指定。

此结构是 5.0 版本中的新结构。它替换了 旧版日期时间 并修复了某些极端情况下的错误。如果服务器和客户端协商其使用情况(请参阅 HELLO 消息),则 4.4 版本也允许使用已修复的结构。

标记字节:49

字段数 3

DateTime::Structure(
    seconds::Integer,
    nanoseconds::Integer,
    tz_offset_seconds::Integer,
)
  • secondsnanoseconds 是自 Unix 纪元 以来经过的时间,通常称为 Unix 时间戳。

  • nanoseconds 的数量范围从 0999_999_999(此处以及后面添加 _ 分隔符以提高清晰度)。

  • tz_offset_seconds 指定相对于 UTC 的秒偏移量。

例如,表示为 1970-01-01T02:15:00.000000042+01:00 的时间点的序列化可以如下实现

  • 计算 UTC 时间,即 1970-01-01T01:15:00.000000042ZZ 表示 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

带时区日期时间

捕获日期、时间和时区的瞬间。时区信息使用时区标识符指定。

此结构是 5.0 版本中的新结构。它替换了 旧版带时区日期时间 并修复了某些极端情况下的错误。如果服务器和客户端协商其使用情况(请参阅 HELLO 消息),则 4.4 版本也允许使用已修复的结构。

标记字节:69

字段数 3

DateTimeZoneId::Structure(
    seconds::Integer,
    nanoseconds::Integer,
    tz_id::String,
)
  • secondsnanoseconds 是自 Unix 纪元 以来经过的时间,通常称为 Unix 时间戳。

  • nanoseconds 的数量范围从 0999_999_999(此处以及后面添加 _ 分隔符以提高清晰度)。

  • tz_id 指定 时区数据库 中理解的时区名称。

例如,表示为 1970-01-01T02:15:00.000000042+0100[Europe/Paris] 的时间点的序列化可以如下实现

  • 检索该时间点命名时区的偏移量,此处为 +1 小时,即 3_600 秒。

  • 计算 UTC 时间,即 1970-01-01T01:15:00.000000042ZZ 表示 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]

已知限制

准确性

给定时区名称和时间点的偏移量分辨率受底层 时区数据库 的准确性限制。特别是,1970 年之前 的时区没有那么明确的规范。此外,偏移量分辨率可能在 Bolt 客户端和 Bolt 服务器端同时发生。它们各自依赖于不同的时区数据库。如果这些副本不同步,则可能导致不希望出现的差异。在这种情况下,服务器或客户端可能会

  • 拒绝另一方认为有效的时区名称。

  • 为同一个时区和 DateTimeZoneId 解析不同的偏移量。

本地日期时间

捕获日期和时间但没有时区的瞬间。

标记字节:64

字段数 2

LocalDateTime::Structure(
    seconds::Integer,
    nanoseconds::Integer,
)

其中 seconds 是自 Unix 纪元以来的秒数。

持续时间

时间量。这捕获了两个瞬间之间的时间差。它仅捕获两个瞬间之间的时间量,不捕获开始时间和结束时间。捕获开始时间和结束时间的单元将是 时间间隔,并且不在本提案的范围内。

持续时间可以为负数。

标签字节: 45

字段数 4

Duration::Structure(
    months::Integer,
    days::Integer,
    seconds::Integer,
    nanoseconds::Integer,
)

Point2D

二维空间中单个位置的表示。

标签字节: 58

字段数 3

Point2D::Structure(
    srid::Integer,
    x::Float,
    y::Float,
)

其中srid空间参考系统标识符

Point3D

三维空间中单个位置的表示。

标签字节: 59

字段数 4

Point3D::Structure(
    srid::Integer,
    x::Float,
    y::Float,
    z::Float,
)

其中srid空间参考系统标识符

旧版结构

Legacy DateTime

捕获日期、时间和时区的瞬间。时区信息使用时区偏移量指定。

此结构在5.0版本中被移除,取而代之的是DateTime

标签字节: 46

字段数 3

DateTime::Structure(
    seconds::Integer,
    nanoseconds::Integer,
    tz_offset_seconds::Integer,
)
  • tz_offset_seconds 指定相对于 UTC 的秒偏移量。

  • Unix纪元开始经过的seconds秒数,通常称为Unix时间戳,加上上述偏移量。

  • nanosecondsDateTime最后1秒剩余的纳秒数。nanoseconds的取值范围为0999_999_999(此处添加_分隔符以提高清晰度)。

例如,表示时间点1970-01-01T02:15:00+01:00(以及42纳秒)的序列化可以如下实现

  • 计算UTC时间,即1970-01-01T01:15:00ZZ表示UTC)。

  • 计算该UTC时间与Unix纪元之间的差值,为1小时15分钟,即4500秒。

  • 将+1小时的偏移量,即3600秒,添加到上述差值中,得到81004500 + 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,
)
  • tz_id 指定 时区数据库 中理解的时区名称。

  • Unix纪元开始经过的seconds秒数,通常称为Unix时间戳,加上从命名时区派生的偏移量,并指定时间点。

  • nanosecondsDateTime最后1秒剩余的纳秒数。纳秒数的取值范围为0999_999_999(此处以及后续添加_分隔符以提高清晰度)。

例如,表示时间点1970-01-01T02:15:00+0100[Europe/Paris](以及42纳秒)的序列化可以如下实现

  • 检索该时间点命名时区的偏移量,此处为+1小时,即3600秒。

  • 计算UTC时间,即1970-01-01T01:15:00ZZ表示UTC)。

  • 计算该UTC时间与Unix纪元之间的差值,为1小时15分钟,即4500秒。

  • 将解析出的+1小时偏移量,即3600秒,添加到上述差值中,得到81004500 + 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实例都映射到单个有效时间点。

  1. 在时间偏移期间,例如在给定的日期和时区内从凌晨2点到凌晨3点,凌晨2:30例如不会发生。

  2. 同样,在给定的日期和时区内从凌晨3点到凌晨2点,凌晨2:30会发生两次。

在第一种情况下,指定凌晨2点到凌晨3点之间的时间的DateTimeZoneId不对应于该时区的任何实际时间点,因此无效。

在第二种情况下,凌晨2点到凌晨3点之间的时间段内的所有时间点都出现两次,但偏移量不同。因此,仅使用时区名称不足以解决歧义,还需要时区偏移量。由于DateTimeZoneId不包含时区偏移量,因此这些特定日期时间的解析是未定义的行为。

各版本更改摘要

以下各节列出了结构语义在发生更改的版本中的更改。另请检查Bolt消息中的更改。

5.0版本