Skip to content

schemaless datomic

· xieyuheng

为 basic-lisp 设计一个内置的 database,用来保存 metadata。

模仿 datomic,但是设计成 schemaless 的形式。 通过等一更多语义不同的 operation, 以使得 operation 完全不依赖于 attribute schema:

  • (add e a v)
  • (delete e a v)
  • (delete-attribute e a)
  • (delete-entity e)
  • (put e a v) -- (delete-attribute e a) + (add e a v)
  • (put-unique e a v)

另外,用 id 不是 int,而是特殊的类型。 literal id 用 #1 #2 ... 表示。

db-transact ​

设计 (db-transact) 语法。

可以引入新 id:

scheme
(db-transact (test)
  (put test :test/name 'test-suite/bool-test)
  (put test :test/status 'running))

也可用于找到已有的 id:

scheme
(db-transact (new-user
              (existing-org :org/name "Acme"))
  (put new-user :user/name "Alice")
  (put new-user :user/org existing-org))
scheme
(db-transact (order
              (user :email "alice@example.com")
              (item :sku "ABC-123"))
  (put order :order/user user)
  (put order :order/item item)
  (put order :order/status 'pending)
  (add order :order/log "created"))

more examples:

scheme
;; 创建一个新用户,绑定到 user
(db-transact (user)
  (put user :user/name "Alice")
  (put user :user/email "alice@example.com")
  (put user :user/age 30))

;; 找到邮箱为 alice@example.com 的用户,更新年龄
(db-transact ((user :user/email "alice@example.com"))
  (put user :user/age 31))

(db-transact (order
              (user :user/email "alice@example.com"))
  (put order :order/id "ORD-001")
  (put order :order/user user)
  (put order :order/amount 99.99)
  (add order :order/tag "electronics")   ;; 多值追加
  (add order :order/tag "urgent"))

;; 给用户添加多个标签
(db-transact ((user :user/email "alice@example.com"))
  (add user :user/tag "clojure")
  (add user :user/tag "datomic")
  (add user :user/tag "functional"))

;; 尝试创建新用户,但邮箱必须全局唯一
(db-transact (new-user)
  (put-unique new-user :user/email "bob@example.com")
  (put new-user :user/name "Bob"))
;; 如果 bob@example.com 已存在,事务失败

;; 删除用户的某个标签
(db-transact ((user :user/email "alice@example.com"))
  (delete user :user/tag "clojure"))

;; 清空用户的所有标签(删除整个属性)
(db-transact ((user :user/email "alice@example.com"))
  (delete-attribute user :user/tag))

;; 删除整个实体(浅删除)
(db-transact ((user :user/email "alice@example.com"))
  (delete-entity user))

;; 将用户的年龄替换为 32(不管之前是否有值)
(db-transact ((user :user/email "alice@example.com"))
  (put user :user/age 32))

db-find ​

设计 (db-find (name ...) ...) 语法, 其 body 中所有的 symbol 都被认为是 pattern variable, 只有 name 所生命的 name 是我们所关心的需要返回的 pattern variable:

scheme
(db-find (name email)
  [e :user/name name]
  [e :user/email email])

;; 查询年龄大于 30 的用户
(db-find (name age)
  [e :user/name name]
  [e :user/age age]
  (int-greater? age 30))

;; 查询用户的所有标签(多值)
(db-find (tag)
  [e :user/email "alice@example.com"]
  [e :user/tag tag])

;; 查询用户及其订单的总金额
(db-find (user-name order-total)
  [u :user/name user-name]
  [o :order/user u]
  [o :order/total order-total]
  (> order-total 100))

db-clause ​

scheme
(db-clause (ancestor a d)
  [a :parent/child d])
(db-clause (ancestor a d)
  [a :parent/child x] (ancestor x d))

db-pull ​

scheme
(db-pull id
  :user/name
  (:user/address :address/street
                 (:address/country :country/name)))

{:user/name "Alice"
 :user/address {:address/street "123 Main St"
                :address/country {:country/name "USA"}}}

默认是一个值,多个值的时候出错。 可以用 (first) 来取多个值中的第一个, 或用 (many) 来让返回值变成 list。

scheme
(db-pull id
  :user/name
  (many :user/tag))

{:user/name "Alice"
 :user/tag ["clojure" "datomic" "fun"]}

(db-pull id
  (many :order/items
    :item/name
    (many :item/tags)))

{:order/items [{:item/name "Laptop" :item/tags ["electronics" "expensive"]}
               {:item/name "Mouse"  :item/tags ["electronics"]}]}