fix: delete recycled node cleanup

This commit is contained in:
Tienson Qin
2026-05-12 17:45:14 +08:00
parent 9c77efda04
commit 562e56e4c5
4 changed files with 140 additions and 38 deletions

View File

@@ -45,35 +45,56 @@
tx))
refs)))
(defn- block-entity?
[entity]
(and (:block/uuid entity)
(:block/page entity)
(not (entity-util/page? entity))))
(defn- retracted-entities
[db txs]
(->> txs
(keep (fn [tx]
(when (and (vector? tx)
(contains? #{:db.fn/retractEntity :db/retractEntity} (first tx)))
(d/entity db (second tx)))))
(common-util/distinct-by :db/id)))
(defn- direct-cleanup-tx
[entities]
(let [retracted-blocks (filter block-entity? entities)
reaction-entities (->> entities
(mapcat :logseq.property.reaction/_target)
(common-util/distinct-by :db/id))
retract-reactions-tx (map (fn [reaction] [:db/retractEntity (:db/id reaction)])
reaction-entities)
retracted-tx (build-retracted-tx retracted-blocks)
history-entities (->> entities
(mapcat (fn [entity]
(concat (:logseq.property.history/_block entity)
(:logseq.property.history/_ref-value entity))))
(common-util/distinct-by :db/id))
retract-history-tx (map (fn [history] [:db/retractEntity (:db/id history)])
history-entities)
delete-views (->> entities
(mapcat :logseq.property/_view-for)
(map (fn [view] [:db/retractEntity (:db/id view)])))]
(vec (concat retracted-tx delete-views retract-history-tx retract-reactions-tx))))
(defn- build-cleanup-tx
[db txs]
(loop [pending-entities (retracted-entities db txs)
seen-ids #{}
cleanup-tx []]
(if-let [entities (seq (remove #(contains? seen-ids (:db/id %))
pending-entities))]
(let [seen-ids' (into seen-ids (map :db/id) entities)
next-tx (direct-cleanup-tx entities)]
(recur (retracted-entities db next-tx) seen-ids' (into cleanup-tx next-tx)))
(distinct cleanup-tx))))
(defn update-refs-history
"When an entity is deleted, related property history, views and reactions
are deleted"
[db txs _opts]
(let [retracted-ids (keep (fn [tx]
(when (and (vector? tx)
(contains? #{:db.fn/retractEntity :db/retractEntity} (first tx)))
(second tx))) txs)
retracted-entities (map #(d/entity db %) retracted-ids)]
(when (seq retracted-ids)
(let [retracted-blocks (remove entity-util/page? retracted-entities)
reaction-entities (->> retracted-entities
(mapcat :logseq.property.reaction/_target)
(common-util/distinct-by :db/id))
retract-reactions-tx (map (fn [reaction] [:db/retractEntity (:db/id reaction)])
reaction-entities)
retracted-tx (build-retracted-tx retracted-blocks)
history-entities (->> retracted-entities
(mapcat (fn [e]
(concat (:logseq.property.history/_block e)
(:logseq.property.history/_ref-value e))))
(common-util/distinct-by :db/id))
retract-history-tx (map (fn [history] [:db/retractEntity (:db/id history)])
history-entities)
delete-views (->>
(mapcat
(fn [item]
(let [block (d/entity db (:db/id item))]
(:logseq.property/_view-for block)))
retracted-entities)
(map (fn [b] [:db/retractEntity (:db/id b)])))]
(concat retracted-tx delete-views retract-history-tx retract-reactions-tx)))))
(seq (build-cleanup-tx db txs)))

View File

@@ -69,3 +69,38 @@
history-entity (d/entity @conn [:block/uuid history-uuid])]
(ldb/transact! conn [[:db/retractEntity (:db/id page)]])
(is (nil? (d/entity @conn (:db/id history-entity)))))))
(deftest delete-blocks-removes-history-for-corresponding-views
(testing "property history entries attached to a deleted block's view are retracted"
(let [conn (db-test/create-conn-with-blocks
{:pages-and-blocks
[{:page {:block/title "Page"}
:blocks [{:block/title "Target block"}]}]})
target-block (db-test/find-block-by-content @conn "Target block")
view-uuid (random-uuid)
target-history-uuid (random-uuid)
view-history-uuid (random-uuid)
now (common-util/time-ms)
_ (d/transact! conn [{:block/uuid view-uuid
:block/title "Target view"
:block/created-at now
:block/updated-at now
:logseq.property/view-for (:db/id target-block)
:logseq.property.view/type :logseq.property.view/type.table
:logseq.property.view/feature-type :linked-references}
{:block/uuid target-history-uuid
:block/created-at now
:block/updated-at now
:logseq.property.history/block (:db/id target-block)
:logseq.property.history/property (:db/id (d/entity @conn :logseq.property/status))
:logseq.property.history/scalar-value "Todo"}
{:block/uuid view-history-uuid
:block/created-at now
:block/updated-at now
:logseq.property.history/block [:block/uuid view-uuid]
:logseq.property.history/property (:db/id (d/entity @conn :logseq.property/status))
:logseq.property.history/scalar-value "List"}])]
(ldb/transact! conn [[:db/retractEntity (:db/id target-block)]])
(is (nil? (d/entity @conn [:block/uuid view-uuid])))
(is (nil? (d/entity @conn [:block/uuid target-history-uuid])))
(is (nil? (d/entity @conn [:block/uuid view-history-uuid]))))))

View File

@@ -4,6 +4,7 @@
[logseq.common.util :as common-util]
[logseq.common.uuid :as common-uuid]
[logseq.db :as ldb]
[logseq.db.common.delete-blocks :as delete-blocks]
[logseq.db.common.initial-data :as common-initial-data]
[logseq.db.common.order :as db-order]))
@@ -105,6 +106,10 @@
(#(d/entity db [:block/uuid %]))
:db/id))
(defn- with-delete-cleanup-tx
[db tx-data]
(distinct (concat tx-data (delete-blocks/update-refs-history db tx-data {}))))
(defn recycle-blocks-tx-data
[db blocks {:keys [deleted-by-uuid now-ms]}]
(let [{:keys [page page-id tx-data]} (ensure-recycle-page db)
@@ -232,15 +237,17 @@
(defn ^:api permanently-delete-tx-data
[db root]
(when (and root (recycled? root))
(->> (if (ldb/page? root)
(keep (fn [id]
(some-> (d/entity db id) :block/uuid))
(page-tree-ids db root))
(keep :block/uuid (block-subtree db root)))
(map (fn [block-uuid]
[:db/retractEntity [:block/uuid block-uuid]]))
distinct
seq)))
(some->> (if (ldb/page? root)
(keep (fn [id]
(some-> (d/entity db id) :block/uuid))
(page-tree-ids db root))
(keep :block/uuid (block-subtree db root)))
(map (fn [block-uuid]
[:db/retractEntity [:block/uuid block-uuid]]))
distinct
seq
(with-delete-cleanup-tx db)
seq)))
(defn ^:api permanently-delete!
[conn root-uuid]
@@ -265,7 +272,9 @@
(if (ldb/page? entity)
(map (fn [id] [:db/retractEntity id]) (page-tree-ids db entity))
(map (fn [node] [:db/retractEntity (:db/id node)]) (block-subtree db entity)))))
distinct)))
distinct
seq
(with-delete-cleanup-tx db))))
(defn ^:api gc!
[conn opts]

View File

@@ -1,6 +1,7 @@
(ns logseq.outliner.recycle-test
(:require [cljs.test :refer [deftest is]]
[datascript.core :as d]
[logseq.common.util :as common-util]
[logseq.db :as ldb]
[logseq.db.test.helper :as db-test]
[logseq.outliner.recycle :as recycle]))
@@ -48,3 +49,39 @@
(is (some? (d/entity @conn [:block/uuid (:block/uuid page)])))
(is (nil? (d/entity @conn [:block/uuid parent-uuid])))
(is (nil? (d/entity @conn [:block/uuid child-uuid])))))
(deftest permanently-delete-recycled-block-removes-corresponding-view-history
(let [conn (db-test/create-conn-with-blocks
[{:page {:block/title "page1"}
:blocks [{:block/title "target"}]}])
target (db-test/find-block-by-content @conn "target")
target-uuid (:block/uuid target)
view-uuid (random-uuid)
target-history-uuid (random-uuid)
view-history-uuid (random-uuid)
now (common-util/time-ms)
_ (d/transact! conn [{:block/uuid view-uuid
:block/title "target view"
:block/created-at now
:block/updated-at now
:logseq.property/view-for (:db/id target)
:logseq.property.view/type :logseq.property.view/type.table
:logseq.property.view/feature-type :linked-references}
{:block/uuid target-history-uuid
:block/created-at now
:block/updated-at now
:logseq.property.history/block (:db/id target)
:logseq.property.history/property (:db/id (d/entity @conn :logseq.property/status))
:logseq.property.history/scalar-value "Todo"}
{:block/uuid view-history-uuid
:block/created-at now
:block/updated-at now
:logseq.property.history/block [:block/uuid view-uuid]
:logseq.property.history/property (:db/id (d/entity @conn :logseq.property/status))
:logseq.property.history/scalar-value "List"}])]
(ldb/transact! conn (recycle/recycle-blocks-tx-data @conn [target] {}) {:outliner-op :delete-blocks})
(is (true? (recycle/permanently-delete! conn target-uuid)))
(is (nil? (d/entity @conn [:block/uuid target-uuid])))
(is (nil? (d/entity @conn [:block/uuid view-uuid])))
(is (nil? (d/entity @conn [:block/uuid target-history-uuid])))
(is (nil? (d/entity @conn [:block/uuid view-history-uuid])))))