Skip to content

Verbose invariant #14

@moea

Description

@moea

Just using GH for communication, this isn't a specific issue.

Working on simple invariant query examples - the one below is intended to ensure that all writes to user/points are >= the preceding value - but written in such a way that a transaction would be rejected if any of its points values are decreasing.

(ns invariant.datahike-scratch
  (:require [invariant.datahike]
            [invariant.test.util
             :refer [read-resource]]
            [datahike.api  :as d]
            [datahike.core :as dc]))

(def schema
  [#:db{:ident       :user/points
        :valueType   :db.type/bigint
        :cardinality :db.cardinality/one}])

(defn query [q db tx]
  (d/q q
       db
       (dc/db-with db tx)
       (dc/db-with (dc/empty-db) (concat schema tx))
       tx))

(let [uri "datahike:mem:///invariant-test-2"]
  (d/delete-database uri)
  (d/create-database uri)
  (let [conn (d/connect uri)]
    (d/transact conn schema)
    (d/transact conn [{:db/id 1 :user/points 1}
                      {:db/id 2 :user/points 1}])

    (let [tx [[:db/add 1 :user/points 2]
              [:db/add 2 :user/points 0]]]

      (query '[:find ?all-valid .
               :in $before $after $empty+tx $tx
               :where
               [(subquery
                 [:find (count ?entity) .
                  :where
                  [?entity :user/points _]]
                 $empty+tx) ?n-total]
               [(subquery
                 [:find (count ?entity) .
                  :in   $before $after $empty+tx
                  :where
                  [$after ?entity :user/points ?points-after]
                  [(get-else $before ?entity :user/points 0) ?points-before]
                  [(<= ?points-before ?points-after)]]
                 $before $after $empty+tx) ?n-valid]
               [(= ?n-total ?n-valid) ?all-valid]]
             @conn tx))))

This works, but it's long. I'm sure I'm doing something dumb here, and that it'd be possible to write a short, single query which returns something falsy if any of the points are invalid (i.e. lifting the second subquery and flipping the :where to contain (> ?points-before ?points-after)) - and then doing something else that isn't clear to me.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions