SQLite 存档文件

、简介

“SQLite 存档”是一个类似于 ZIP 存档Tarball的文件容器,但基于 SQLite 数据库。

SQLite Archive 是一个普通的 SQLite 数据库文件,包含下表作为其架构的一部分:

CREATE TABLE sqlar(
  name TEXT PRIMARY KEY,  -- name of the file
  mode INT,               -- access permissions
  mtime INT,              -- last modification time
  sz INT,                 -- original file size
  data BLOB               -- compressed content
);

SQLAR 表的每一行都包含一个文件的内容。文件名(相对于存档根目录的完整路径名)在“名称”字段中。“模式”字段是一个整数,它是文件的 unix 风格的访问权限。“mtime”是自 1970 年以来文件的修改时间,以秒为单位。“sz”是文件的原始未压缩大小。“数据”字段包含文件内容。内容通常使用Deflate压缩,但并非总是如此。如果“sz”字段等于“data”字段的大小,则内容未压缩存储。

1.1. 数据库作为容器对象

SQLite Archive 是一个更普遍的想法的例子,即 SQLite 数据库可以表现为一个容器对象,包含许多较小的数据组件。

对于 PostgreSQL 或 Oracle 等客户端/服务器数据库,用户和开发人员倾向于将数据库视为服务或“节点”,而不是对象。这是因为数据库内容分布在服务器上的多个文件中,或者可能分布在服务集群中的多个服务器中。不能指着一个文件甚至一个目录说“这是数据库”。

相比之下,SQLite 将所有内容存储在磁盘上的单个文件中您可以指着那个文件说“这是数据库”。它表现为一个对象。SQLite 数据库文件可以被复制、重命名、作为电子邮件附件发送、作为 POST HTTP 请求的参数传递,或者以其他方式被视为其他数据对象,例如图像、文档或媒体文件。

研究表明,许多应用程序已经使用 SQLite 作为容器对象。例如, Kennedy (与SQLite 开发人员无关)报告说 14% 的 Android 应用程序从不写入他们的 SQLite 数据库。据信,这些应用程序正在从云端下载整个数据库,然后根据需要在本地使用这些信息。换句话说,应用程序使用 SQLite 与其说是数据库,不如说是一种可查询的电汇格式。

1.2. 使用 SQLite 归档的应用程序

Fossil 分布式版本控制系统为用户提供了 将签入下载为 Tarball、ZIP 存档或 SQLite 存档的选项。

