返回

文章详情

三种将SQLite引入Nix的方法

Hacker News2026年8月21日 16:15

nixpkgs-multiverse的核心部分,在去掉Nix API和CLI后,是一个索引。它是一个从(属性,版本)到修订的映射,以JSON文件形式提供。实际上还有其他一些文件驱动着其他功能,比如统计或“快速模式”,但它们也是JSON文件。 $ ls -lh index/ -rw-r--r--. 1 fmzakari fmzakari 7.5M Aug 19 13:57 history.json -rw-r--r--. 1 fmzakari fmzakari 5.3M Aug 19 13:57 versions.json 截至9cc0209,versions.json的大小为5.3 MiB,history.json为7.5 MiB,涵盖了305,492个包版本,涉及31,904个包和1,534个修订。 Nix API懒加载JSON文件,并通过builtins.fromJSON读取: index = builtins.fromJSON (builtins.readFile ./index/versions.json); 我希望用更多的信息来丰富数据,但是这会带来代价:数据越多,问题越多。项目的目标是最小化下载的Nixpkgs数量。如果我们仅仅是把获取大型Nixpkgs换成获取大型JSON,那并不是一个明确的胜利。现在我们必须谨慎地考虑在JSON文件中存储什么,并思考巧妙的编码方案以使数据小而紧凑。如果不受Nix内置函数的约束,我们本可以利用成熟的技术来高效编码我们的数据集,以允许多种查询访问模式:数据库! 假设我们不受JSON的限制,我们还有其他选择吗?§ 一个查找成本整个文件 为什么大型JSON文件如此麻烦?builtins.fromJSON是渴求的。在Nix中没有懒加载JSON,没有流式解析(即“只给我这个键”)。一旦你接触到结果,你就已经解析了所有5.3 MB并在Nix堆中实物化了所有305,492个值。在多元宇宙的情况下,请求一个包的成本与请求所有包的成本相同。 注意:查找本身不是问题。Nix属性集是一个已排序的数组,因此访问是二分搜索,而不是扫描。成本完全在于JSON解析、分配值和下载大文件。如果我们想对索引进行其它查询,我们必须确保以高效的方式存储答案,以更好地匹配访问模式。我们想要的非常明显。我们想要一种高效编码数据的方法,以及一种声明性定义查询的方法:我们想要SQLite! nixpkgs-multiverse已经导出一个SQLite数据库作为包,以帮助其他人探索这些数据。 $ sqlite3 index.db "SELECT version, rev FROM versions WHERE attr='hello'" 2.10|728 … 0.01s, 4 MB Nix默认无法做到这一点。不幸的是,没有builtins.sqlite,尽管我认为应该有……然而,事实证明,有些我们可以调整的旋钮或可以修补的源代码,尽管每一个都有其警告。 § 一:builtins.exec 我很惊讶我不知道这个内置函数,它自2017年4月的1.11.9版本以来就已存在。它是处理各种用例的终极逃生通道,当你无法用可用的方式完成时。builtins.exec接受一个字符串列表,运行程序,并将其标准输出解析为Nix表达式。它受到一个设置的限制,使得很明显这是不安全的。 $ nix eval --option allow-unsafe-native-code-during-evaluation true --expr 'builtins.exec [ "/bin/sh" "-c" "echo 42" ]' 42 对于集成而言,SQLite完全能够打印Nix语法。在我们制作SQLite直接输出属性集时,我们从不需要中间的序列化格式: let versionsOf = attr : builtins.exec [ " ${ sqlite } /bin/sqlite3" "-noheader" "-separator" "" "./index.db" '' SELECT '{' || group_concat( '"' || version || '" = ' || COALESCE(CAST(rev AS TEXT), 'null') || ';', ' ') || '}' FROM versions WHERE attr = ' ${ attr } '; '' ]; in versionsOf "hello" $ nix eval --impure -f query.nix --option allow-unsafe-native-code-during-evaluation true { "2.10" = 728; "2.12" = 822 ; "2.12.1" = 1369 ; "2.12.2" = 1486; "2.12.3" = null ; "2.7" = 0 ; "2.8" = 13 ; } 警告是每个查询现在都是一个分叉,一个exec,一个SQLite进程图像,以及通过Nix解析器重新解析输出。如果你不打算执行许多查询,考虑到集成的简单性,这个开销可能是可以接受的。 § 二:builtins.importNative 在研究builtins.exec时,我偶然发现了builtins.importNative。它接受一个共享对象的路径和一个符号名称,dlopen它,并调用该符号。它于2014年12月在1.8中引入。共享对象必须实现以下签名: extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v); 我们可以定义一个新原生函数,根据输入返回版本: extern "C" void nix_sqlite_versions (EvalState & state, Value & v) { v.mkPrimOp(new PrimOp { .name = "nix_sqlite_versions" , .args = { "dbPath" , "attr" }, .arity = 2 , .impl = versions , }); } 实现是...

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