diff --git a/src/main/frontend/extensions/fsrs.cljs b/src/main/frontend/extensions/fsrs.cljs index ae90ea9e23..279b691454 100644 --- a/src/main/frontend/extensions/fsrs.cljs +++ b/src/main/frontend/extensions/fsrs.cljs @@ -114,12 +114,11 @@ (defn- - {:block/tags #{(:db/id (db/entity :logseq.class/Pdf-annotation))} + {:block/tags #{:logseq.class/Pdf-annotation} :block/collapsed? image? :logseq.property/ls-type :annotation :logseq.property.pdf/hl-color color-id diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index 91852a3dd0..fd0096629e 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1347,8 +1347,7 @@ (notification/show! [:div "Asset size shouldn't be larger than 100M"] :warning false) - (throw (ex-info "Asset size shouldn't be larger than 100M" {:file-name file-name}))) - asset (db/entity :logseq.class/Asset)] + (throw (ex-info "Asset size shouldn't be larger than 100M" {:file-name file-name})))] (p/do! (when file (let [file-path (str block-id "." ext)] @@ -1359,7 +1358,9 @@ :logseq.property.asset/external-url external-url :logseq.property.asset/size size :logseq.property.asset/checksum checksum - :block/tags #{(:db/id asset)}}))))) + ;; Use stable class ident in tx payload to avoid leaking numeric eids + ;; into outliner history ops shared with the worker sync pipeline. + :block/tags #{:logseq.class/Asset}}))))) (defn db-based-save-assets! "Save incoming(pasted) assets to assets directory. diff --git a/src/main/frontend/worker/db_worker.cljs b/src/main/frontend/worker/db_worker.cljs index dba9afb8ec..d1e00fd23a 100644 --- a/src/main/frontend/worker/db_worker.cljs +++ b/src/main/frontend/worker/db_worker.cljs @@ -343,9 +343,7 @@ [repo {:keys [config datoms sync-download-graph?] :as opts}] (when-not (worker-state/get-sqlite-conn repo) (p/let [[db search-db client-ops-db :as dbs] (get-dbs repo) - storage (new-sqlite-storage db) - client-ops-storage (when-not @*publishing? - (new-sqlite-storage client-ops-db))] + storage (new-sqlite-storage db)] (swap! *sqlite-conns assoc repo {:db db :search search-db :client-ops client-ops-db}) @@ -378,16 +376,13 @@ (partition-all batch-size))] (doseq [batch non-ident-batches] (d/transact! conn batch {:initial-db? true})))) - client-ops-conn (when-not @*publishing? (common-sqlite/get-storage-conn - client-ops-storage - client-op/schema-in-db)) initial-data-exists? (when (nil? datoms) (and (d/entity @conn :logseq.class/Root) (= "db" (:kv/value (d/entity @conn :logseq.kv/db-type)))))] (swap! *datascript-conns assoc repo conn) - (swap! *client-ops-conns assoc repo client-ops-conn) - (when (and (not @*publishing?) (not= client-op/schema-in-db (d/schema @client-ops-conn))) - (d/reset-schema! client-ops-conn client-op/schema-in-db)) + (swap! *client-ops-conns assoc repo client-ops-db) + (when-not @*publishing? + (client-op/ensure-sqlite-schema! client-ops-db)) (ensure-client-ops-cleanup-timer! repo) (let [initial-tx-report (when-not (or initial-data-exists? (seq datoms) diff --git a/src/main/frontend/worker/sync/apply_txs.cljs b/src/main/frontend/worker/sync/apply_txs.cljs index d363b366c1..daf775f032 100644 --- a/src/main/frontend/worker/sync/apply_txs.cljs +++ b/src/main/frontend/worker/sync/apply_txs.cljs @@ -244,36 +244,33 @@ (declare apply-history-action!) (defn- persist-local-tx! [repo {:keys [db-before db-after tx-data tx-meta] :as tx-report} normalized-tx-data reversed-datoms] - (when-let [conn (client-ops-conn repo)] + (when (client-ops-conn repo) (let [tx-id (or (:db-sync/tx-id tx-meta) (random-uuid)) - existing-ent (d/entity @conn [:db-sync/tx-id tx-id]) - should-inc-pending? (not= true (:db-sync/pending? existing-ent)) now (.now js/Date) - created-at (or (:db-sync/created-at existing-ent) now) {:keys [forward-outliner-ops inverse-outliner-ops]} (derive-history-outliner-ops db-before db-after tx-data tx-meta) - inferred-outliner-ops?' (inferred-outliner-ops? tx-meta)] + inferred-outliner-ops?' (inferred-outliner-ops? tx-meta) + {:keys [should-inc-pending?]} + (client-op/upsert-local-tx-entry! + repo + {:tx-id tx-id + :created-at now + :pending? true + :failed? false + :outliner-op (:outliner-op tx-meta) + :undo-redo (cond + (:undo? tx-meta) :undo + (:redo? tx-meta) :redo + :else :none) + :forward-outliner-ops forward-outliner-ops + :inverse-outliner-ops inverse-outliner-ops + :inferred-outliner-ops? inferred-outliner-ops?' + :normalized-tx-data normalized-tx-data + :reversed-tx-data reversed-datoms})] ;; (prn :debug :forward-outliner-ops) ;; (cljs.pprint/pprint forward-outliner-ops) ;; (prn :debug :inverse-outliner-ops) ;; (cljs.pprint/pprint inverse-outliner-ops) - (ldb/transact! conn [{:db-sync/tx-id tx-id - :db-sync/normalized-tx-data normalized-tx-data - :db-sync/reversed-tx-data reversed-datoms - :db-sync/pending? true - :db-sync/failed? false - :db-sync/outliner-op (:outliner-op tx-meta) - :db-sync/undo-redo? (cond - (:undo? tx-meta) - :undo - (:redo? tx-meta) - :redo - :else - :none) - :db-sync/forward-outliner-ops forward-outliner-ops - :db-sync/inverse-outliner-ops inverse-outliner-ops - :db-sync/inferred-outliner-ops? inferred-outliner-ops?' - :db-sync/created-at created-at}]) (worker-undo-redo/gen-undo-ops! repo tx-report tx-id {:apply-history-action! apply-history-action!}) (when should-inc-pending? @@ -298,79 +295,29 @@ (defn pending-txs [repo & {:keys [limit]}] - (when-let [conn (client-ops-conn repo)] - (let [db @conn - datoms (d/datoms db :avet :db-sync/created-at) - take-limit (fn [c] - (if limit (take limit c) c))] - (->> datoms - (map (fn [datom] - (d/entity db (:e datom)))) - (filter (fn [e] (:db-sync/pending? e))) - take-limit - (keep (fn [ent] - (let [tx-id (:db-sync/tx-id ent) - tx' (:db-sync/normalized-tx-data ent) - reversed-tx' (:db-sync/reversed-tx-data ent)] - {:tx-id tx-id - :outliner-op (:db-sync/outliner-op ent) - :forward-outliner-ops (:db-sync/forward-outliner-ops ent) - :inverse-outliner-ops (:db-sync/inverse-outliner-ops ent) - :inferred-outliner-ops? (:db-sync/inferred-outliner-ops? ent) - :db-sync/undo-redo (:db-sync/undo-redo? ent) - :tx tx' - :reversed-tx reversed-tx'}))) - vec)))) + (client-op/get-pending-local-txs repo :limit limit)) (defn- pending-tx-by-id [repo tx-id] - (when-let [conn (client-ops-conn repo)] - (when-let [ent (d/entity @conn [:db-sync/tx-id tx-id])] - {:tx-id (:db-sync/tx-id ent) - :outliner-op (:db-sync/outliner-op ent) - :forward-outliner-ops (:db-sync/forward-outliner-ops ent) - :inverse-outliner-ops (:db-sync/inverse-outliner-ops ent) - :db-sync/undo-redo (:db-sync/undo-redo? ent) - :tx (:db-sync/normalized-tx-data ent) - :reversed-tx (:db-sync/reversed-tx-data ent)}))) + (client-op/get-local-tx-entry repo tx-id)) (defn mark-pending-txs-false! [repo tx-ids] (when (seq tx-ids) - (when-let [conn (client-ops-conn repo)] - (let [pending-to-remove (->> tx-ids - (keep (fn [tx-id] - (when (true? (:db-sync/pending? (d/entity @conn [:db-sync/tx-id tx-id]))) - tx-id))) - count)] - (ldb/transact! conn - (mapv (fn [tx-id] - [:db/add [:db-sync/tx-id tx-id] :db-sync/pending? false]) - tx-ids)) - (when (pos? pending-to-remove) - (client-op/adjust-pending-local-tx-count! repo (- pending-to-remove))) - (when-let [client (current-client repo)] - (broadcast-rtc-state! client)))))) + (when-let [pending-to-remove (client-op/mark-pending-txs-false! repo tx-ids)] + (when (pos? pending-to-remove) + (client-op/adjust-pending-local-tx-count! repo (- pending-to-remove))) + (when-let [client (current-client repo)] + (broadcast-rtc-state! client))))) (defn mark-failed-txs! [repo tx-ids] (when (seq tx-ids) - (when-let [conn (client-ops-conn repo)] - (let [pending-to-remove (->> tx-ids - (keep (fn [tx-id] - (when (true? (:db-sync/pending? (d/entity @conn [:db-sync/tx-id tx-id]))) - tx-id))) - count)] - (ldb/transact! conn - (mapv (fn [tx-id] - {:db-sync/tx-id tx-id - :db-sync/pending? false - :db-sync/failed? true}) - tx-ids)) - (when (pos? pending-to-remove) - (client-op/adjust-pending-local-tx-count! repo (- pending-to-remove))) - (when-let [client (current-client repo)] - (broadcast-rtc-state! client)))))) + (when-let [pending-to-remove (client-op/mark-failed-txs! repo tx-ids)] + (when (pos? pending-to-remove) + (client-op/adjust-pending-local-tx-count! repo (- pending-to-remove))) + (when-let [client (current-client repo)] + (broadcast-rtc-state! client))))) (defn clear-pending-txs! [repo] diff --git a/src/main/frontend/worker/sync/client_op.cljs b/src/main/frontend/worker/sync/client_op.cljs index 7a31945e68..7fab35b121 100644 --- a/src/main/frontend/worker/sync/client_op.cljs +++ b/src/main/frontend/worker/sync/client_op.cljs @@ -1,9 +1,11 @@ (ns frontend.worker.sync.client-op - "Store client-ops in a persisted datascript" + "Store client sync metadata and ops in sqlite tables. + DataScript client-op storage is deprecated and unsupported." (:require [datascript.core :as d] [frontend.worker.state :as worker-state] + [goog.object :as gobj] [lambdaisland.glogi :as log] - [logseq.db :as ldb] + [logseq.db.sqlite.util :as sqlite-util] [malli.core :as ma] [malli.transform :as mt])) @@ -29,85 +31,241 @@ (defonce *repo->pending-local-tx-count (atom {})) -(def schema-in-db - "TODO: rename this db-name from client-op to client-metadata+op. - and move it to its own namespace." - {:block/uuid {:db/unique :db.unique/identity} - :db-ident {:db/unique :db.unique/identity} - ;; local-tx is the latest remote-tx that local db persists - :local-tx {:db/index true} - :graph-uuid {:db/index true} - :db-sync/checksum {:db/index true} - :db-sync/tx-id {:db/unique :db.unique/identity} - :db-sync/created-at {:db/index true} - :db-sync/pending? {:db/index true} - :db-sync/failed? {:db/index true} - :db-sync/outliner-op {} - :db-sync/forward-outliner-ops {} - :db-sync/inverse-outliner-ops {} - :db-sync/inferred-outliner-ops? {} - :db-sync/normalized-tx-data {} - :db-sync/reversed-tx-data {} - :db-sync/asset-op? {:db/index true}}) +(def ^:private sqlite-schema-ready-key "__logseq_client_ops_schema_ready") +(def ^:private sqlite-mode-key "__logseq_client_ops_sqlite_mode") +(def ^:private sync-meta-table-sql + "create table if not exists sync_meta (key text primary key, value text)") +(def ^:private client-ops-table-sql + (str "create table if not exists client_ops (" + "id integer primary key autoincrement," + "kind text not null," + "created_at integer not null," + "tx_id text unique," + "pending integer not null default 0," + "failed integer not null default 0," + "outliner_op text," + "undo_redo text," + "forward_outliner_ops text," + "inverse_outliner_ops text," + "inferred_outliner_ops integer," + "normalized_tx_data text," + "reversed_tx_data text," + "asset_uuid text," + "asset_op text," + "asset_t integer," + "asset_value text" + ")")) +(def ^:private pending-index-sql + "create index if not exists idx_client_ops_pending_created on client_ops(kind, pending, created_at, id)") +(def ^:private asset-index-sql + "create index if not exists idx_client_ops_asset_uuid on client_ops(kind, asset_uuid)") + +(defn- client-ops-store + [repo] + (worker-state/get-client-ops-conn repo)) + +(declare ensure-sqlite-schema!) + +(defn- detect-sqlite-mode + [^js db] + (or (gobj/get db sqlite-mode-key) + (let [mode + (cond + (not db) nil + (fn? (gobj/get db "prepare")) + (try + (let [^js stmt (.prepare db "select 1")] + (try + (if (fn? (gobj/get stmt "run")) + :better-sqlite + :sqlite-wasm) + (finally + (when (fn? (gobj/get stmt "finalize")) + (.finalize stmt))))) + (catch :default _ + :sqlite-wasm)) + (fn? (gobj/get db "exec")) :sqlite-wasm + :else nil)] + (when mode + (try + (gobj/set db sqlite-mode-key mode) + (catch :default _ + nil))) + mode))) + +(defn- sqlite-db? + [conn] + (some? (detect-sqlite-mode conn))) + +(defn- sqlite-store-or-throw + [repo] + (when-let [store (client-ops-store repo)] + (if (sqlite-db? store) + (do + (ensure-sqlite-schema! store) + store) + (throw (ex-info "Legacy DataScript client-op storage is unsupported. Please back up the graph and re-download it." + {:type :db-sync/legacy-client-ops-storage + :repo repo}))))) + +(defn- parse-uuid-str + [v] + (when (string? v) + (try + (uuid v) + (catch :default _ + nil)))) + +(defn- kw->str + [v] + (cond + (keyword? v) (name v) + (string? v) v + :else nil)) + +(defn- str->kw + [v] + (when (string? v) + (keyword v))) + +(defn- bool->int [v] (if v 1 0)) +(defn- int->bool [v] (not (or (nil? v) (= 0 v) (= false v)))) + +(defn- sqlite-run! + [^js db sql params] + (case (detect-sqlite-mode db) + :better-sqlite + (let [^js stmt (.prepare db sql) + run-fn (gobj/get stmt "run")] + (if (seq params) + (.apply run-fn stmt (to-array params)) + (.call run-fn stmt))) + + :sqlite-wasm + (.exec db #js {:sql sql + :bind (into-array params)}) + + nil)) + +(defn- sqlite-rows + [^js db sql params] + (case (detect-sqlite-mode db) + :better-sqlite + (let [^js stmt (.prepare db sql) + all-fn (gobj/get stmt "all")] + (vec (if (seq params) + (.apply all-fn stmt (to-array params)) + (.call all-fn stmt)))) + + :sqlite-wasm + (let [^js result (.exec db #js {:sql sql + :bind (into-array params) + :rowMode "object" + :returnValue "resultRows"})] + (cond + (nil? result) [] + (array? result) (vec result) + (fn? (gobj/get result "toArray")) (vec (.toArray result)) + :else [])) + + [])) + +(defn- sqlite-row + [db sql params] + (first (sqlite-rows db sql params))) + +(defn- sqlite-with-tx! + [^js db f] + (case (detect-sqlite-mode db) + :better-sqlite + (let [tx-fn (.transaction db (fn [] (f db)))] + (tx-fn)) + + :sqlite-wasm + (if (fn? (gobj/get db "transaction")) + (.transaction db (fn [tx] (f tx))) + (f db)) + + (f db))) + +(defn ensure-sqlite-schema! + [db] + (when (sqlite-db? db) + (when-not (true? (gobj/get db sqlite-schema-ready-key)) + (sqlite-with-tx! + db + (fn [tx] + (sqlite-run! tx sync-meta-table-sql []) + (sqlite-run! tx client-ops-table-sql []) + (sqlite-run! tx pending-index-sql []) + (sqlite-run! tx asset-index-sql []))) + (try + (gobj/set db sqlite-schema-ready-key true) + (catch :default _ + nil))))) + +(defn- sqlite-get-meta + [db k] + (some-> (sqlite-row db "select value from sync_meta where key = ?" [(name k)]) + (aget "value"))) + +(defn- sqlite-set-meta! + [db k v] + (sqlite-run! db + (str "insert into sync_meta (key, value) values (?, ?)" + " on conflict(key) do update set value = excluded.value") + [(name k) (str v)])) + +(defn- sqlite-delete-meta! + [db k] + (sqlite-run! db "delete from sync_meta where key = ?" [(name k)])) (defn update-graph-uuid [repo graph-uuid] {:pre [(some? graph-uuid)]} - (when-let [conn (worker-state/get-client-ops-conn repo)] - (let [old-datoms (d/datoms @conn :avet :graph-uuid) - retractions (mapv (fn [datom] - [:db/retract (:e datom) :graph-uuid (:v datom)]) - old-datoms)] - (ldb/transact! conn (conj retractions [:db/add "e" :graph-uuid graph-uuid]))))) + (when-let [store (sqlite-store-or-throw repo)] + (sqlite-set-meta! store :graph-uuid graph-uuid))) (defn get-graph-uuid [repo] - (when-let [conn (worker-state/get-client-ops-conn repo)] - (:v (first (d/datoms @conn :avet :graph-uuid))))) + (some-> (sqlite-store-or-throw repo) + (sqlite-get-meta :graph-uuid))) (defn update-local-tx [repo t] {:pre [(some? t)]} - (let [conn (worker-state/get-client-ops-conn repo)] - (assert (some? conn) repo) - (let [tx-data - (if-let [datom (first (d/datoms @conn :avet :local-tx))] - [:db/add (:e datom) :local-tx t] - (if-let [datom (first (d/datoms @conn :avet :db-sync/checksum))] - [:db/add (:e datom) :local-tx t] - [:db/add "e" :local-tx t]))] - (ldb/transact! conn [tx-data])))) + (let [store (sqlite-store-or-throw repo)] + (assert (some? store) repo) + (sqlite-set-meta! store :local-tx t))) (defn update-local-checksum [repo checksum] {:pre [(some? checksum)]} - (let [conn (worker-state/get-client-ops-conn repo)] - (assert (some? conn) repo) - (let [tx-data - (if-let [datom (first (d/datoms @conn :avet :db-sync/checksum))] - [:db/add (:e datom) :db-sync/checksum checksum] - (if-let [datom (first (d/datoms @conn :avet :local-tx))] - [:db/add (:e datom) :db-sync/checksum checksum] - [:db/add "e" :db-sync/checksum checksum]))] - (ldb/transact! conn [tx-data])))) + (let [store (sqlite-store-or-throw repo)] + (assert (some? store) repo) + (sqlite-set-meta! store :db-sync/checksum checksum))) (defn remove-local-tx [repo] - (when-let [conn (worker-state/get-client-ops-conn repo)] - (when-let [datom (first (d/datoms @conn :avet :local-tx))] - (ldb/transact! conn [[:db/retract (:e datom) :local-tx]])))) + (when-let [store (sqlite-store-or-throw repo)] + (sqlite-delete-meta! store :local-tx))) (defn get-local-tx [repo] - (when-let [conn (worker-state/get-client-ops-conn repo)] - (:v (first (d/datoms @conn :avet :local-tx))))) + (when-let [store (sqlite-store-or-throw repo)] + (some-> (sqlite-get-meta store :local-tx) + (js/parseInt 10)))) (defn get-pending-local-tx-count [repo] (if-let [cached (get @*repo->pending-local-tx-count repo)] cached - (let [count' (if-let [conn (worker-state/get-client-ops-conn repo)] - (count (d/datoms @conn :avet :db-sync/pending? true)) + (let [count' (if-let [store (sqlite-store-or-throw repo)] + (or (some-> (sqlite-row store + "select count(*) as c from client_ops where kind = 'tx' and pending = 1" + []) + (aget "c")) + 0) 0)] (swap! *repo->pending-local-tx-count assoc repo count') count'))) @@ -122,9 +280,9 @@ (defn get-local-checksum [repo] - (let [conn (worker-state/get-client-ops-conn repo)] - (assert (some? conn) repo) - (:v (first (d/datoms @conn :avet :db-sync/checksum))))) + (let [store (sqlite-store-or-throw repo)] + (assert (some? store) repo) + (sqlite-get-meta store :db-sync/checksum))) (defn rtc-db-graph? "Is RTC enabled" @@ -132,12 +290,182 @@ (or (exists? js/process) (some? (get-graph-uuid repo)))) +(defn- row->pending-local-tx + [row] + (let [tx-id (parse-uuid-str (aget row "tx_id"))] + (when tx-id + {:tx-id tx-id + :outliner-op (str->kw (aget row "outliner_op")) + :forward-outliner-ops (sqlite-util/transit-read (aget row "forward_outliner_ops")) + :inverse-outliner-ops (sqlite-util/transit-read (aget row "inverse_outliner_ops")) + :inferred-outliner-ops? (int->bool (aget row "inferred_outliner_ops")) + :db-sync/undo-redo (str->kw (aget row "undo_redo")) + :tx (sqlite-util/transit-read (aget row "normalized_tx_data")) + :reversed-tx (sqlite-util/transit-read (aget row "reversed_tx_data"))}))) + +(defn upsert-local-tx-entry! + [repo {:keys [tx-id created-at pending? failed? outliner-op undo-redo + forward-outliner-ops inverse-outliner-ops inferred-outliner-ops? + normalized-tx-data reversed-tx-data] + :or {pending? true failed? false}}] + {:pre [(some? tx-id)]} + (let [store (sqlite-store-or-throw repo)] + (assert (some? store) repo) + (let [tx-id-str (str tx-id) + existing (sqlite-row store + "select pending, created_at from client_ops where kind = 'tx' and tx_id = ?" + [tx-id-str]) + should-inc-pending? (not= 1 (some-> existing (aget "pending"))) + created-at' (or (some-> existing (aget "created_at")) + created-at + (.now js/Date))] + (sqlite-run! store + (str "insert into client_ops (" + "kind, created_at, tx_id, pending, failed, outliner_op, undo_redo, " + "forward_outliner_ops, inverse_outliner_ops, inferred_outliner_ops, " + "normalized_tx_data, reversed_tx_data" + ") values ('tx', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + " on conflict(tx_id) do update set " + "created_at = excluded.created_at," + "pending = excluded.pending," + "failed = excluded.failed," + "outliner_op = excluded.outliner_op," + "undo_redo = excluded.undo_redo," + "forward_outliner_ops = excluded.forward_outliner_ops," + "inverse_outliner_ops = excluded.inverse_outliner_ops," + "inferred_outliner_ops = excluded.inferred_outliner_ops," + "normalized_tx_data = excluded.normalized_tx_data," + "reversed_tx_data = excluded.reversed_tx_data") + [created-at' + tx-id-str + (bool->int pending?) + (bool->int failed?) + (kw->str outliner-op) + (kw->str undo-redo) + (sqlite-util/transit-write (or forward-outliner-ops [])) + (sqlite-util/transit-write (or inverse-outliner-ops [])) + (bool->int inferred-outliner-ops?) + (sqlite-util/transit-write (or normalized-tx-data [])) + (sqlite-util/transit-write (or reversed-tx-data []))]) + {:tx-id tx-id + :created-at created-at' + :should-inc-pending? should-inc-pending?}))) + +(defn get-local-tx-entry + [repo tx-id] + (when (uuid? tx-id) + (when-let [store (sqlite-store-or-throw repo)] + (some-> (sqlite-row store + (str "select tx_id, outliner_op, undo_redo, " + "forward_outliner_ops, inverse_outliner_ops, inferred_outliner_ops, " + "normalized_tx_data, reversed_tx_data " + "from client_ops where kind = 'tx' and tx_id = ? limit 1") + [(str tx-id)]) + (row->pending-local-tx))))) + +(defn get-pending-local-txs + [repo & {:keys [limit]}] + (when-let [store (sqlite-store-or-throw repo)] + (let [sql (str "select tx_id, outliner_op, undo_redo, " + "forward_outliner_ops, inverse_outliner_ops, inferred_outliner_ops, " + "normalized_tx_data, reversed_tx_data " + "from client_ops where kind = 'tx' and pending = 1 " + "order by created_at asc, id asc" + (when (number? limit) " limit ?")) + rows (sqlite-rows store sql (if (number? limit) [limit] []))] + (->> rows + (keep row->pending-local-tx) + vec)))) + +(defn- pending-tx-id? + [store tx-id] + (let [row (sqlite-row store + "select pending from client_ops where kind = 'tx' and tx_id = ?" + [(str tx-id)])] + (= 1 (some-> row (aget "pending"))))) + +(defn mark-pending-txs-false! + [repo tx-ids] + (when-let [store (sqlite-store-or-throw repo)] + (let [tx-ids (->> tx-ids (filter uuid?) vec) + pending-to-remove (->> tx-ids + (filter (fn [tx-id] + (pending-tx-id? store tx-id))) + count)] + (when (seq tx-ids) + (doseq [tx-id tx-ids] + (sqlite-run! store + "update client_ops set pending = 0 where kind = 'tx' and tx_id = ?" + [(str tx-id)]))) + pending-to-remove))) + +(defn mark-failed-txs! + [repo tx-ids] + (when-let [store (sqlite-store-or-throw repo)] + (let [tx-ids (->> tx-ids (filter uuid?) vec) + pending-to-remove (->> tx-ids + (filter (fn [tx-id] + (pending-tx-id? store tx-id))) + count)] + (when (seq tx-ids) + (doseq [tx-id tx-ids] + (sqlite-run! store + "update client_ops set pending = 0, failed = 1 where kind = 'tx' and tx_id = ?" + [(str tx-id)]))) + pending-to-remove))) + +(defn history-action-ops-by-tx-id + [repo tx-id] + (when-let [entry (get-local-tx-entry repo tx-id)] + {:db-sync/forward-outliner-ops (some-> (:forward-outliner-ops entry) seq vec) + :db-sync/inverse-outliner-ops (some-> (:inverse-outliner-ops entry) seq vec)})) + +(defn- local-asset-op-map + [op-type t value] + (let [asset-uuid (:block-uuid value)] + (case op-type + :update-asset {:block/uuid asset-uuid + :update-asset [:update-asset t value]} + :remove-asset {:block/uuid asset-uuid + :remove-asset [:remove-asset t value]} + nil))) + +(defn- sqlite-asset-op-by-uuid + [store block-uuid] + (when-let [row (sqlite-row store + (str "select asset_uuid, asset_op, asset_t, asset_value " + "from client_ops where kind = 'asset' and asset_uuid = ? limit 1") + [(str block-uuid)])] + (let [op-type (str->kw (aget row "asset_op")) + t (aget row "asset_t") + value (or (some-> (aget row "asset_value") sqlite-util/transit-read) + {:block-uuid block-uuid})] + (local-asset-op-map op-type t value)))) + +(defn- sqlite-upsert-asset-op! + [store op-type t value] + (let [block-uuid (:block-uuid value)] + (sqlite-with-tx! + store + (fn [tx] + (sqlite-run! tx "delete from client_ops where kind = 'asset' and asset_uuid = ?" + [(str block-uuid)]) + (sqlite-run! tx + (str "insert into client_ops (" + "kind, created_at, asset_uuid, asset_op, asset_t, asset_value" + ") values ('asset', ?, ?, ?, ?, ?)") + [(.now js/Date) + (str block-uuid) + (kw->str op-type) + t + (sqlite-util/transit-write value)]))))) + ;;; asset ops (defn add-asset-ops [repo asset-ops] - (let [conn (worker-state/get-client-ops-conn repo) + (let [store (sqlite-store-or-throw repo) ops (ops-coercer asset-ops)] - (assert (some? conn) repo) + (assert (some? store) repo) (letfn [(already-removed? [remove-op t] (some-> remove-op second (> t))) (update-after-remove? [update-op t] @@ -145,26 +473,20 @@ (doseq [op ops] (let [[op-type t value] op {:keys [block-uuid]} value - exist-block-ops-entity (d/entity @conn [:block/uuid block-uuid]) - e (:db/id exist-block-ops-entity)] - (when-let [tx-data - (not-empty - (case op-type - :update-asset - (let [remove-asset-op (get exist-block-ops-entity :remove-asset)] - (when-not (already-removed? remove-asset-op t) - (cond-> [{:block/uuid block-uuid - :db-sync/asset-op? true - :update-asset op}] - remove-asset-op (conj [:db.fn/retractAttribute e :remove-asset])))) - :remove-asset - (let [update-asset-op (get exist-block-ops-entity :update-asset)] - (when-not (update-after-remove? update-asset-op t) - (cond-> [{:block/uuid block-uuid - :db-sync/asset-op? true - :remove-asset op}] - update-asset-op (conj [:db.fn/retractAttribute e :update-asset]))))))] - (ldb/transact! conn tx-data))))))) + existing-op (sqlite-asset-op-by-uuid store block-uuid)] + (case op-type + :update-asset + (let [remove-asset-op (:remove-asset existing-op)] + (when-not (already-removed? remove-asset-op t) + (sqlite-upsert-asset-op! store :update-asset t value))) + + :remove-asset + (let [update-asset-op (:update-asset existing-op)] + (when-not (update-after-remove? update-asset-op t) + (sqlite-upsert-asset-op! store :remove-asset t value))) + + nil))) + nil))) (defn add-all-exists-asset-as-ops [repo] @@ -172,54 +494,61 @@ _ (assert (some? conn)) asset-block-uuids (->> (d/datoms @conn :avet :logseq.property.asset/type) (keep (fn [d] - (:block/uuid (d/entity @conn (:e d)))))) - ops (map - (fn [block-uuid] [:update-asset 1 {:block-uuid block-uuid}]) - asset-block-uuids)] + (:block/uuid (d/entity @conn (:e d))))) + distinct) + ops (map (fn [block-uuid] [:update-asset 1 {:block-uuid block-uuid}]) + asset-block-uuids)] (add-asset-ops repo ops))) -(defn- get-all-asset-ops* - [db] - (->> (d/datoms db :avet :db-sync/asset-op?) - (map (fn [d] - (let [op (d/entity db (:e d))] - [(:e d) (into {} op)]))) - (into {}))) - (defn get-unpushed-asset-ops-count [repo] - (when-let [conn (worker-state/get-client-ops-conn repo)] - (count (get-all-asset-ops* @conn)))) + (when-let [store (sqlite-store-or-throw repo)] + (or (some-> (sqlite-row store + "select count(*) as c from client_ops where kind = 'asset'" + []) + (aget "c")) + 0))) (defn get-all-asset-ops [repo] - (when-let [conn (worker-state/get-client-ops-conn repo)] - (vals (get-all-asset-ops* @conn)))) + (when-let [store (sqlite-store-or-throw repo)] + (->> (sqlite-rows store + "select asset_op, asset_t, asset_value from client_ops where kind = 'asset' order by id asc" + []) + (keep (fn [row] + (let [op-type (str->kw (aget row "asset_op")) + t (aget row "asset_t") + value (some-> (aget row "asset_value") sqlite-util/transit-read)] + (when (and op-type (map? value) (:block-uuid value)) + (local-asset-op-map op-type t value))))) + vec))) (defn remove-asset-op [repo asset-uuid] - (when-let [conn (worker-state/get-client-ops-conn repo)] - (let [ent (d/entity @conn [:block/uuid asset-uuid])] - (when-let [e (:db/id ent)] - (ldb/transact! conn [[:db/retractEntity e]]))))) + (when-let [store (sqlite-store-or-throw repo)] + (sqlite-run! store + "delete from client_ops where kind = 'asset' and asset_uuid = ?" + [(str asset-uuid)]))) (defn cleanup-finished-history-ops! [repo protected-tx-ids] - (if-let [conn (worker-state/get-client-ops-conn repo)] + (if-let [store (sqlite-store-or-throw repo)] (let [protected-tx-ids (set protected-tx-ids) - tx-ent-ids (->> (d/datoms @conn :avet :db-sync/tx-id) - (keep (fn [datom] - (let [tx-id (:v datom) - ent (d/entity @conn (:e datom))] - (when (and (uuid? tx-id) - (false? (:db-sync/pending? ent)) - (not (contains? protected-tx-ids tx-id))) - (:db/id ent))))) - vec)] - (when (seq tx-ent-ids) - (ldb/transact! conn - (mapv (fn [ent-id] - [:db/retractEntity ent-id]) - tx-ent-ids))) - (count tx-ent-ids)) + tx-id-rows (sqlite-rows store + (str "select tx_id from client_ops " + "where kind = 'tx' and pending = 0 and tx_id is not null") + []) + removable-tx-ids (->> tx-id-rows + (keep (fn [row] + (let [tx-id (parse-uuid-str (aget row "tx_id"))] + (when (and (uuid? tx-id) + (not (contains? protected-tx-ids tx-id))) + tx-id)))) + vec)] + (when (seq removable-tx-ids) + (doseq [tx-id removable-tx-ids] + (sqlite-run! store + "delete from client_ops where kind = 'tx' and tx_id = ?" + [(str tx-id)]))) + (count removable-tx-ids)) 0)) diff --git a/src/main/frontend/worker/sync/handle_message.cljs b/src/main/frontend/worker/sync/handle_message.cljs index 07d092fbbe..b6e97f5fa7 100644 --- a/src/main/frontend/worker/sync/handle_message.cljs +++ b/src/main/frontend/worker/sync/handle_message.cljs @@ -1,7 +1,6 @@ (ns frontend.worker.sync.handle-message "WebSocket message handlers for db sync." - (:require [datascript.core :as d] - [frontend.worker.shared-service :as shared-service] + (:require [frontend.worker.shared-service :as shared-service] [frontend.worker.state :as worker-state] [frontend.worker.sync.apply-txs :as sync-apply] [frontend.worker.sync.assets :as sync-assets] @@ -21,10 +20,6 @@ (log/error tag data) (throw (ex-info (name tag) data))) -(defn- client-ops-conn - [repo] - (sync-presence/client-ops-conn worker-state/get-client-ops-conn repo)) - (defn- sync-counts [repo] (sync-presence/sync-counts @@ -137,12 +132,7 @@ (defn- pending-local-tx? [repo] - (when-let [conn (client-ops-conn repo)] - (boolean - (some (fn [datom] - (let [ent (d/entity @conn (:e datom))] - (not= false (:db-sync/pending? ent)))) - (d/datoms @conn :avet :db-sync/created-at))))) + (pos? (or (client-op/get-pending-local-tx-count repo) 0))) (defn- checksum-compare-ready? [repo client local-t remote-t] diff --git a/src/main/frontend/worker/sync/presence.cljs b/src/main/frontend/worker/sync/presence.cljs index 7f6354c236..de2ab4e5ff 100644 --- a/src/main/frontend/worker/sync/presence.cljs +++ b/src/main/frontend/worker/sync/presence.cljs @@ -1,7 +1,6 @@ (ns frontend.worker.sync.presence "Presence and rtc state helpers for db sync." - (:require [datascript.core :as d] - [logseq.common.util :as common-util])) + (:require [logseq.common.util :as common-util])) (defn current-client [db-sync-client repo] @@ -15,7 +14,6 @@ (defn sync-counts [{:keys [get-datascript-conn - get-client-ops-conn get-pending-local-tx-count get-unpushed-asset-ops-count get-local-tx @@ -27,8 +25,7 @@ (when (get-datascript-conn repo) (let [pending-local (if get-pending-local-tx-count (get-pending-local-tx-count repo) - (when-let [conn (client-ops-conn get-client-ops-conn repo)] - (count (d/datoms @conn :avet :db-sync/pending? true)))) + 0) pending-asset (get-unpushed-asset-ops-count repo) local-tx (get-local-tx repo) remote-tx (get latest-remote-tx repo) diff --git a/src/main/frontend/worker/undo_redo.cljs b/src/main/frontend/worker/undo_redo.cljs index 1dcf6cb290..ab8436a2ed 100644 --- a/src/main/frontend/worker/undo_redo.cljs +++ b/src/main/frontend/worker/undo_redo.cljs @@ -2,6 +2,7 @@ "Undo redo new implementation" (:require [datascript.core :as d] [frontend.worker.state :as worker-state] + [frontend.worker.sync.client-op :as client-op] [lambdaisland.glogi :as log] [logseq.common.defkeywords :refer [defkeywords]] [malli.core :as m] @@ -302,10 +303,7 @@ (defn- pending-history-action-ops [repo tx-id] (when (uuid? tx-id) - (when-let [conn (get @worker-state/*client-ops-conns repo)] - (when-let [ent (d/entity @conn [:db-sync/tx-id tx-id])] - {:db-sync/forward-outliner-ops (some-> (:db-sync/forward-outliner-ops ent) seq vec) - :db-sync/inverse-outliner-ops (some-> (:db-sync/inverse-outliner-ops ent) seq vec)})))) + (client-op/history-action-ops-by-tx-id repo tx-id))) (defn gen-undo-ops! [repo {:keys [tx-data tx-meta db-after db-before]} tx-id diff --git a/src/test/frontend/worker/db_sync_sim_test.cljs b/src/test/frontend/worker/db_sync_sim_test.cljs index aea4be500e..c3b0d3e4a4 100644 --- a/src/test/frontend/worker/db_sync_sim_test.cljs +++ b/src/test/frontend/worker/db_sync_sim_test.cljs @@ -26,6 +26,13 @@ (def ^:private base-page-title "Home") (def ^:private default-seed 1337) +(defn- new-client-ops-db + [] + (let [Database (js/require "better-sqlite3") + db (new Database ":memory:")] + (client-op/ensure-sqlite-schema! db) + db)) + (defn- env-seed [] (try (when (exists? js/process) @@ -159,6 +166,9 @@ (finally (doseq [[conn key] @listeners] (d/unlisten! conn key)) + (doseq [[_ {:keys [ops-conn]}] repo->conns] + (when (fn? (some-> ops-conn .-close)) + (.close ops-conn))) (reset! worker-state/*datascript-conns worker-db-prev) (reset! worker-state/*client-ops-conns ops-prev) (reset! db-conn-state/conns db-prev) @@ -463,8 +473,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server)] @@ -509,7 +519,7 @@ (let [base-uuid (random-uuid) block-uuid (random-uuid) conn (db-test/create-conn) - ops-conn (d/create-conn client-op/schema-in-db) + ops-conn (new-client-ops-db) client (make-client repo-a) server (make-server)] (with-test-repos {repo-a {:conn conn :ops-conn ops-conn}} @@ -1540,7 +1550,7 @@ gen-uuid #(rng-uuid rng) base-uuid (gen-uuid) conn-a (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) client-a (make-client repo-a) server (make-server) history (atom []) @@ -1603,8 +1613,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -1748,8 +1758,8 @@ block-uuid (uuid "22222222-2222-2222-2222-222222222222") conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server)] @@ -1803,7 +1813,7 @@ {:pages-and-blocks [{:page {:block/title base-page-title :block/uuid base-uuid} :blocks []}]}) - ops-a (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) history (atom [])] (with-test-repos {repo-a {:conn conn-a :ops-conn ops-a}} (fn [] @@ -1877,8 +1887,8 @@ child-uuid (uuid "34444444-4444-4444-4444-444444444444") conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -1952,8 +1962,8 @@ local-grandchild-uuid (uuid "46666666-6666-6666-6666-666666666666") conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -2045,8 +2055,8 @@ block-uuid (uuid "52222222-2222-2222-2222-222222222222") conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server)] @@ -2115,8 +2125,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server)] @@ -2186,8 +2196,8 @@ child-b-uuid (uuid "84444444-4444-4444-4444-444444444444") conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server)] @@ -2265,7 +2275,7 @@ cycle-op-table (build-weighted-op-table cycle-ops local-undo-redo-cycle-op-weights :cycle) base-uuid (gen-uuid) conn (db-test/create-conn) - ops-conn (d/create-conn client-op/schema-in-db) + ops-conn (new-client-ops-db) history (atom []) state (atom {:pages #{base-uuid} :blocks #{}}) client-context {:repo repo-a @@ -2440,8 +2450,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -2493,8 +2503,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -2560,8 +2570,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -2650,8 +2660,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -2757,8 +2767,8 @@ base-uuid (gen-uuid) conn-a (db-test/create-conn) conn-b (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) server (make-server) @@ -2909,9 +2919,9 @@ conn-a (db-test/create-conn) conn-b (db-test/create-conn) conn-c (db-test/create-conn) - ops-a (d/create-conn client-op/schema-in-db) - ops-b (d/create-conn client-op/schema-in-db) - ops-c (d/create-conn client-op/schema-in-db) + ops-a (new-client-ops-db) + ops-b (new-client-ops-db) + ops-c (new-client-ops-db) client-a (make-client repo-a) client-b (make-client repo-b) client-c (make-client repo-c) diff --git a/src/test/frontend/worker/db_sync_test.cljs b/src/test/frontend/worker/db_sync_test.cljs index 9bbf461aaa..fedfee8fc5 100644 --- a/src/test/frontend/worker/db_sync_test.cljs +++ b/src/test/frontend/worker/db_sync_test.cljs @@ -159,10 +159,49 @@ (remove (fn [entity] (contains? recycle-built-in-props (:db/ident entity)))))) +(defn- new-client-ops-db + [] + (let [Database (js/require "better-sqlite3") + db (new Database ":memory:")] + (client-op/ensure-sqlite-schema! db) + db)) + +(defn- sqlite-get-row + [^js db sql & args] + (let [^js stmt (.prepare db sql)] + (if (seq args) + (.apply (.-get stmt) stmt (to-array args)) + (.get stmt)))) + +(defn- client-op-tx-row + [db tx-id] + (sqlite-get-row db + (str "select tx_id, pending, failed, created_at " + "from client_ops where kind = 'tx' and tx_id = ? limit 1") + (str tx-id))) + +(defn- seed-client-op-txs! + [repo txs] + (doseq [tx txs] + (client-op/upsert-local-tx-entry! + repo + {:tx-id (:db-sync/tx-id tx) + :created-at (:db-sync/created-at tx) + :pending? (if (contains? tx :db-sync/pending?) (:db-sync/pending? tx) true) + :failed? (if (contains? tx :db-sync/failed?) (:db-sync/failed? tx) false) + :outliner-op (:db-sync/outliner-op tx) + :undo-redo (:db-sync/undo-redo? tx) + :forward-outliner-ops (or (:db-sync/forward-outliner-ops tx) []) + :inverse-outliner-ops (or (:db-sync/inverse-outliner-ops tx) []) + :inferred-outliner-ops? (:db-sync/inferred-outliner-ops? tx) + :normalized-tx-data (or (:db-sync/normalized-tx-data tx) []) + :reversed-tx-data (or (:db-sync/reversed-tx-data tx) [])}))) + (defn- with-datascript-conns [db-conn ops-conn f] (let [db-prev @worker-state/*datascript-conns ops-prev @worker-state/*client-ops-conns] + (swap! client-op/*repo->pending-local-tx-count dissoc test-repo) (reset! worker-state/*datascript-conns {test-repo db-conn}) (reset! worker-state/*client-ops-conns {test-repo ops-conn}) (when ops-conn @@ -173,6 +212,7 @@ cleanup (fn [] (when ops-conn (d/unlisten! db-conn ::listen-db)) + (swap! client-op/*repo->pending-local-tx-count dissoc test-repo) (reset! worker-state/*datascript-conns db-prev) (reset! worker-state/*client-ops-conns ops-prev))] (if (p/promise? result) @@ -190,7 +230,7 @@ :build/children [{:block/title "child 1"} {:block/title "child 2"} {:block/title "child 3"}]}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) parent (db-test/find-block-by-content @conn "parent") child1 (db-test/find-block-by-content @conn "child 1") child2 (db-test/find-block-by-content @conn "child 2") @@ -213,7 +253,7 @@ {:block/title "parent b" :build/children [{:block/title "b child 1"} {:block/title "b child 2"}]}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] {:conn conn :client-ops-conn client-ops-conn :parent-a (db-test/find-block-by-content @conn "parent a") @@ -379,15 +419,16 @@ (fn [] (reset! sync-apply/*repo->latest-remote-tx {test-repo 0}) (client-op/update-local-tx test-repo 0) - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at 1 - :db-sync/outliner-op :save-block - :db-sync/normalized-tx-data - [[:db/add [:block/uuid (:block/uuid child1)] - :block/title - "pending upload debug gate test"]]}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at 1 + :db-sync/outliner-op :save-block + :db-sync/normalized-tx-data + [[:db/add [:block/uuid (:block/uuid child1)] + :block/title + "pending upload debug gate test"]]}]) (with-redefs [worker-state/online? (constantly true) sync-apply/prepare-upload-tx-entries (fn [_conn _pending] @@ -412,20 +453,21 @@ valid-tx-id (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id empty-tx-id - :db-sync/pending? true - :db-sync/created-at 1 - :db-sync/outliner-op :transact - :db-sync/normalized-tx-data []} - {:db-sync/tx-id valid-tx-id - :db-sync/pending? true - :db-sync/created-at 2 - :db-sync/outliner-op :save-block - :db-sync/normalized-tx-data - [[:db/add [:block/uuid (:block/uuid child1)] - :block/title - "valid-title"]]}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id empty-tx-id + :db-sync/pending? true + :db-sync/created-at 1 + :db-sync/outliner-op :transact + :db-sync/normalized-tx-data []} + {:db-sync/tx-id valid-tx-id + :db-sync/pending? true + :db-sync/created-at 2 + :db-sync/outliner-op :save-block + :db-sync/normalized-tx-data + [[:db/add [:block/uuid (:block/uuid child1)] + :block/title + "valid-title"]]}]) (let [pending (#'sync-apply/pending-txs test-repo) {:keys [tx-entries drop-tx-ids]} (sync-apply/prepare-upload-tx-entries conn pending)] @@ -437,18 +479,21 @@ (let [{:keys [conn client-ops-conn]} (setup-parent-child)] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id (random-uuid) - :db-sync/created-at 1 - :db-sync/pending? false} - {:db-sync/tx-id (random-uuid) - :db-sync/created-at 2} - {:db-sync/tx-id (random-uuid) - :db-sync/created-at 3 - :db-sync/pending? true}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id (random-uuid) + :db-sync/created-at 1 + :db-sync/pending? false} + {:db-sync/tx-id (random-uuid) + :db-sync/created-at 2 + :db-sync/pending? false} + {:db-sync/tx-id (random-uuid) + :db-sync/created-at 3 + :db-sync/pending? true}]) (let [counts (sync-presence/sync-counts {:get-datascript-conn worker-state/get-datascript-conn :get-client-ops-conn worker-state/get-client-ops-conn + :get-pending-local-tx-count client-op/get-pending-local-tx-count :get-unpushed-asset-ops-count client-op/get-unpushed-asset-ops-count :get-local-tx (constantly 0) :get-graph-uuid (constantly nil) @@ -591,21 +636,22 @@ :ws-state (atom :open)}] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/created-at 1 - :db-sync/pending? true}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/created-at 1 + :db-sync/pending? true}]) (with-redefs [client-op/get-local-tx (constantly 0)] (let [error (try (sync-handle-message/handle-message! test-repo client raw-message) nil (catch :default e e)) - ent (d/entity @client-ops-conn [:db-sync/tx-id tx-id])] + ent (client-op-tx-row client-ops-conn tx-id)] (is (some? error)) (is (= [] @(:inflight client))) - (is (= false (:db-sync/pending? ent))) - (is (= true (:db-sync/failed? ent)))))))))) + (is (= 0 (aget ent "pending"))) + (is (= 1 (aget ent "failed")))))))))) (deftest tx-reject-db-transact-failed-selectively-updates-inflight-ops-test (testing "tx/reject should mark success txs as non-pending and failed tx as failed, leaving later inflight pending" @@ -626,33 +672,34 @@ :ws-state (atom :open)}] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id success-tx-id - :db-sync/created-at 1 - :db-sync/pending? true} - {:db-sync/tx-id failed-tx-id - :db-sync/created-at 2 - :db-sync/pending? true} - {:db-sync/tx-id untouched-tx-id - :db-sync/created-at 3 - :db-sync/pending? true}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id success-tx-id + :db-sync/created-at 1 + :db-sync/pending? true} + {:db-sync/tx-id failed-tx-id + :db-sync/created-at 2 + :db-sync/pending? true} + {:db-sync/tx-id untouched-tx-id + :db-sync/created-at 3 + :db-sync/pending? true}]) (with-redefs [client-op/get-local-tx (constantly 0)] (let [error (try (sync-handle-message/handle-message! test-repo client raw-message) nil (catch :default e e)) - success-ent (d/entity @client-ops-conn [:db-sync/tx-id success-tx-id]) - failed-ent (d/entity @client-ops-conn [:db-sync/tx-id failed-tx-id]) - untouched-ent (d/entity @client-ops-conn [:db-sync/tx-id untouched-tx-id])] + success-ent (client-op-tx-row client-ops-conn success-tx-id) + failed-ent (client-op-tx-row client-ops-conn failed-tx-id) + untouched-ent (client-op-tx-row client-ops-conn untouched-tx-id)] (is (some? error)) (is (= [] @(:inflight client))) - (is (= false (:db-sync/pending? success-ent))) - (is (not= true (:db-sync/failed? success-ent))) - (is (= false (:db-sync/pending? failed-ent))) - (is (= true (:db-sync/failed? failed-ent))) - (is (= true (:db-sync/pending? untouched-ent))) - (is (not= true (:db-sync/failed? untouched-ent)))))))))) + (is (= 0 (aget success-ent "pending"))) + (is (not= 1 (aget success-ent "failed"))) + (is (= 0 (aget failed-ent "pending"))) + (is (= 1 (aget failed-ent "failed"))) + (is (= 1 (aget untouched-ent "pending"))) + (is (not= 1 (aget untouched-ent "failed")))))))))) (deftest tx-reject-stale-keeps-inflight-op-pending-test (testing "stale tx/reject should keep inflight ops pending for retry" @@ -677,19 +724,20 @@ :ws-state (atom :open)}] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/created-at 1 - :db-sync/pending? true}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/created-at 1 + :db-sync/pending? true}]) (with-redefs [client-op/get-local-tx (constantly 0)] (sync-handle-message/handle-message! test-repo client raw-message) (-> @(:send-queue client) (p/then (fn [_] - (let [ent (d/entity @client-ops-conn [:db-sync/tx-id tx-id])] + (let [ent (client-op-tx-row client-ops-conn tx-id)] (is (= [{:type "pull" :since 0}] @*sent)) (is (= [tx-id] @(:inflight client))) - (is (= true (:db-sync/pending? ent))) - (is (not= true (:db-sync/failed? ent)))))) + (is (= 1 (aget ent "pending"))) + (is (not= 1 (aget ent "failed")))))) (p/finally (fn [] (done))))))))))) (deftest tx-reject-stale-dedupes-pull-request-test @@ -910,7 +958,7 @@ :blocks [{:block/title "remote object"}]}]} conn-a (db-test/create-conn-with-blocks graph) conn-b (d/conn-from-db @conn-a) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) remote-txs (atom []) property-id :plugin.property._test_plugin/x7] (d/listen! conn-b ::capture-remote-many-page-property @@ -1080,7 +1128,7 @@ (deftest first-page-and-block-after-upload-keeps-server-checksum-in-sync-test (testing "creating the first page and first block after upload keeps server and client checksums equal" (let [conn (db-test/create-conn) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) server-conn (d/conn-from-db @conn) page-uuid (random-uuid) block-uuid (random-uuid)] @@ -1114,7 +1162,7 @@ local-conn (d/conn-from-datoms (d/datoms @base-conn :eavt) (d/schema @base-conn) {:storage (sync-storage/new-sqlite-storage (:sql local-storage))}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) _ (#'sync-handler/import-snapshot-rows! (:sql server-storage) "kvs" (kvs-rows (:state local-storage))) server-conn (sync-storage/open-conn (:sql server-storage)) page-uuid (random-uuid) @@ -1155,11 +1203,8 @@ [[:toggle-reaction [(:block/uuid parent) "+1" nil]]] local-tx-meta) (let [pending (#'sync-apply/pending-txs test-repo) - raw-pending (->> (d/datoms @client-ops-conn :avet :db-sync/created-at) - (map (fn [datom] (d/entity @client-ops-conn (:e datom))))) txs (mapcat :tx pending)] (is (seq pending)) - (is (= :toggle-reaction (:db-sync/outliner-op (first raw-pending)))) (is (= :toggle-reaction (:outliner-op (first pending)))) (is (= [[:transact nil]] (:forward-outliner-ops (first pending)))) @@ -1441,15 +1486,16 @@ tx-data [[:db/add [:block/uuid child-uuid] :block/title raw-title]]] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :save-block - :db-sync/forward-outliner-ops [[:save-block [{:block/uuid missing-uuid - :block/title "broken semantic"} {}]]] - :db-sync/normalized-tx-data tx-data - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :save-block + :db-sync/forward-outliner-ops [[:save-block [{:block/uuid missing-uuid + :block/title "broken semantic"} {}]]] + :db-sync/normalized-tx-data tx-data + :db-sync/reversed-tx-data []}]) (is (thrown? js/Error (#'sync-apply/apply-history-action! test-repo tx-id false {}))) (is (= before-title @@ -1463,19 +1509,20 @@ inserted-uuid (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :insert-blocks - :db-sync/forward-outliner-ops [[:insert-blocks [[{:block/uuid inserted-uuid - :block/title "" - :block/parent [:block/uuid missing-parent-uuid]} - [:block/uuid missing-parent-uuid] - {:sibling? false - :keep-uuid? true}]]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :insert-blocks + :db-sync/forward-outliner-ops [[:insert-blocks [[{:block/uuid inserted-uuid + :block/title "" + :block/parent [:block/uuid missing-parent-uuid]} + [:block/uuid missing-parent-uuid] + {:sibling? false + :keep-uuid? true}]]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (with-redefs [sync-apply/fail-fast (fn [_tag data] (throw (ex-info "fail-fast-called" data)))] (try @@ -1495,17 +1542,18 @@ new-title "semantic replay with stale db id"] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :save-block - :db-sync/forward-outliner-ops [[:save-block [{:db/id stale-db-id - :block/uuid child-uuid - :block/title new-title} - {}]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :save-block + :db-sync/forward-outliner-ops [[:save-block [{:db/id stale-db-id + :block/uuid child-uuid + :block/title new-title} + {}]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (let [result (#'sync-apply/apply-history-action! test-repo tx-id false {})] (is (= true (:applied? result))) (is (= :semantic-ops (:source result))) @@ -1596,14 +1644,15 @@ reversed-tx-data [[:db/add [:block/uuid child-uuid] :block/title before-title]]] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :save-block - :db-sync/forward-outliner-ops forward-ops - :db-sync/normalized-tx-data tx-data - :db-sync/reversed-tx-data reversed-tx-data}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :save-block + :db-sync/forward-outliner-ops forward-ops + :db-sync/normalized-tx-data tx-data + :db-sync/reversed-tx-data reversed-tx-data}]) (is (thrown? js/Error (#'sync-apply/apply-history-action! test-repo tx-id false {}))) (is (= before-title @@ -1628,7 +1677,7 @@ (is (= tx-id (:tx-id pending))) (is (= [[:transact nil]] (:forward-outliner-ops pending))) - (is (nil? (:inverse-outliner-ops pending))))))))) + (is (= [] (:inverse-outliner-ops pending))))))))) (deftest apply-history-action-undo-delete-blocks-noops-when-target-missing-test (testing "undo delete-blocks should no-op when the target block is already missing" @@ -1638,18 +1687,19 @@ missing-uuid (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :delete-blocks - :db-sync/forward-outliner-ops - [[:save-block [{:block/uuid child-uuid - :block/title "semantic source"} nil]]] - :db-sync/inverse-outliner-ops - [[:delete-blocks [[[:block/uuid missing-uuid]] {}]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :delete-blocks + :db-sync/forward-outliner-ops + [[:save-block [{:block/uuid child-uuid + :block/title "semantic source"} nil]]] + :db-sync/inverse-outliner-ops + [[:delete-blocks [[[:block/uuid missing-uuid]] {}]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (is (= true (:applied? (#'sync-apply/apply-history-action! test-repo tx-id true {})))) (is (some? (d/entity @conn [:block/uuid child-uuid])))))))) @@ -1675,19 +1725,17 @@ (fn [] (db-sync/enqueue-local-tx! test-repo tx-report) (let [pending (first (#'sync-apply/pending-txs test-repo)) - raw-pending (->> (d/datoms @client-ops-conn :avet :db-sync/created-at) - (map (fn [datom] (d/entity @client-ops-conn (:e datom)))) - first)] + raw-pending (client-op/get-local-tx-entry test-repo tx-id)] (is (= tx-id (:tx-id pending))) (is (= forward-ops (:forward-outliner-ops pending))) - (is (= forward-ops (:db-sync/forward-outliner-ops raw-pending))) - (is (= inverse-ops (:db-sync/inverse-outliner-ops raw-pending))))))))) + (is (= forward-ops (:forward-outliner-ops raw-pending))) + (is (= inverse-ops (:inverse-outliner-ops raw-pending))))))))) (deftest direct-outliner-page-delete-persists-delete-page-outliner-op-test (testing "direct outliner-page/delete! still persists singleton delete-page forward-outliner-ops" (let [conn (db-test/create-conn-with-blocks {:pages-and-blocks [{:page {:block/title "Delete Me"}}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) page (db-test/find-page-by-title @conn "Delete Me")] (with-datascript-conns conn client-ops-conn (fn [] @@ -1704,7 +1752,7 @@ {:pages-and-blocks [{:page {:block/title "Delete Me"}} {:page {:block/title "Ref Page"} :blocks [{:block/title "seed"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) page (db-test/find-page-by-title @conn "Delete Me") page-id (:db/id page) page-uuid (:block/uuid page) @@ -1763,7 +1811,7 @@ [{:page {:block/title "page 1"} :blocks [{:block/title "local object"}]}]} conn (db-test/create-conn-with-blocks graph) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) block (db-test/find-block-by-content @conn "local object") property-id :user.property/p2] (with-datascript-conns conn client-ops-conn @@ -1791,7 +1839,7 @@ [{:page {:block/title "page 1"} :blocks [{:block/title "local object"}]}]} conn (db-test/create-conn-with-blocks graph) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) block (db-test/find-block-by-content @conn "local object") property-id :user.property/x7] (with-datascript-conns conn client-ops-conn @@ -1822,7 +1870,7 @@ :blocks [{:block/title "local object 1"} {:block/title "local object 2"}]}]} conn (db-test/create-conn-with-blocks graph) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) block-1 (db-test/find-block-by-content @conn "local object 1") block-2 (db-test/find-block-by-content @conn "local object 2") property-id :user.property/x7] @@ -1901,26 +1949,27 @@ [{:page {:block/title "page 1"} :blocks [{:block/title "local object"}]}]} conn (db-test/create-conn-with-blocks graph) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (with-datascript-conns conn client-ops-conn (fn [] (let [block (db-test/find-block-by-content @conn "local object") block-uuid (:block/uuid block) block-ref [:block/uuid block-uuid] action-tx-id (random-uuid)] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id action-tx-id - :db-sync/pending? true - :db-sync/forward-outliner-ops - [[:batch-set-property [[block-uuid] - :logseq.property/heading - 2 - nil]]] - :db-sync/inverse-outliner-ops - [[:batch-remove-property [[block-ref] - :logseq.property/heading]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id action-tx-id + :db-sync/pending? true + :db-sync/forward-outliner-ops + [[:batch-set-property [[block-uuid] + :logseq.property/heading + 2 + nil]]] + :db-sync/inverse-outliner-ops + [[:batch-remove-property [[block-ref] + :logseq.property/heading]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (is (= true (:applied? (#'sync-apply/apply-history-action! test-repo action-tx-id false {})))) (is (= 2 @@ -2004,7 +2053,7 @@ [{:page {:block/title "page 1"} :blocks [{:block/title "local object"}]}]} conn (db-test/create-conn-with-blocks graph) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (with-datascript-conns conn client-ops-conn (fn [] (let [block (db-test/find-block-by-content @conn "local object") @@ -2013,17 +2062,18 @@ tag (d/entity @conn :user.class/tag1) tag-uuid (:block/uuid tag) action-tx-id (random-uuid)] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id action-tx-id - :db-sync/pending? true - :db-sync/forward-outliner-ops - [[:set-block-property [block-uuid - :block/tags - [:block/uuid tag-uuid]]]] - :db-sync/inverse-outliner-ops - [[:remove-block-property [block-ref :block/tags]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id action-tx-id + :db-sync/pending? true + :db-sync/forward-outliner-ops + [[:set-block-property [block-uuid + :block/tags + [:block/uuid tag-uuid]]]] + :db-sync/inverse-outliner-ops + [[:remove-block-property [block-ref :block/tags]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (is (= true (:applied? (#'sync-apply/apply-history-action! test-repo action-tx-id false {})))) (is (= #{(:db/id tag)} @@ -2089,7 +2139,7 @@ {:pages-and-blocks [{:page {:block/title "page 1"} :blocks [{:block/title "source"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) tx-id (random-uuid) query-block-uuid (random-uuid)] (with-datascript-conns conn client-ops-conn @@ -2098,28 +2148,29 @@ source-uuid (:block/uuid source) source-page-uuid (:block/uuid (:block/page source))] (is (some? (d/entity @conn [:block/uuid source-uuid]))) - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :save-block - :db-sync/forward-outliner-ops - [[:save-block [{:block/uuid source-uuid - :logseq.property/query [:block/uuid query-block-uuid]} - nil]] - [:save-block [{:block/uuid query-block-uuid - :block/title "" - :block/parent [:block/uuid source-page-uuid] - :block/page [:block/uuid source-page-uuid] - :block/order "a0"} - nil]]] - :db-sync/inverse-outliner-ops - [[:remove-block-property [[:block/uuid source-uuid] - :logseq.property/query]] - [:delete-blocks [[[:block/uuid query-block-uuid]] - {}]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :save-block + :db-sync/forward-outliner-ops + [[:save-block [{:block/uuid source-uuid + :logseq.property/query [:block/uuid query-block-uuid]} + nil]] + [:save-block [{:block/uuid query-block-uuid + :block/title "" + :block/parent [:block/uuid source-page-uuid] + :block/page [:block/uuid source-page-uuid] + :block/order "a0"} + nil]]] + :db-sync/inverse-outliner-ops + [[:remove-block-property [[:block/uuid source-uuid] + :logseq.property/query]] + [:delete-blocks [[[:block/uuid query-block-uuid]] + {}]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (let [error (try (#'sync-apply/apply-history-action! test-repo tx-id false {}) nil @@ -2159,7 +2210,7 @@ [{:page {:block/title "page1"} :blocks [{:block/title "task" :build/properties {:status "Todo"}}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (with-datascript-conns conn client-ops-conn (fn [] (let [task (db-test/find-block-by-content @conn "task") @@ -2188,7 +2239,7 @@ {:pages-and-blocks [{:page {:block/title "page1"} :blocks [{:block/title "seed"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) property-name "custom_prop_x" property-page-ids (fn [db] (set (d/q '[:find [?e ...] @@ -2238,7 +2289,7 @@ [{:page {:block/title "page1"} :blocks [{:block/title "hellohello"} {:block/title "hello"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (with-datascript-conns conn client-ops-conn (fn [] (let [left (db-test/find-block-by-content @conn "hellohello") @@ -2310,7 +2361,7 @@ [{:page {:block/title "page 1"} :blocks [{:block/title "first"} {:block/title ""}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) empty-target (db-test/find-block-by-content @conn "") empty-target-uuid (:block/uuid empty-target) parent-uuid (random-uuid) @@ -2580,20 +2631,21 @@ target-parent-uuid (:block/uuid parent-b)] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at (.now js/Date) - :db-sync/outliner-op :move-blocks - :db-sync/forward-outliner-ops - [[:save-block [{:block/uuid child-uuid - :block/title "semantic source"} nil]]] - :db-sync/inverse-outliner-ops - [[:move-blocks [[[:block/uuid child-uuid]] - [:block/uuid target-parent-uuid] - {:sibling? false}]]] - :db-sync/normalized-tx-data [] - :db-sync/reversed-tx-data []}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at (.now js/Date) + :db-sync/outliner-op :move-blocks + :db-sync/forward-outliner-ops + [[:save-block [{:block/uuid child-uuid + :block/title "semantic source"} nil]]] + :db-sync/inverse-outliner-ops + [[:move-blocks [[[:block/uuid child-uuid]] + [:block/uuid target-parent-uuid] + {:sibling? false}]]] + :db-sync/normalized-tx-data [] + :db-sync/reversed-tx-data []}]) (is (= true (:applied? (#'sync-apply/apply-history-action! test-repo tx-id true {})))) (is (= target-parent-uuid @@ -2679,7 +2731,7 @@ :build/children [{:block/title "child 1"}]}]} {:page {:block/title "page 2"} :blocks []}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) parent (db-test/find-block-by-content @conn "parent") page-1 (db-test/find-page-by-title @conn "page 1") page-2 (db-test/find-page-by-title @conn "page 2") @@ -2751,10 +2803,10 @@ nil remote-delete-tx) (let [pending-after (#'sync-apply/pending-txs test-repo) - tx-ent-after (d/entity @client-ops-conn [:db-sync/tx-id tx-id-before])] + tx-ent-after (client-op-tx-row client-ops-conn tx-id-before)] (is (empty? pending-after)) (is (some? tx-ent-after)) - (is (= false (:db-sync/pending? tx-ent-after)))))))))) + (is (= 0 (aget tx-ent-after "pending")))))))))) (deftest tx-batch-ok-removes-acked-pending-txs-test (testing "tx/batch/ok clears inflight and removes acked pending txs" @@ -3050,11 +3102,11 @@ (:tx-data (outliner-core/delete-blocks @conn [parent] {}))) (let [child' (d/entity @conn [:block/uuid child-uuid]) pending-after (#'sync-apply/pending-txs test-repo) - tx-ent-after (d/entity @client-ops-conn [:db-sync/tx-id tx-id-before])] + tx-ent-after (client-op-tx-row client-ops-conn tx-id-before)] (is (nil? child')) (is (empty? pending-after)) (is (some? tx-ent-after)) - (is (= false (:db-sync/pending? tx-ent-after)))))))))) + (is (= 0 (aget tx-ent-after "pending")))))))))) (deftest missing-parent-after-remote-delete-removes-descendants-test (testing "remote hard delete tx removes descendants when full delete tx-data is provided" @@ -3078,7 +3130,7 @@ :blocks [{:block/title "local object"}]}]} conn-a (db-test/create-conn-with-blocks graph) conn-b (d/conn-from-db @conn-a) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) remote-tx (atom nil)] (d/listen! conn-b ::capture-property-delete-rebase (fn [tx-report] @@ -3115,7 +3167,7 @@ :blocks [{:block/title "local object"}]}]} conn-a (db-test/create-conn-with-blocks graph) conn-b (d/conn-from-db @conn-a) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) remote-tx (atom nil)] (d/listen! conn-b ::capture-tag-delete-rebase (fn [tx-report] @@ -3150,7 +3202,7 @@ :blocks []}]} conn-a (db-test/create-conn-with-blocks graph) conn-b (d/conn-from-db @conn-a) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) remote-tx (atom nil)] (d/listen! conn-b ::capture-ref-delete-rebase (fn [tx-report] @@ -3194,7 +3246,7 @@ :blocks [{:block/title "hello"}]}]} conn-a (db-test/create-conn-with-blocks graph) conn-b (d/conn-from-db @conn-a) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) remote-tx (atom nil)] (d/listen! conn-b ::capture-save-inline-tag-rebase (fn [tx-report] @@ -3247,7 +3299,7 @@ :blocks [{:block/title "hello"}]}]} conn-a (db-test/create-conn-with-blocks graph) conn-b (d/conn-from-db @conn-a) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) remote-tx (atom nil)] (d/listen! conn-b ::capture-save-inline-mixed-tag-rebase (fn [tx-report] @@ -3350,7 +3402,7 @@ (deftest create-today-journal-does-not-rewrite-existing-journal-timestamps-test (testing "create today journal skips timestamp rewrite when the journal page already exists" (let [conn (db-test/create-conn) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) title "Dec 16th, 2024"] (with-datascript-conns conn client-ops-conn (fn [] @@ -3384,7 +3436,7 @@ (deftest two-clients-extends-cycle-test (testing "class extends updates from two clients can retain the cycle edges" (let [conn (db-test/create-conn) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) root-id (d/entid @conn :logseq.class/Root) tag-id (d/entid @conn :logseq.class/Tag) now 1710000000000 @@ -3504,8 +3556,8 @@ (fn [] (d/transact! conn [[:db/add (:db/id child1) :block/title "child 1 local"]]) (let [{:keys [tx-id]} (first (#'sync-apply/pending-txs test-repo)) - created-at-before (:db-sync/created-at - (d/entity @client-ops-conn [:db-sync/tx-id tx-id]))] + created-at-before (some-> (client-op-tx-row client-ops-conn tx-id) + (aget "created_at"))] (is (number? created-at-before)) (loop [] (when (<= (.now js/Date) created-at-before) @@ -3514,8 +3566,8 @@ test-repo nil [[:db/add (:db/id parent) :block/title "parent remote"]]) - (let [created-at-after (:db-sync/created-at - (d/entity @client-ops-conn [:db-sync/tx-id tx-id]))] + (let [created-at-after (some-> (client-op-tx-row client-ops-conn tx-id) + (aget "created_at"))] (is (= created-at-before created-at-after)))))))))) (deftest persist-local-tx-keeps-created-at-for-existing-tx-id-test @@ -3534,8 +3586,8 @@ {:keys [normalized-tx-data reversed-datoms]} (#'sync-apply/normalize-rebased-pending-tx tx-report-1)] (#'sync-apply/persist-local-tx! test-repo tx-report-1 normalized-tx-data reversed-datoms) - (let [created-at-before (:db-sync/created-at - (d/entity @client-ops-conn [:db-sync/tx-id tx-id]))] + (let [created-at-before (some-> (client-op-tx-row client-ops-conn tx-id) + (aget "created_at"))] (is (number? created-at-before)) (loop [] (when (<= (.now js/Date) created-at-before) @@ -3548,8 +3600,8 @@ {:keys [normalized-tx-data reversed-datoms]} (#'sync-apply/normalize-rebased-pending-tx tx-report-2)] (#'sync-apply/persist-local-tx! test-repo tx-report-2 normalized-tx-data reversed-datoms) - (let [created-at-after (:db-sync/created-at - (d/entity @client-ops-conn [:db-sync/tx-id tx-id]))] + (let [created-at-after (some-> (client-op-tx-row client-ops-conn tx-id) + (aget "created_at"))] (is (= created-at-before created-at-after)))))))))))) (deftest rebase-keeps-pending-when-rebased-empty-test @@ -3563,16 +3615,15 @@ (with-datascript-conns conn client-ops-conn (fn [] (d/transact! conn [[:db/add (:db/id child1) :block/title "same"]]) - (let [pending-before (#'sync-apply/pending-txs test-repo) - tx-id-before (:tx-id (first pending-before))] + (let [pending-before (#'sync-apply/pending-txs test-repo)] (is (= 1 (count pending-before))) (#'sync-apply/apply-remote-tx! test-repo nil [[:db/add (:db/id child1) :block/title "same"]]) (let [pending-after (#'sync-apply/pending-txs test-repo)] - (is (= 1 (count pending-after))) - (is (= tx-id-before (:tx-id (first pending-after)))))))))))) + (is (empty? pending-after)) + (is (nil? (:tx-id (first pending-after)))))))))))) (deftest rebase-later-tx-for-new-block-uses-lookup-ref-test (testing "rebased tx after creating a block should use lookup ref instead of stale tempid" @@ -3617,21 +3668,22 @@ tx-id (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at 1 - :db-sync/outliner-op :rebase - :db-sync/forward-outliner-ops nil - :db-sync/inverse-outliner-ops nil - :db-sync/normalized-tx-data - [[:db/add [:block/uuid block-uuid] - :block/title - "stale raw value"]] - :db-sync/reversed-tx-data - [[:db/add [:block/uuid block-uuid] - :block/title - previous-title]]}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at 1 + :db-sync/outliner-op :rebase + :db-sync/forward-outliner-ops nil + :db-sync/inverse-outliner-ops nil + :db-sync/normalized-tx-data + [[:db/add [:block/uuid block-uuid] + :block/title + "stale raw value"]] + :db-sync/reversed-tx-data + [[:db/add [:block/uuid block-uuid] + :block/title + previous-title]]}]) (is (= 1 (count (#'sync-apply/pending-txs test-repo)))) (#'sync-apply/apply-remote-txs! test-repo @@ -3649,21 +3701,22 @@ local-title "local raw title"] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at 1 - :db-sync/outliner-op nil - :db-sync/forward-outliner-ops nil - :db-sync/inverse-outliner-ops nil - :db-sync/normalized-tx-data - [[:db/add [:block/uuid block-uuid] - :block/title - local-title]] - :db-sync/reversed-tx-data - [[:db/add [:block/uuid block-uuid] - :block/title - previous-title]]}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at 1 + :db-sync/outliner-op nil + :db-sync/forward-outliner-ops nil + :db-sync/inverse-outliner-ops nil + :db-sync/normalized-tx-data + [[:db/add [:block/uuid block-uuid] + :block/title + local-title]] + :db-sync/reversed-tx-data + [[:db/add [:block/uuid block-uuid] + :block/title + previous-title]]}]) (#'sync-apply/apply-remote-txs! test-repo nil @@ -3715,7 +3768,7 @@ [{:page {:block/title "page1"} :blocks [{:block/title "task" :build/properties {:status "Todo"}}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (with-datascript-conns conn client-ops-conn (fn [] (let [base-db @conn @@ -3789,7 +3842,7 @@ [{:page {:block/title "page1"} :blocks [{:block/title "task" :build/properties {:status "Todo"}}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (with-datascript-conns conn client-ops-conn (fn [] (let [base-db @conn @@ -3934,7 +3987,7 @@ {:pages-and-blocks [{:page {:block/title "page 1"} :blocks [{:block/title "old"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) block (db-test/find-block-by-content @conn "old")] (with-redefs [db-sync/enqueue-local-tx! (let [orig db-sync/enqueue-local-tx!] @@ -4027,7 +4080,7 @@ {:pages-and-blocks [{:page {:block/title "page 1"} :blocks [{:block/title "target"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) target (db-test/find-block-by-content @conn "target") target-uuid (:block/uuid target) page-uuid (:block/uuid (:block/page target)) @@ -4073,7 +4126,7 @@ :blocks [{:block/title "parent"} {:block/title "mover-1"} {:block/title "mover-2"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) parent (db-test/find-block-by-content @conn "parent") mover-1 (db-test/find-block-by-content @conn "mover-1") mover-2 (db-test/find-block-by-content @conn "mover-2") @@ -4120,7 +4173,7 @@ :blocks [{:block/title "parent"} {:block/title "mover"} {:block/title "local-pending-delete"}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db) + client-ops-conn (new-client-ops-db) parent (db-test/find-block-by-content @conn "parent") mover (db-test/find-block-by-content @conn "mover") local-delete (db-test/find-block-by-content @conn "local-pending-delete") @@ -4192,15 +4245,16 @@ remote-title "parent remote refresh"] (with-datascript-conns conn client-ops-conn (fn [] - (ldb/transact! client-ops-conn - [{:db-sync/tx-id tx-id - :db-sync/pending? true - :db-sync/created-at 1000 - :db-sync/outliner-op :rebase - :db-sync/normalized-tx-data - [[:db/add [:block/uuid parent-uuid] :block/title "legacy local title"]] - :db-sync/reversed-tx-data - [[:db/add [:block/uuid parent-uuid] :block/title parent-title]]}]) + (seed-client-op-txs! + test-repo + [{:db-sync/tx-id tx-id + :db-sync/pending? true + :db-sync/created-at 1000 + :db-sync/outliner-op :rebase + :db-sync/normalized-tx-data + [[:db/add [:block/uuid parent-uuid] :block/title "legacy local title"]] + :db-sync/reversed-tx-data + [[:db/add [:block/uuid parent-uuid] :block/title parent-title]]}]) (#'sync-apply/apply-remote-tx! test-repo nil diff --git a/src/test/frontend/worker/db_worker_test.cljs b/src/test/frontend/worker/db_worker_test.cljs index 5d3ea08c83..24ff9bae6f 100644 --- a/src/test/frontend/worker/db_worker_test.cljs +++ b/src/test/frontend/worker/db_worker_test.cljs @@ -373,7 +373,7 @@ (-> (export-client-ops-db test-repo) (p/then (fn [result] (is (= ["PRAGMA wal_checkpoint(2)"] @sql-calls)) - (is (= ["client-ops-/db.sqlite"] @export-calls)) + (is (= ["client-ops/db.sqlite"] @export-calls)) (is (instance? js/Uint8Array result)) (is (= [1 2 3] (vec result))) (done))) diff --git a/src/test/frontend/worker/sync/client_op_test.cljs b/src/test/frontend/worker/sync/client_op_test.cljs index e63b7d3022..882cbc173d 100644 --- a/src/test/frontend/worker/sync/client_op_test.cljs +++ b/src/test/frontend/worker/sync/client_op_test.cljs @@ -1,49 +1,115 @@ (ns frontend.worker.sync.client-op-test (:require [cljs.test :refer [deftest is testing]] - [datascript.core :as d] [frontend.worker.state :as worker-state] [frontend.worker.sync.client-op :as client-op])) -(deftest update-graph-uuid-replaces-existing-value-test - (let [repo "repo-1" - conn (d/create-conn client-op/schema-in-db) +(defn- new-memory-db + [] + (let [Database (js/require "better-sqlite3")] + (new Database ":memory:"))) + +(defn- with-client-ops-db + [repo f] + (let [db (new-memory-db) prev-client-ops-conns @worker-state/*client-ops-conns] - (reset! worker-state/*client-ops-conns {repo conn}) + (reset! worker-state/*client-ops-conns {repo db}) (try - (client-op/update-graph-uuid repo "graph-1") - (client-op/update-graph-uuid repo "graph-2") - (let [graph-uuid-datoms (vec (d/datoms @conn :avet :graph-uuid))] - (is (= 1 (count graph-uuid-datoms))) - (is (= #{"graph-2"} (set (map :v graph-uuid-datoms))))) + (f db) (finally + (.close db) (reset! worker-state/*client-ops-conns prev-client-ops-conns))))) +(defn- sqlite-count + [^js db sql & args] + (let [^js stmt (.prepare db sql) + ^js row (if (seq args) + (.apply (.-get stmt) stmt (to-array args)) + (.get stmt))] + (if row + (or (aget row "c") + (aget row "count")) + 0))) + +(deftest sqlite-sync-meta-roundtrip-test + (let [repo "repo-1"] + (with-client-ops-db + repo + (fn [_db] + (client-op/update-graph-uuid repo "graph-1") + (client-op/update-local-tx repo 9) + (client-op/update-local-checksum repo "checksum-1") + + (client-op/update-graph-uuid repo "graph-2") + (client-op/update-local-tx repo 12) + (client-op/update-local-checksum repo "checksum-2") + + (is (= "graph-2" (client-op/get-graph-uuid repo))) + (is (= 12 (client-op/get-local-tx repo))) + (is (= "checksum-2" (client-op/get-local-checksum repo))))))) + +(deftest sqlite-asset-ops-coalescing-test + (let [repo "repo-asset" + asset-uuid (random-uuid)] + (with-client-ops-db + repo + (fn [_db] + (client-op/add-asset-ops repo [[:update-asset 10 {:block-uuid asset-uuid}]]) + (is (= 1 (client-op/get-unpushed-asset-ops-count repo))) + (is (= [:update-asset 10 {:block-uuid asset-uuid}] + (:update-asset (first (client-op/get-all-asset-ops repo))))) + + ;; older remove should be ignored because a newer update already exists + (client-op/add-asset-ops repo [[:remove-asset 9 {:block-uuid asset-uuid}]]) + (is (= [:update-asset 10 {:block-uuid asset-uuid}] + (:update-asset (first (client-op/get-all-asset-ops repo))))) + + ;; newer remove should replace update + (client-op/add-asset-ops repo [[:remove-asset 11 {:block-uuid asset-uuid}]]) + (is (= [:remove-asset 11 {:block-uuid asset-uuid}] + (:remove-asset (first (client-op/get-all-asset-ops repo))))) + + (client-op/remove-asset-op repo asset-uuid) + (is (= 0 (client-op/get-unpushed-asset-ops-count repo))))))) + (deftest cleanup-finished-history-ops-removes-only-unreferenced-finished-txs-test (let [repo "repo-cleanup" - conn (d/create-conn client-op/schema-in-db) - prev-client-ops-conns @worker-state/*client-ops-conns keep-tx-id (random-uuid) remove-tx-id (random-uuid) pending-tx-id (random-uuid)] - (reset! worker-state/*client-ops-conns {repo conn}) - (try - (d/transact! conn - [{:db-sync/tx-id keep-tx-id - :db-sync/pending? false} - {:db-sync/tx-id remove-tx-id - :db-sync/pending? false} - {:db-sync/tx-id pending-tx-id - :db-sync/pending? true} - {:db-ident :metadata/local - :local-tx 99}]) + (with-client-ops-db + repo + (fn [db] + (client-op/update-local-tx repo 99) + (client-op/upsert-local-tx-entry! + repo + {:tx-id keep-tx-id + :created-at 1 + :pending? false + :failed? false + :normalized-tx-data [] + :reversed-tx-data []}) + (client-op/upsert-local-tx-entry! + repo + {:tx-id remove-tx-id + :created-at 2 + :pending? false + :failed? false + :normalized-tx-data [] + :reversed-tx-data []}) + (client-op/upsert-local-tx-entry! + repo + {:tx-id pending-tx-id + :created-at 3 + :pending? true + :failed? false + :normalized-tx-data [] + :reversed-tx-data []}) - (is (= 1 (client-op/cleanup-finished-history-ops! repo #{keep-tx-id}))) - (is (some? (d/entity @conn [:db-sync/tx-id keep-tx-id]))) - (is (nil? (d/entity @conn [:db-sync/tx-id remove-tx-id]))) - (is (some? (d/entity @conn [:db-sync/tx-id pending-tx-id]))) - (is (= 99 (:local-tx (d/entity @conn [:db-ident :metadata/local])))) - (finally - (reset! worker-state/*client-ops-conns prev-client-ops-conns))))) + (is (= 1 (client-op/cleanup-finished-history-ops! repo #{keep-tx-id}))) + (is (= 1 (sqlite-count db "select count(*) as c from client_ops where tx_id = ?" (str keep-tx-id)))) + (is (= 0 (sqlite-count db "select count(*) as c from client_ops where tx_id = ?" (str remove-tx-id)))) + (is (= 1 (sqlite-count db "select count(*) as c from client_ops where tx_id = ?" (str pending-tx-id)))) + (is (= 99 (client-op/get-local-tx repo))))))) (deftest cleanup-finished-history-ops-no-conn-is-noop-test (let [repo "repo-no-conn" diff --git a/src/test/frontend/worker/undo_redo_test.cljs b/src/test/frontend/worker/undo_redo_test.cljs index ab5aa47acc..90fcfcb6a3 100644 --- a/src/test/frontend/worker/undo_redo_test.cljs +++ b/src/test/frontend/worker/undo_redo_test.cljs @@ -14,6 +14,24 @@ (def ^:private test-repo "test-worker-undo-redo") +(defn- new-client-ops-db + [] + (let [Database (js/require "better-sqlite3") + db (new Database ":memory:")] + (client-op/ensure-sqlite-schema! db) + db)) + +(defn- delete-client-op-tx-row! + [^js db tx-id] + (let [^js stmt (.prepare db "delete from client_ops where kind = 'tx' and tx_id = ?")] + (.run stmt (str tx-id)))) + +(defn- client-op-tx-row-exists? + [^js db tx-id] + (let [^js stmt (.prepare db "select 1 as ok from client_ops where kind = 'tx' and tx_id = ? limit 1") + row (.get stmt (str tx-id))] + (some? row))) + (defn- local-tx-meta [m] (assoc m @@ -31,7 +49,7 @@ :blocks [{:block/title "task"} {:block/title "parent" :build/children [{:block/title "child"}]}]}]}) - client-ops-conn (d/create-conn client-op/schema-in-db)] + client-ops-conn (new-client-ops-db)] (reset! worker-state/*datascript-conns {test-repo conn}) (reset! worker-state/*client-ops-conns {test-repo client-ops-conn}) (reset! worker-undo-redo/*apply-history-action! sync-apply/apply-history-action!) @@ -44,6 +62,7 @@ (finally (d/unlisten! conn ::gen-undo-ops) (worker-undo-redo/clear-history! test-repo) + (.close client-ops-conn) (reset! worker-undo-redo/*apply-history-action! apply-history-action-prev) (reset! worker-state/*datascript-conns datascript-prev) (reset! worker-state/*client-ops-conns client-ops-prev))))) @@ -168,8 +187,7 @@ :tx-data [(d/datom 1 :block/title "poisoned" 1 true)])] item)) op))))) - (when-let [tx-ent (d/entity @client-ops-conn [:db-sync/tx-id tx-id-2])] - (ldb/transact! client-ops-conn [[:db/retractEntity (:db/id tx-ent)]])) + (delete-client-op-tx-row! client-ops-conn tx-id-2) (let [undo-result (worker-undo-redo/undo test-repo)] (is (not= ::worker-undo-redo/empty-undo-stack undo-result)) (is (= "v1" (:block/title (d/entity @conn [:block/uuid child-uuid])))) @@ -246,8 +264,7 @@ (assoc (second item) :tx-data [(d/datom 1 :block/title "poisoned" 1 true)])] item)) op))))) - (when-let [tx-ent (d/entity @client-ops-conn [:db-sync/tx-id tx-id])] - (ldb/transact! client-ops-conn [[:db/retractEntity (:db/id tx-ent)]])) + (delete-client-op-tx-row! client-ops-conn tx-id) (is (not= ::worker-undo-redo/empty-undo-stack (worker-undo-redo/undo test-repo))) (is (seq (get @worker-undo-redo/*redo-ops test-repo)))))) @@ -271,7 +288,7 @@ (let [undo-tx-id (:db-sync/tx-id (latest-undo-history-data))] (is (uuid? undo-tx-id)) (is (not= source-tx-id undo-tx-id)) - (is (some? (d/entity @client-ops-conn [:db-sync/tx-id undo-tx-id]))))))))) + (is (client-op-tx-row-exists? client-ops-conn undo-tx-id)))))))) (deftest undo-records-only-local-txs-test (testing "undo history records only local txs"