Semver 数据库存储实用工具

https://blueprints.launchpad.net/glance/+spec/semver-support

使用各种对象的版本是一个常见问题,已经有许多市场采用的解决方案。 其中一种解决方案是语义化版本控制——一套用于为软件组件和其他对象分配版本号的规则和要求。 可以在 semver.org <http://semver.org> 免费找到 SemVer 的规范

建议根据规范的 2.0.0 版本 [1],向 Glance 添加对语义版本控制概念的支持,以便 Glance 对象(从 Artifacts 开始,但未来可能包括 Images)可以正确地进行版本控制。

问题描述

语义版本控制提供了基于关联的版本比较两个或多个对象的能力。 根据规范 [1],对象首先按其主要版本、然后是次要版本、然后是补丁版本进行比较,并且还有“预发布”版本(alpha、beta、发布候选版本 (RC) 等)的概念,这些版本应始终被认为低于具有相同数值版本的值的“已发布”版本。

例如,1.2.2 < 1.2.3-beta < 1.2.3

如果我们要将版本化的对象存储在目录中(这可能适用于 images、artifacts 和其他实体),那么我们需要能够对大量的实体执行这种语义比较。

因此,比较不仅要在内存中进行,还要在数据库中进行,并且现代 RDBMS 中没有通用的数据类型来存储这种版本信息。 因此,应该引入一种用于存储易于排序的版本标识符的方法。

提议的变更

首先,Glance 必须采用某种实用工具来解析包含版本信息的字符串,验证它们是否符合规范,并正确处理内存中的版本对象。 有许多成熟的库具有此功能,并且无需重新实现它们。 在进行了一些研究之后,建议使用在 pypi [2] 上可用的“semantic_version”库。 此库未包含在 OpenStack 全局需求中,因此已提交补丁集 [3] 以将其添加到那里。

为了能够在数据库中对这些版本对象进行排序,需要将它们转换为某种通用的可比较数据类型。 由于版本信息(主要-次要-补丁部分的固定数字组件)和预发布和元数据标签的任意字母数字字符串序列的性质,建议将它们分别存储为三个数据库字段:一个用于数字部分,另一个用于预发布标签,最后一个用于构建元数据。

可以将三个数字组件(主要、次要和补丁)转换为单个无符号 64 位整数:此数字的前 16 位将分配给存储主要修订版本,接下来的 16 位 - 用于次要修订版本,接下来的 16 位 - 用于补丁修订版本。 剩余的 16 位将用于存储发布类型标志(以确保最终发布比预发布版本具有更高的优先级),并且可以保留用于未来的改进和存储与语义版本控制无关但可能具有其他含义的其他信息(有关更多详细信息,请参阅下面的替代方案部分)。

预发布版本的标签应独立于数字部分存储为常规字符串,因为 - 根据 semver 规范 - 只有当版本的数字部分相同时,它们才应根据常规字母数字比较进行比较。

因此,这两个值——长数字和字符串——可以组合成数据库中的单个复合索引,这将提供高效的排序和过滤具有分配版本对象的可能性。

但是,semver 要求与简单字母数字字符串比较之间存在一个重要的区别:semver 要求按“组件”比较标签(其中“组件”是标签中用点分隔的部分),并且仅包含数字的组件应作为整数而不是 ASCII 字符串进行处理。 例如,版本“1.0.0-alpha.4.foo”应低于“1.0.0-alpha.10.bar”,因为它们的数字组件相等,并且标签具有相同的第一个组件(‘alpha’),但在第二个组件上有所不同(“4”与“10”),并且 4 小于 10。 但是,如果将标签作为字符串数据库字段进行比较,则优先级将错误,因为“alpha.4.foo”在词法上大于“alpha.10.bar”(由于“4”大于“1”)。

为了解决这个问题,建议对这个 semver 实现添加一个约束:将预发布标签中数字组件的最大长度限制为合理的值(例如,6 个字符),并在将其保存到数据库时为这些组件添加额外的前导零。

在这种情况下,来自上述示例的“alpha.4.foo”标签将变为“alpha.000004.foo”,而“alpha.10.foo” - “alpha.000010.foo”。 对这些字符串进行基于 ASCII 的比较将产生与 semver 要求一致的结果。 稍后,当从数据库读取这些值时,可以删除前导零,以便标签再次看起来不错。

这仅适用于预发布标签部分。 构建元数据(由“+”字符分隔的部分)不参与优先级解析,因此既不需要成为数据库索引的一部分,也不需要以任何方式进行预处理。

