From 562e56e4c51b783d3edc83ff6dcc7f0ca9e7faaf Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Tue, 12 May 2026 17:45:14 +0800 Subject: [PATCH] fix: delete recycled node cleanup --- .../src/logseq/db/common/delete_blocks.cljs | 77 ++++++++++++------- .../logseq/db/common/delete_blocks_test.cljs | 35 +++++++++ .../outliner/src/logseq/outliner/recycle.cljs | 29 ++++--- .../test/logseq/outliner/recycle_test.cljs | 37 +++++++++ 4 files changed, 140 insertions(+), 38 deletions(-) diff --git a/deps/db/src/logseq/db/common/delete_blocks.cljs b/deps/db/src/logseq/db/common/delete_blocks.cljs index b637b34b15..10719096a9 100644 --- a/deps/db/src/logseq/db/common/delete_blocks.cljs +++ b/deps/db/src/logseq/db/common/delete_blocks.cljs @@ -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))) diff --git a/deps/db/test/logseq/db/common/delete_blocks_test.cljs b/deps/db/test/logseq/db/common/delete_blocks_test.cljs index 8e67dec387..931cdd55a6 100644 --- a/deps/db/test/logseq/db/common/delete_blocks_test.cljs +++ b/deps/db/test/logseq/db/common/delete_blocks_test.cljs @@ -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])))))) diff --git a/deps/outliner/src/logseq/outliner/recycle.cljs b/deps/outliner/src/logseq/outliner/recycle.cljs index ca5c289452..ca8f6ec007 100644 --- a/deps/outliner/src/logseq/outliner/recycle.cljs +++ b/deps/outliner/src/logseq/outliner/recycle.cljs @@ -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] diff --git a/deps/outliner/test/logseq/outliner/recycle_test.cljs b/deps/outliner/test/logseq/outliner/recycle_test.cljs index 99fd642845..68faf518d2 100644 --- a/deps/outliner/test/logseq/outliner/recycle_test.cljs +++ b/deps/outliner/test/logseq/outliner/recycle_test.cljs @@ -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])))))