fix(rtc): batch store and validate db (#12249)

* fix(rtc): batch store and validate db

* fix: logseq.db/transact! shouldn't distinct tx-data

since move-op include 2 steps:
1. insert-block
2. update-attrs

This results in db invalid after step 1.

* refactor: add transact-with-temp-conn!

* bump nbb-logseq and add tests for ldb/transact* fns

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: rcmerci <rcmerci@gmail.com>
This commit is contained in:
Tienson Qin
2025-12-09 20:47:37 +08:00
committed by GitHub
parent 61d28c307e
commit f594e2034f
22 changed files with 138 additions and 57 deletions

View File

@@ -21,7 +21,6 @@
[logseq.db.common.property-util :as db-property-util]
[logseq.db.frontend.property :as db-property]
[logseq.graph-parser.whiteboard :as gp-whiteboard]
[logseq.outliner.batch-tx :as batch-tx]
[logseq.outliner.core :as outliner-core]
[logseq.outliner.transaction :as outliner-tx]
[missionary.core :as m]))
@@ -192,9 +191,11 @@
(when-let [target-b
(d/entity @conn (:db/id (:block/page (d/entity @conn [:block/uuid block-uuid]))))]
(transact-db! :move-blocks&persist-op repo conn [b] target-b {:sibling? false}))))
(doseq [block-uuid block-uuids-to-remove]
(when-let [b (d/entity @conn [:block/uuid block-uuid])]
(transact-db! :delete-blocks repo conn date-formatter [b] {}))))))
(let [deleting-blocks (keep (fn [block-uuid]
(d/entity @conn [:block/uuid block-uuid]))
block-uuids-to-remove)]
(when (seq deleting-blocks)
(transact-db! :delete-blocks repo conn date-formatter deleting-blocks {}))))))
(defn- insert-or-move-block
[repo conn block-uuid remote-parents remote-block-order move? op-value]
@@ -209,8 +210,7 @@
(if move?
(transact-db! :move-blocks repo conn [(block-reuse-db-id b)] local-parent {:sibling? false})
(transact-db! :insert-blocks repo conn
[{:block/uuid block-uuid
:block/title ""}]
[{:block/uuid block-uuid}]
local-parent {:sibling? false :keep-uuid? true}))
(transact-db! :update-block-order-directly repo conn block-uuid first-remote-parent remote-block-order))
@@ -681,20 +681,25 @@
update-ops (vals update-ops-map)
update-page-ops (vals update-page-ops-map)
remove-page-ops (vals remove-page-ops-map)
db-before @conn]
db-before @conn
tx-meta {:rtc-tx? true
:persist-op? false
:gen-undo-ops? false}]
(rtc-log-and-state/update-remote-t graph-uuid remote-t)
(js/console.groupCollapsed "rtc/apply-remote-ops-log")
(batch-tx/with-batch-tx-mode conn {:rtc-tx? true
:persist-op? false
:gen-undo-ops? false}
(worker-util/profile :ensure-refed-blocks-exist (ensure-refed-blocks-exist repo conn refed-blocks))
(worker-util/profile :apply-remote-update-page-ops (apply-remote-update-page-ops repo conn update-page-ops))
(worker-util/profile :apply-remote-move-ops (apply-remote-move-ops repo conn sorted-move-ops))
(worker-util/profile :apply-remote-update-ops (apply-remote-update-ops repo conn update-ops))
(worker-util/profile :apply-remote-remove-page-ops (apply-remote-remove-page-ops repo conn remove-page-ops)))
(ldb/transact-with-temp-conn!
conn tx-meta
(fn [temp-conn]
(worker-util/profile :ensure-refed-blocks-exist (ensure-refed-blocks-exist repo temp-conn refed-blocks))
(worker-util/profile :apply-remote-update-page-ops (apply-remote-update-page-ops repo temp-conn update-page-ops))
(worker-util/profile :apply-remote-move-ops (apply-remote-move-ops repo temp-conn sorted-move-ops))
(worker-util/profile :apply-remote-update-ops (apply-remote-update-ops repo temp-conn update-ops))
(worker-util/profile :apply-remote-remove-page-ops (apply-remote-remove-page-ops repo temp-conn remove-page-ops))))
;; NOTE: we cannot set :persist-op? = true when batch-tx/with-batch-tx-mode (already set to false)
;; and there're some transactions in `apply-remote-remove-ops` need to :persist-op?=true
(worker-util/profile :apply-remote-remove-ops (apply-remote-remove-ops repo conn date-formatter remove-ops))
;; wait all remote-ops transacted into db,
;; then start to check any asset-updates in remote
(let [db-after @conn]

View File

@@ -156,6 +156,14 @@
:logseq.property/status [status-value-uuid2]}}
r)))))
(defn- apply-move-ops!
[repo conn move-ops]
(ldb/transact-with-temp-conn!
conn
{}
(fn [temp-conn]
(#'r.remote/apply-remote-move-ops repo temp-conn move-ops))))
(deftest apply-remote-move-ops-test
(let [repo (state/get-current-repo)
conn (conn/get-db repo false)
@@ -182,6 +190,7 @@
:block/parent [:block/uuid page-uuid]}]
(ldb/get-page @conn page-name)
{:sibling? false :keep-uuid? true}))
(testing "apply-remote-move-ops-test1"
(let [data-from-ws {:req-id "req-id"
:t 1 ;; not used
@@ -199,7 +208,7 @@
(#'r.remote/affected-blocks->diff-type-ops
repo (:affected-blocks data-from-ws))))]
(is (rtc-schema/data-from-ws-validator data-from-ws) data-from-ws)
(#'r.remote/apply-remote-move-ops repo conn move-ops)
(apply-move-ops! repo conn move-ops)
(let [page-blocks (ldb/get-page-blocks @conn (:db/id (ldb/get-page @conn page-name)) {})]
(is (= #{uuid1-remote uuid1-client uuid2-client} (set (map :block/uuid page-blocks)))
[uuid1-remote uuid1-client uuid2-client])
@@ -229,7 +238,7 @@
(#'r.remote/affected-blocks->diff-type-ops
repo (:affected-blocks data-from-ws))))]
(is (rtc-schema/data-from-ws-validator data-from-ws))
(#'r.remote/apply-remote-move-ops repo conn move-ops)
(apply-move-ops! repo conn move-ops)
(let [page-blocks (ldb/get-page-blocks @conn (:db/id (ldb/get-page @conn page-name)) {})]
(is (= #{uuid1-remote uuid2-remote uuid1-client uuid2-client} (set (map :block/uuid page-blocks))))
(is (= ["a0" "a1"]

View File

@@ -0,0 +1,43 @@
(ns logseq.db-test
(:require [cljs.test :refer [deftest is testing] :as t]
[datascript.core :as d]
[frontend.db.conn :as conn]
[frontend.test.helper :as test-helper]
[logseq.db :as ldb]))
;; TODO: move tests to deps/db
(t/use-fixtures :each
test-helper/db-based-start-and-destroy-db-map-fixture)
(deftest test-transact-with-multiple-tx-datoms
(testing "last write wins with same tx"
(let [conn (d/create-conn)]
(d/transact! conn [[:db/add -1 :property :v1]])
(let [tx (:max-tx @conn)]
(ldb/transact! conn
[(d/datom 1 :property :v1 (inc tx) false)
(d/datom 1 :property :v1 (inc tx) true)]))
(is (= :v1 (:property (d/entity @conn 1))))))
(testing "last write wins with different tx"
(let [conn (d/create-conn)]
(d/transact! conn [[:db/add -1 :property :v1]])
(let [tx (:max-tx @conn)]
(ldb/transact! conn
[(d/datom 1 :property :v1 (inc tx) false)
(d/datom 1 :property :v1 (+ tx 2) true)]))
(is (= :v1 (:property (d/entity @conn 1)))))))
(deftest test-transact-with-temp-conn!
(testing "DB validation should be running after the whole transaction"
(let [conn (conn/get-db false)]
(testing "#Task shouldn't be converted to property"
(is (thrown? js/Error (ldb/transact! conn [{:db/ident :logseq.class/Task
:block/tags :logseq.class/Property}]))))
(ldb/transact-with-temp-conn!
conn
{}
(fn [temp-conn]
(ldb/transact! temp-conn [{:db/ident :logseq.class/Task
:block/tags :logseq.class/Property}])
(ldb/transact! temp-conn [[:db/retract :logseq.class/Task :block/tags :logseq.class/Property]]))))))