建议为 SQLAlchemy 创建自定义复合字段,该字段将封装上述逻辑(从语义版本转换为 3 个数据库友好值以及反之),并可用于构建感知版本的模型类。

备选方案

语义版本控制不是定义版本字符串格式的唯一规范。 还有另一个标准——PEP440——它描述了识别 Python 软件分发版本的一种方案 [4]。 它与语义版本控制共享一些共同的功能,但具有不同的且略微复杂的符号。

除了略微不同的语法(它只是将预发布段连接到发布号的右侧,而 semver 使用破折号分隔它们),它还对预发布标签可能包含的内容施加了额外的约束。 在 semver 中,预发布标签可以包含任意的字母数字字符,而在 PEP440 中,它们只能是“a”、“b”或“rc”,后跟一个数字。 这理论上允许将预发布组件作为用于存储发布号的相同 64 位长数据库字段的一部分(例如,发布类型标志占用 2 位,其余 14 位留给预发布构建的编号),但会显着降低预发布版本字段的灵活性。

此外,PEP440 添加了更多额外的实体:它具有开发构建的概念(这是在预发布段之后的一个额外的特殊段),Epochs(它位于构建编号之前),本地版本(实际上与语义版本控制的构建元数据类似,但具有不同的目的,并且也以与语义版本控制的任意预发布标签相同的方式参与优先级解析)等。 此外,与语义版本控制不同,PEP440 对构建编号中数字组件的数量没有限制:因此,它可以是简单的“1”到“1.2.3.4.5.6.7.8.9.10”及更高版本。 这当然提供了更多的灵活性和力量,但可能不容易映射到高效的数据库存储。

更重要的是,PEP440 是 Python 世界的原生标准,但对其外部不为人知,而 Glance Artifacts 的目的是在对象的性质方面尽可能通用。 这意味着 artifacts 的用户不一定局限于 Python 开发人员:他们可能根本不是开发人员。 因此,遵循更简单、更通用的标准似乎更可取。

还有一种标准介于 semver 和 pep440 之间。 它被称为“Linux Compatible Semantic Versioning 3.0.0”,是常规 semver(其 2.0 版本)的一个分支,并在 OpenStack 社区内开发 [5]。 它试图将常规 semver 与 Linux 发行版包的版本结合起来,并使用来自 pep440 的一些概念。

这种符号更容易映射到数据库类型,但是它仍然是相对较小的开发人员社区(在本例中为 OpenStack 开发人员)的本地标准,因此更通用和广泛采用的标准(如 semver)似乎更可取。

但是,我们不限于只拥有一个版本控制符号。 在未来,我们可以添加对其他模式的支持,包括 pep440 或 Linux Compatible Semantic Versioning 的一些子集。 这可以作为 Artifact Repository 路线图的一部分或作为其他活动来实现。 此规范不包含在范围内,并且仅关注 semver 实现。

数据模型影响

无:此规范不涵盖任何实际的数据库更改,它只是描述了一种允许使用 semver 对象并将它们转换为可用于 DB 存储的数据(反之亦然)的实用工具。

REST API 影响

安全影响

通知影响

其他最终用户影响

性能影响

建议的更改不会以任何方式影响现有代码。

其他部署者影响

此规范假定 [3] 已合并,即 semantic_version 库已添加到全局需求中。

开发人员影响

应记录库的使用方法,以便开发人员可以在他们的代码中有效地使用它。

实现

负责人

主要负责人

ativelkov

评审人员

核心评审人

jokke

其他审核员

ivasilevskaya mfedosin travis-tripp icordasc

工作项

该功能的初始实现可以在单个变更集中完成。 但是,似乎最好将此支持添加到 semantic_version 库 [2],并在之后将其从 glance 代码库中删除。 如果库的维护者不同意此功能(或者如果我们决定稍后添加对更多版本控制符号的支持),则可以将此代码转移到一些通用的 OpenStack 库,例如 Oslo。

实施此功能后,我们应该继续进行工作,以添加对其他版本控制模式的支持,例如 pep440、Linux Compatible Semantic Versioning 等。 这些应作为独立的功能添加,并由单独的规范涵盖。

依赖项

测试

应添加一个单元测试,演示数据结构的使用、比较、字符串解析和转换为 DB 类型(长整数)的操作

文档影响

必须更新开发人员指南,以提示开发人员如何在他们的代码中正确使用该库。

参考资料

[1] http://semver.org [2] https://pypi.ac.cn/project/semantic_version/ [3] https://review.openstack.org/#/c/151466/ [4] https://pythonlang.cn/dev/peps/pep-0440/