fix: retract children entities on server

This commit is contained in:
Tienson Qin
2026-04-07 16:49:55 +08:00
parent 987fa8ec48
commit 5685a2dacf
2 changed files with 105 additions and 5 deletions

View File

@@ -1,5 +1,6 @@
(ns logseq.db-sync.worker.handler.sync
(:require [clojure.string :as string]
(:require [clojure.set :as set]
[clojure.string :as string]
[datascript.core :as d]
[lambdaisland.glogi :as log]
[logseq.db :as ldb]
@@ -296,10 +297,32 @@
(defn- sanitize-tx
[db outliner-op tx-data]
(if (= outliner-op :fix)
(remove (fn [[_ id]]
(nil? (d/entity db id))) tx-data)
tx-data))
(let [retract-op? (fn [item]
(and (vector? item)
(= 2 (count item))
(contains? #{:db/retractEntity :db.fn/retractEntity} (first item))))
->eid (fn [entity-ref]
(some-> (d/entity db entity-ref) :db/id))
retract-eids (->> tx-data
(keep (fn [item]
(when (retract-op? item)
(->eid (second item)))))
set)
descendant-retract-eids (->> retract-eids
(mapcat (fn [eid]
(let [entity (d/entity db eid)]
(when (:block/uuid entity)
(ldb/get-block-full-children-ids db eid)))))
(remove nil?)
set)
missing-retract-eids (sort (set/difference descendant-retract-eids retract-eids))
tx-data' (cond-> (vec tx-data)
(seq missing-retract-eids)
(into (map (fn [eid] [:db/retractEntity eid]) missing-retract-eids)))]
(if (= outliner-op :fix)
(remove (fn [[_ id]]
(nil? (d/entity db id))) tx-data')
tx-data')))
(defn- apply-tx-entry!
[conn {:keys [tx outliner-op]}]

View File

@@ -234,6 +234,83 @@
(is (nil? (:data response)))
(is (= 2 @apply-calls)))))
(defn- seed-page-with-block-tree!
[conn]
(let [page-uuid (random-uuid)
parent-uuid (random-uuid)
child-a-uuid (random-uuid)
child-b-uuid (random-uuid)
now 1775549093572]
(d/transact! conn [{:block/uuid page-uuid
:block/name "sync-repro-page"
:block/title "sync-repro-page"
:block/created-at now
:block/updated-at now}
{:block/uuid parent-uuid
:block/title "parent"
:block/parent [:block/uuid page-uuid]
:block/page [:block/uuid page-uuid]
:block/order "a0"
:block/created-at now
:block/updated-at now}
{:block/uuid child-a-uuid
:block/title "child-a"
:block/parent [:block/uuid parent-uuid]
:block/page [:block/uuid page-uuid]
:block/order "a1"
:block/created-at now
:block/updated-at now}
{:block/uuid child-b-uuid
:block/title "child-b"
:block/parent [:block/uuid parent-uuid]
:block/page [:block/uuid page-uuid]
:block/order "a2"
:block/created-at now
:block/updated-at now}])
{:page-uuid page-uuid
:parent-uuid parent-uuid
:child-a-uuid child-a-uuid
:child-b-uuid child-b-uuid}))
(deftest tx-batch-stale-retract-block-includes-current-descendants-test
(testing "stale block retract should still delete descendants attached in current db"
(let [sql (test-sql/make-sql)
conn (storage/open-conn sql)
self #js {:sql sql
:conn conn
:schema-ready true}
{:keys [parent-uuid child-a-uuid child-b-uuid]} (seed-page-with-block-tree! conn)
t-before (storage/get-t sql)
stale-delete-entry {:tx (protocol/tx->transit [[:db/retractEntity [:block/uuid parent-uuid]]])
:outliner-op :delete-blocks}
response (with-redefs [ws/broadcast! (fn [& _] nil)]
(sync-handler/handle-tx-batch! self nil [stale-delete-entry] t-before))]
(is (= "tx/batch/ok" (:type response)))
(is (number? (:t response)))
(is (nil? (d/entity @conn [:block/uuid parent-uuid])))
(is (nil? (d/entity @conn [:block/uuid child-a-uuid])))
(is (nil? (d/entity @conn [:block/uuid child-b-uuid]))))))
(deftest tx-batch-stale-retract-page-includes-current-page-tree-test
(testing "stale page retract should still delete page tree to avoid orphan blocks"
(let [sql (test-sql/make-sql)
conn (storage/open-conn sql)
self #js {:sql sql
:conn conn
:schema-ready true}
{:keys [page-uuid parent-uuid child-a-uuid child-b-uuid]} (seed-page-with-block-tree! conn)
t-before (storage/get-t sql)
stale-delete-entry {:tx (protocol/tx->transit [[:db/retractEntity [:block/uuid page-uuid]]])
:outliner-op :delete-page}
response (with-redefs [ws/broadcast! (fn [& _] nil)]
(sync-handler/handle-tx-batch! self nil [stale-delete-entry] t-before))]
(is (= "tx/batch/ok" (:type response)))
(is (number? (:t response)))
(is (nil? (d/entity @conn [:block/uuid page-uuid])))
(is (nil? (d/entity @conn [:block/uuid parent-uuid])))
(is (nil? (d/entity @conn [:block/uuid child-a-uuid])))
(is (nil? (d/entity @conn [:block/uuid child-b-uuid]))))))
(deftest sync-pull-is-blocked-when-graph-is-not-ready-for-use-test
(async done
(let [self #js {:env #js {"DB" :db}