Kaynağa Gözat

debug: db validate

Tienson Qin 2 yıl önce
ebeveyn
işleme
5445a83b5c

+ 0 - 18
src/main/frontend/db/model.cljs

@@ -511,24 +511,6 @@ independent of format as format specific heading characters are stripped"
                                   (not= parent-id (:db/id node)))
                          node)) lefts))))
 
-(defn- get-next-outdented-block
-  "Get the next outdented block of the block that has the `id`.
-  e.g.
-  - a
-    - b
-      - c
-  - d
-
-  The next outdented block of `c` is `d`."
-  [db id]
-  (when-let [block (db-utils/entity db id)]
-    (let [parent (:block/parent block)]
-      (if-let [parent-sibling (get-by-parent-&-left db
-                                                    (:db/id (:block/parent parent))
-                                                    (:db/id parent))]
-        parent-sibling
-        (get-next-outdented-block db (:db/id parent))))))
-
 (defn top-block?
   [block]
   (= (:db/id (:block/parent block))

+ 50 - 0
src/main/frontend/db/validate.cljs

@@ -0,0 +1,50 @@
+(ns frontend.db.validate
+  "DB validation.
+  For pages:
+  1. Each block should has a unique [:block/parent :block/left] position.
+  2. For any block, its children should be connected by :block/left."
+  (:require [datascript.core :as d]
+            [medley.core :as medley]))
+
+(defn- broken-chain?
+  [page parent-left->eid]
+  (let [parents (->> (:block/_page page)
+                     (filter #(seq (:block/_parent %)))
+                     (cons page))]
+    (some
+     (fn [parent]
+       (let [parent-id (:db/id parent)
+             blocks (:block/_parent parent)]
+         (when (seq blocks)
+           (when-let [start (parent-left->eid [parent-id parent-id])]
+             (let [chain (loop [current start
+                                chain [start]]
+                           (let [next (parent-left->eid [parent-id current])]
+                             (if next
+                               (recur next (conj chain next))
+                               chain)))]
+               (when (not= (count chain) (count blocks))
+                 {:parent parent
+                  :chain chain
+                  :broken-blocks (remove (set chain) (map :db/id blocks))
+                  :blocks blocks}))))))
+     parents)))
+
+(defn broken-page?
+  "Whether `page` is broken."
+  [db page-id]
+  (prn :debug " Validate page: " page-id)
+  (let [parent-left-f (fn [b]
+                        [(get-in b [:block/parent :db/id])
+                         (get-in b [:block/left :db/id])])
+        page (d/entity db page-id)
+        blocks (:block/_page page)
+        parent-left->es (group-by parent-left-f blocks)
+        conflicted (filter #(> (count (second %)) 1) parent-left->es)]
+    (if (seq conflicted)
+      [:conflict-parent-left conflicted]
+
+      (let [parent-left->eid (medley/map-vals (fn [c] (:db/id (first c))) parent-left->es)]
+        (if-let [result (broken-chain? page parent-left->eid)]
+          [:broken-chain result]
+          false)))))

+ 40 - 14
src/main/frontend/modules/outliner/datascript.cljs

@@ -1,19 +1,20 @@
 (ns frontend.modules.outliner.datascript
   (:require [datascript.core :as d]
-                  [frontend.db.conn :as conn]
-                  [frontend.db :as db]
-                  [frontend.db.react :as react]
-                  [frontend.modules.outliner.pipeline :as pipelines]
-                  [frontend.modules.editor.undo-redo :as undo-redo]
-                  [frontend.state :as state]
-                  [frontend.config :as config]
-                  [logseq.graph-parser.util :as gp-util]
-                  [lambdaisland.glogi :as log]
-                  [frontend.search :as search]
-                  [clojure.string :as string]
-                  [frontend.util :as util]
-                  [frontend.util.property-edit :as property-edit]
-                  [logseq.graph-parser.util.block-ref :as block-ref]))
+            [frontend.db.conn :as conn]
+            [frontend.db :as db]
+            [frontend.db.react :as react]
+            [frontend.modules.outliner.pipeline :as pipelines]
+            [frontend.modules.editor.undo-redo :as undo-redo]
+            [frontend.state :as state]
+            [frontend.config :as config]
+            [logseq.graph-parser.util :as gp-util]
+            [lambdaisland.glogi :as log]
+            [frontend.search :as search]
+            [clojure.string :as string]
+            [frontend.util :as util]
+            [frontend.util.property-edit :as property-edit]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [frontend.db.validate :as db-validate]))
 
 (defn new-outliner-txs-state [] (atom []))
 
@@ -113,6 +114,29 @@
       (concat txs retracted-tx'))
     txs))
 
+(defn validate-db!
+  [{:keys [db-before db-after tx-data]}]
+  (let [changed-pages (->> (filter (fn [d] (contains? #{:block/left :block/parent} (:a d))) tx-data)
+                           (map :e)
+                           distinct
+                           (map (fn [id]
+                                  (-> (or (d/entity db-after id)
+                                          (d/entity db-before id))
+                                      :block/page
+                                      :db/id)))
+                           (remove nil?)
+                           (distinct))]
+    (reduce
+     (fn [_ page-id]
+       (if-let [result (db-validate/broken-page? db-after page-id)]
+         (do
+           ;; revert db changes
+           (assert (false? result) (str "Broken page: " result))
+           (reduced false))
+         true))
+     true
+     changed-pages)))
+
 (defn transact!
   [txs opts before-editor-cursor]
   (let [repo (state/get-current-repo)
@@ -148,6 +172,8 @@
               conn (conn/get-db repo false)
               rs (d/transact! conn txs (assoc opts :outliner/transact? true))
               tx-id (get-tx-id rs)]
+          ;; TODO: disable this when db is stable
+          (validate-db! rs)
           (state/update-state! :history/tx->editor-cursor
                                (fn [m] (assoc m tx-id before-editor-cursor)))