#sqlite #datalog #query #csv #querying #modifying

bin+lib owoof

使用 SQLite 存储数据,并使用类似 datalog 的格式进行查询

6 个版本

0.2.0 2022 年 2 月 17 日
0.2.0-pre.42021 年 12 月 29 日
0.1.1 2020 年 9 月 9 日

#5 in #modifying

Apache-2.0 协议

155KB
3.5K SLoC

owoof

github crates.io docs.rs

这是一个受 Datomic 启发的查询构建器,使用类似 datalog 的格式来查询和修改 SQLite 数据库周围的信息。

这是一个个人项目,可能不适用于任何严肃的应用。

这是一个作为 Rust 库实现的。它有文档说明,您可以阅读源代码或可能找到在 docs.rs 上发布的 文档

有两个 Rust 可执行目标。一个提供命令行界面(如下所示),另一个可用于从 CSV 文件导入数据。

CLI

使用 cargo build 编译此软件,使用 --features cli --bin cli

CLI 可用于初始化新的数据库文件、断言/创建、撤回/删除或查询信息。

以下是一些示例

$ echo '[{":db/attribute": ":pet/name"},
         {":pet/name": "Garfield"},
         {":pet/name": "Odie"},
         {":pet/name": "Spot"},
         {":db/attribute": ":person/name"},
         {":db/attribute": ":person/starship"},
         {":person/name": "Jon Arbuckle"},
         {":person/name": "Lieutenant Commander Data",
          ":person/starship": "USS Enterprise (NCC-1701-D)"}]' \
      | owoof assert
[
  "#45e9d8e9-51ea-47e6-8172-fc8179f8fbb7",
  "#4aa95e29-8d45-470b-98a7-ee39aae1b9c9",
  "#2450b9e6-71a4-4311-b93e-3920eebb2c06",
  "#c544251c-a279-4809-b9b6-7d3cd68d2f2c",
  "#19a4cba1-6fc7-4904-ad36-e8502445412f",
  "#f1bf032d-b036-4633-b6f1-78664e44603c",
  "#e7ecd66e-222f-44bc-9932-c778aa26d6ea",
  "#af32cfdb-b0f1-4bbc-830f-1eb83e4380a3"
]

$ echo '[{":db/attribute": ":pet/owner"},
         {":db/id": "#4aa95e29-8d45-470b-98a7-ee39aae1b9c9",
          ":pet/owner": "#e7ecd66e-222f-44bc-9932-c778aa26d6ea"},
         {":db/id": "#2450b9e6-71a4-4311-b93e-3920eebb2c06",
          ":pet/owner": "#e7ecd66e-222f-44bc-9932-c778aa26d6ea"},
         {":db/id": "#c544251c-a279-4809-b9b6-7d3cd68d2f2c",
          ":pet/owner": "#af32cfdb-b0f1-4bbc-830f-1eb83e4380a3"}]' \
      | owoof assert
[
  "#ffc46ae2-1bde-4c08-bfea-09db8241aa2b",
  "#4aa95e29-8d45-470b-98a7-ee39aae1b9c9",
  "#2450b9e6-71a4-4311-b93e-3920eebb2c06",
  "#c544251c-a279-4809-b9b6-7d3cd68d2f2c"
]

$ owoof  '?pet :pet/owner ?owner' \
  --show '?pet :pet/name' \
  --show '?owner :person/name'
[
  [
    { ":pet/name": "Garfield" },
    { ":person/name": "Jon Arbuckle" }
  ],
  [
    { ":pet/name": "Odie" },
    { ":person/name": "Jon Arbuckle" }
  ],
  [
    { ":pet/name": "Spot" },
    { ":person/name": "Lieutenant Commander Data" }
  ]
]

$ owoof '?person :person/starship "USS Enterprise (NCC-1701-D)"' \
        '?pet :pet/owner ?person' \
        '?pet :pet/name ?n'
[
  "Spot"
]

# Or, suppose you know someone's name and their pet's name but don't know the attribute
# that relates them...  (But also this doesn't use indexes well so don't do it.)