2. SQLite 归档的优势

  1. SQLite Archive 是灵活的。ZIP 存档和 Tarball 仅限于存储文件。SQLite Archive 存储文件以及对应用程序似乎有用的任何其他表格和/或关系数据。

  2. SQLite 存档是事务性的。更新是原子的和持久的,即使在更新过程中出现崩溃或断电。读者看到一致且不变的内容版本,即使其他进程正在同时更新档案。

  3. SQLite Archive 可以增量更新。可以添加、删除或替换单个文件,而无需重写整个存档。

  4. 可以使用高级查询语言 (SQL) 查询 SQLite 存档。一些例子:

    • 存档中名称以“.h”或“.cpp”结尾的所有文件的总大小是多少?
    • 压缩率低于 25% 的文件百分比是多少?
    • 存档中有多少个可执行文件?
    无需解压缩或提取任何内容即可回答此类(以及无数其他)问题。
  5. 已经将 SQLite 用于其他目的的应用程序可以使用一个小扩展 ( https://sqlite.org/src/file/ext/misc/sqlar.c ) 轻松添加对 SQLite Archives 的支持,以处理内容的压缩和解压缩。如果存档中的文件未压缩,即使是这个微小的扩展名也可以省略。相比之下,支持 ZIP 存档和/或 Tarball 需要单独的库或大量额外的自定义代码,有时两者都需要。

  6. SQLite 存档可以绕过防火墙强加的审查。例如,某些被视为“危险”的文件类型(例如:DLL)将被 Gmail 以及可能许多其他电子邮件服务和防火墙阻止,即使这些文件被封装在 ZIP 存档或 Tarball 中。但是这些防火墙通常(还)不知道 SQLite Archives,因此可以将内容放入 SQLite Archive 中以逃避审查。

3. SQLite 归档的缺点

  1. SQLite Archive 是一种相对较新的格式。它于 2014 年首次被描述。另一方面,ZIP 档案和 Tarball 已经存在了几十年,并且作为标准格式已经根深蒂固。大多数程序员都知道 ZIP 存档或 Tarball 是什么,但是如果您说“SQLite 存档”,您更有可能得到“什么?”的答复。处理 ZIP 档案和 Tarball 的工具更有可能安装在备用计算机上。

  2. 由于 SQLite 数据库是一种更通用的格式(它的设计不仅仅是简单地存储一堆文件),它不像 ZIP 存档或 Tarball 格式那样紧凑。SQLite 存档通常比等效的 ZIP 存档大 1% 左右。Tarball 被压缩为一个单元,而不是像 SQLite 和 ZIP Archives 那样分别压缩每个文件。由于这些原因,Tarball 往往比 ZIP 或 SQLite 档案小。

    例如,下表显示了 SQLite 3.22.0 源代码树中 1,743 个文件的 SQLite 存档、ZIP 存档和 Tarball 的相对大小:

    SQLite 存档10,754,048
    ZIP 存档(使用 Info-ZIP 3.0)10,662,365
    ZIP 存档(使用zipfile10,390,215
    压缩包 9,781,109
  3. SQLite Archive 仅支持Deflate压缩方法。Tarball 和 ZIP Archive 支持更广泛的压缩方法。

4.从命令行管理 SQLite 存档

创建、更新、列出和提取 SQLite 存档的推荐方法是使用 SQLite版本 3.23.0 (2018-04-02) 或更高版本的sqlite3.exe 命令行 shell 。此 CLI 支持 -A 命令行选项,可轻松管理 SQLite 存档。SQLite版本 3.22.0 (2018-01-22) 的 CLI具有用于管理 SQLite 存档的.archive 命令,但这需要与 shell 交互。

使用以下命令之一列出名为“example.sqlar”的 SQLite 存档中的所有文件:

sqlite3 example.sqlar -At
sqlite3 example.sqlar -Atv

从名为“example.sqlar”的 SQLite 存档中提取所有文件:

sqlite3 example.sqlar -Ax

要创建一个名为“alltxt.sqlar”的新 SQLite 存档,其中包含当前目录中的所有 *.txt 文件:

sqlite3 alltxt.sqlar -Ac *.txt

在现有 SQLite 存档中添加或更新文件:

sqlite3 example.sqlar -Au *.md

有关使用提示和所有选项的摘要,只需为CLI 提供 -A 选项,无需其他参数:

sqlite3 -A

如果文件名参数是 ZIP 存档而不是 SQLite 数据库,则所有这些命令的工作方式相同。

4.1. 其他命令行工具

正如有管理 ZIP 档案的“zip”程序和管理 Tarball 的“tar”程序一样, 存在管理 SQL 档案的“sqlar”程序。“sqlar”程序能够创建一个新的 SQLite 存档,列出现有存档的内容,从存档中添加或删除文件,和/或从存档中提取文件。一个单独的“sqlarfs”程序能够将 SQLite Archive 安装为Fuse 文件系统

5.从应用程序代码管理 SQLite 归档

应用程序可以通过链接到 SQLite 并包含ext/misc/sqlar.c扩展来处理压缩和解压缩, 从而轻松读取或写入 SQLite 存档 。sqlar.c 扩展创建了两个新的 SQL 函数。

sqlar_compress(X)

sqlar_compress(X) 函数尝试使用默认算法压缩字符串或 blob X 的副本,并将结果作为 blob 返回。如果输入 X 是不可压缩的,则返回 X 的副本。在将内容插入 SQLite 存档时使用此例程。

sqlar_uncompress(Y,SZ)

sqlar_uncompress(Y,SZ) 函数将撤消由 sqlar_compress(X) 完成的压缩。Y 参数是压缩后的内容(先前调用 sqlar_compress() 的输出),SZ 是生成 Y 的输入 X 的原始未压缩大小。如果 SZ 小于或等于 Y 的大小,则表示没有发生压缩,因此 sqlar_uncompress(Y,SZ) 返回 Y 的副本。否则,sqlar_uncompress(Y,SZ) 对 Y 运行 Inflate 算法以将其解压缩并将其恢复为原始形式并返回未压缩的内容。从 SQLite 存档中提取内容时使用此例程。

使用上面的两个例程,应用程序可以很容易地将新记录插入到 SQLite 存档中或从中提取现有记录。使用如下代码将新的插入到 SQLite 存档中:

INSERT INTO sqlar(name,mode,mtime,sz,data)
 VALUES ($name,$mode,strftime('%s',$mtime),
         length($content),sqlar_compress($content));

使用如下代码从 SQLite 存档中提取条目:

SELECT name, mode, datetime(mtime,'unixepoch'), sqlar_uncompress(data,sz)
  FROM sqlar
 WHERE ...;

上面的代码适用于一般情况。对于仅存储未压缩或不可压缩内容的 SQLite 存档的特殊情况(例如,这可能出现在仅存储 JPEG、GIF 和/或 PNG 图像的 SQLite 存档中),然后可以将内容插入和提取从数据库中提取,而不使用 sqlar_compress() 和 sqlar_uncompress() 函数,并且不需要 sqlar.c 扩展名。