$ owoof '?person :person/name "Lieutenant Commander Data"' \
        '?pet ?owner ?person' \
        '?pet :pet/name "Spot"' \
 --show '?owner :db/attribute'
[
  { ":db/attribute": ":pet/owner" }
]

数据集来自 goodbooks-10k

$ owoof '?r :rating/score 1' \
        '?r :rating/book ?b' \
        '?b :book/authors "Dan Brown"' \
 --show '?r :rating/user' \
 --show '?b :book/title' \
 --limit 5
[
  [
    { ":rating/user": 9 },
    { ":book/title": "Angels & Demons  (Robert Langdon, #1)" }
  ],
  [
    { ":rating/user": 58 },
    { ":book/title": "The Da Vinci Code (Robert Langdon, #2)" }
  ],
  [
    { ":rating/user": 65 },
    { ":book/title": "The Da Vinci Code (Robert Langdon, #2)" }
  ],
  [
    { ":rating/user": 80 },
    { ":book/title": "The Da Vinci Code (Robert Langdon, #2)" }
  ],
  [
    { ":rating/user": 89 },
    { ":book/title": "The Da Vinci Code (Robert Langdon, #2)" }
  ]
]

导入 goodbooks-10k

  1. 初始化一个空数据库。

    $ owoof init
    
  2. 导入书籍并输出数据的副本,每个导入的行都有一个 :db/id 列。

    $ owoof-csv --output -- \
          :book/title \
          :book/authors \
          :book/isbn \
          :book/avg-rating\ average_rating \
          < goodbooks-10k/books.csv \
          > /tmp/imported-books
    
  3. 导入评分,我们使用 mlr 将评分与导入的书籍关联起来。

    $ mlr --csv join \
          -f /tmp/imported-books \
          -j book_id \
          < goodbooks-10k/ratings.csv \
      | owoof-csv -- \
          ':rating/book :db/id' \
          ':rating/score rating' \
          ':rating/user user_id'
    
  4. 这需要一些时间(可能是几分钟),然后您可以执行类似以下操作。

    $ owoof '?calvin :book/title "The Complete Calvin and Hobbes"' \
            '?rating :rating/book ?calvin' \
            '?rating :rating/score 1' \
            '?rating :rating/user ?u' \
            '?more-great-takes :rating/user ?u' \
            '?more-great-takes :rating/book ?b' \
            '?more-great-takes :rating/score 5' \
     --show '?b :book/title :book/avg-rating' \
     --asc  '?b :book/avg-rating'
    

    它应该会输出一些答案。

待办事项/注意事项

  • 目前测试不全面。

    应该强制执行架构,因此不要删除正在使用的属性,但我还没有完成验证这项工作,所以可能会有一些惊喜。

  • 性能并不特别可靠。

    版本 0.2 在特定属性上添加了部分索引,这大大提高了搜索性能。然而,没有对值进行索引。某些查询受此影响更大,因此性能并不可靠。

    目前值索引的难点在于 SQLite 的查询计划器在不应选择它的情况下会优先选择它。这不是一个好的索引,而应该是最后的手段--它也非常庞大。

  • 功能还不丰富,约束确保了等价性,尚不支持涉及范围或逻辑运算的约束,而且我还没有测试它在 0.2 中进行的架构更改后的表现如何。

内部待办事项

  • 从连接创建 DontWoof。

  • 选择借用网络有点奇怪。我尝试将其分开,但仍然很奇怪。不确定该怎么办。一个考虑是,将选择查询推送到查询中只是从网络中借用。也许可以放宽这个限制?

  • 测试引用计数?添加清理代码,移除零rc的soup并运行pragma optimize。

  • 也许可以添加一些更新机制,简化撤回和断言?

  • 由于该属性的实体和值对于该属性的元组是相同的,所以:db/id属性有点愚蠢。

    对于对象形式/映射来说很有用;例如{":db/id": ...}。但也许有更巧妙的方式来按某种方式分组?(比如与每个数据库存储的形式关联的某种主键……🤔)

参见

与该软件版本0.1关联的博客文章:https://froghat.ca/blag/dont-woof

许可证

本软件受Apache许可证,版本2.0许可。

依赖项

~23MB
~447K SLoC