Просмотр исходного кода

Merge pull request #11483 from logseq/fix/ensure-db-safety

safety: delete kvs row if only it's not used anymore
Tienson Qin 1 год назад
Родитель
Сommit
ca34eb6f36

+ 8 - 5
.github/workflows/build-stage.yml

@@ -12,7 +12,7 @@ on:
       cloudflare-project-name:
         description: "Cloudflare pages project name"
         required: true
-        default: "logseq-demo"
+        default: "logseq-dev"
 
   release:
     types: [released]
@@ -25,8 +25,6 @@ env:
 jobs:
   build:
     runs-on: ubuntu-latest
-    env:
-      asset-path: ${GITHUB_REF##*/}/static/js/
 
     steps:
       - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c  # v3.3.0
@@ -52,9 +50,14 @@ jobs:
       - name: Fetch yarn deps
         run: yarn cache clean && yarn install --frozen-lockfile
 
+      - name: Set Build Environment Variables
+        run: |
+          echo "ENABLE_FILE_SYNC_PRODUCTION=false" >> $GITHUB_ENV
+
       - name: Build Released-Web
         run: |
-          yarn gulp:build && clojure -M:cljs release app  --config-merge '{:asset-path "${{env.asset-path}}" :compiler-options {:source-map true :source-map-include-sources-content false :source-map-detail-level :symbols}}'
+          yarn gulp:build && clojure -M:cljs release app  --config-merge '{:compiler-options {:source-map true :source-map-include-sources-content false :source-map-detail-level :symbols}}'
+          rsync -avz --exclude node_modules --exclude android --exclude ios ./static/ ./public/static/
           ls -lR ./public
 
       - name: Publish to Cloudflare Pages
@@ -62,7 +65,7 @@ jobs:
         with:
           apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
           accountId: 2553ea8236c11ea0f88de28fce1cbfee
-          projectName: ${{ github.event.inputs.cloudflare-project-name || 'logseq-demo' }}
+          projectName: ${{ github.event.inputs.cloudflare-project-name || 'logseq-dev' }}
           directory: 'public'
           gitHubToken: ${{ secrets.GITHUB_TOKEN }}
           branch: 'production'

+ 1 - 1
deps.edn

@@ -4,7 +4,7 @@
   rum/rum                               {:mvn/version "0.12.9"}
 
   datascript/datascript                 {:git/url "https://github.com/logseq/datascript" ;; fork
-                                         :sha     "8c7168b0c29ae2dc3c3efb5d28188916e991bde1"}
+                                         :sha     "1f84d10df4970f054489b0ee78799f64b8dd4ee2"}
 
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}
   borkdude/rewrite-edn                  {:mvn/version "0.4.7"}

+ 1 - 1
deps/db/deps.edn

@@ -1,7 +1,7 @@
 {:deps
  ;; These deps are kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
  {datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
-                         :sha     "8c7168b0c29ae2dc3c3efb5d28188916e991bde1"}
+                         :sha     "1f84d10df4970f054489b0ee78799f64b8dd4ee2"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"
                                          :exclusions [datascript/datascript]}
   cljs-bean/cljs-bean         {:mvn/version "1.5.0"}

+ 1 - 1
deps/db/src/logseq/db/sqlite/common_db.cljs

@@ -282,7 +282,7 @@
 (defn create-kvs-table!
   "Creates a sqlite table for use with datascript.storage if one doesn't exist"
   [sqlite-db]
-  (.exec sqlite-db "create table if not exists kvs (addr INTEGER primary key, content TEXT)"))
+  (.exec sqlite-db "create table if not exists kvs (addr INTEGER primary key, content TEXT, addresses JSON)"))
 
 (defn get-storage-conn
   "Given a datascript storage, returns a datascript connection for it"

+ 1 - 1
deps/outliner/deps.edn

@@ -1,7 +1,7 @@
 {:deps
  ;; External deps should be kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
  {datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
-                         :sha     "8c7168b0c29ae2dc3c3efb5d28188916e991bde1"}
+                         :sha     "1f84d10df4970f054489b0ee78799f64b8dd4ee2"}
   logseq/db             {:local/root "../db"}
   logseq/graph-parser   {:local/root "../db"}
   com.cognitect/transit-cljs {:mvn/version "0.8.280"}

+ 17 - 9
src/main/frontend/components/export.cljs

@@ -20,39 +20,47 @@
   (when-let [current-repo (state/get-current-repo)]
     (let [db-based? (config/db-based-graph? current-repo)]
       [:div.export
-       [:h1.title (t :export)]
-       [:ul.mr-1
+       [:h1.title.mb-8 (t :export)]
+
+       [:div.flex.flex-col.gap-4.ml-1
         (when-not db-based?
-          [:li.mb-4
+          [:div
            [:a.font-medium {:on-click #(export/export-repo-as-edn! current-repo)}
             (t :export-edn)]])
         (when-not db-based?
-          [:li.mb-4
+          [:div
            [:a.font-medium {:on-click #(export/export-repo-as-json! current-repo)}
             (t :export-json)]])
         (when (config/db-based-graph? current-repo)
-          [:li.mb-4
+          [:div
            [:a.font-medium {:on-click #(export/export-repo-as-sqlite-db! current-repo)}
             (t :export-sqlite-db)]])
+        (when (config/db-based-graph? current-repo)
+          [:div
+           [:a.font-medium {:on-click #(export/export-repo-as-debug-json! current-repo)}
+            "Export debug JSON"]
+           [:p.text-sm.opacity-70 "Any sensitive data will be removed in the exported json file, you can send it to us for debugging."]])
         (when-not db-based?
           (when (util/electron?)
-            [:li.mb-4
+            [:div
              [:a.font-medium {:on-click #(export/download-repo-as-html! current-repo)}
               (t :export-public-pages)]]))
         (when-not (or (mobile-util/native-platform?) db-based?)
-          [:li.mb-4
+          [:div
            [:a.font-medium {:on-click #(export-text/export-repo-as-markdown! current-repo)}
             (t :export-markdown)]])
         (when-not (or (mobile-util/native-platform?) db-based?)
-          [:li.mb-4
+          [:div
            [:a.font-medium {:on-click #(export-opml/export-repo-as-opml! current-repo)}
             (t :export-opml)]])
         (when-not (or (mobile-util/native-platform?) db-based?)
-          [:li.mb-4
+          [:div
            [:a.font-medium {:on-click #(export/export-repo-as-roam-json! current-repo)}
             (t :export-roam-json)]])]
+
        [:a#download-as-edn-v2.hidden]
        [:a#download-as-json-v2.hidden]
+       [:a#download-as-json-debug.hidden]
        [:a#download-as-sqlite-db.hidden]
        [:a#download-as-roam-json.hidden]
        [:a#download-as-html.hidden]

+ 14 - 0
src/main/frontend/handler/export.cljs

@@ -166,6 +166,20 @@
         (.setAttribute anchor "download" filename)
         (.click anchor)))))
 
+(defn export-repo-as-debug-json!
+  [repo]
+  (p/let [result (export-common-handler/<get-debug-datoms repo)
+          json-str (-> result
+                       bean/->js
+                       js/JSON.stringify)
+          filename (file-name (str repo "-debug-datoms") :json)
+          data-str (str "data:text/json;charset=utf-8,"
+                        (js/encodeURIComponent json-str))]
+    (when-let [anchor (gdom/getElement "download-as-json-debug")]
+      (.setAttribute anchor "href" data-str)
+      (.setAttribute anchor "download" filename)
+      (.click anchor))))
+
 (defn export-repo-as-sqlite-db!
   [repo]
   (p/let [data (persist-db/<export-db repo {:return-data? true})

+ 10 - 4
src/main/frontend/handler/export/common.cljs

@@ -4,7 +4,6 @@
   when use together with dynamic var."
   (:refer-clojure :exclude [map filter mapcat concat remove])
   (:require [cljs.core.match :refer [match]]
-            [clojure.edn :as edn]
             [clojure.string :as string]
             [frontend.db :as db]
             [frontend.format.mldoc :as mldoc]
@@ -16,7 +15,8 @@
             [frontend.common.file.core :as common-file]
             [malli.core :as m]
             [malli.util :as mu]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.db :as ldb]))
 
 ;;; TODO: split frontend.handler.export.text related states
 (def ^:dynamic *state*
@@ -186,13 +186,19 @@
   [repo]
   (when-let [^object worker @db-browser/*worker]
     (p/let [result (.get-all-pages worker repo)]
-      (edn/read-string result))))
+      (ldb/read-transit-str result))))
+
+(defn <get-debug-datoms
+  [repo]
+  (when-let [^object worker @db-browser/*worker]
+    (p/let [result (.get-debug-datoms worker repo)]
+      (ldb/read-transit-str result))))
 
 (defn <get-all-page->content
   [repo]
   (when-let [^object worker @db-browser/*worker]
     (p/let [result (.get-all-page->content worker repo)]
-      (edn/read-string result))))
+      (ldb/read-transit-str result))))
 
 (defn <get-file-contents
   [repo suffix]

+ 39 - 3
src/main/frontend/worker/db/migrate.cljs

@@ -5,7 +5,9 @@
             [logseq.db.frontend.property :as db-property]
             [logseq.db :as ldb]
             [logseq.db.frontend.schema :as db-schema]
-            [frontend.worker.search :as search]))
+            [frontend.worker.search :as search]
+            [cljs-bean.core :as bean]
+            [logseq.db.sqlite.util :as sqlite-util]))
 
 ;; TODO: fixes/rollback
 
@@ -117,13 +119,43 @@
                                            :else
                                            (first types))
                                          types)]
-                              [[:db/retract id :block/type]
-                               [:db/add id :block/type type]]))))
+                              (when type
+                                [[:db/retract id :block/type]
+                                [:db/add id :block/type type]])))))
         schema (:schema db)]
     (ldb/transact! conn new-type-tx {:db-migrate? true})
     (d/reset-schema! conn (update schema :block/type #(assoc % :db/cardinality :db.cardinality/one)))
     []))
 
+(defn- add-addresses-in-kvs-table
+  [^Object sqlite-db]
+  (let [columns (->> (.exec sqlite-db #js {:sql "SELECT NAME FROM PRAGMA_TABLE_INFO('kvs')"
+                                           :rowMode "array"})
+                     bean/->clj
+                     (map first)
+                     set)]
+    (when-not (contains? columns "addresses")
+      (let [data (some->> (.exec sqlite-db #js {:sql "select addr, content from kvs"
+                                                :rowMode "array"})
+                          bean/->clj
+                          (map (fn [[addr content]]
+                                 (let [content' (sqlite-util/transit-read content)
+                                       [content' addresses] (if (map? content')
+                                                              [(dissoc content' :addresses)
+                                                               (when-let [addresses (:addresses content')]
+                                                                 (js/JSON.stringify (bean/->js addresses)))]
+                                                              [content' nil])
+                                       content' (sqlite-util/transit-write content')]
+                                   #js {:$addr addr
+                                        :$content content'
+                                        :$addresses addresses}))))]
+        (.exec sqlite-db #js {:sql "alter table kvs add column addresses JSON"})
+        (.transaction sqlite-db
+                      (fn [tx]
+                        (doseq [item data]
+                          (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
+                                         :bind item}))))))))
+
 (def schema-version->updates
   [[3 {:properties [:logseq.property/table-sorting :logseq.property/table-filters
                     :logseq.property/table-hidden-columns :logseq.property/table-ordered-columns]
@@ -151,6 +183,10 @@
   (when (< db-schema/version max-schema-version)
     (js/console.warn (str "Current db schema-version is " db-schema/version ", max available schema-version is " max-schema-version))))
 
+(defn migrate-sqlite-db
+  [db]
+  (add-addresses-in-kvs-table db))
+
 (defn migrate
   [conn search-db]
   (let [db @conn

+ 86 - 26
src/main/frontend/worker/db_worker.cljs

@@ -88,6 +88,29 @@
   [^js pool data]
   (.importDb ^js pool repo-path data))
 
+(comment
+  (defn- gc-kvs-table!
+    [^Object db]
+    (let [schema (some->> (.exec db #js {:sql "select content from kvs where addr = 0"
+                                         :rowMode "array"})
+                          bean/->clj
+                          ffirst
+                          sqlite-util/transit-read)
+          result (->> (.exec db #js {:sql "select addr, addresses from kvs"
+                                     :rowMode "array"})
+                      bean/->clj
+                      (map (fn [[addr addresses]]
+                             [addr (bean/->clj (js/JSON.parse addresses))])))
+          used-addresses (set (concat (mapcat second result)
+                                      [0 1 (:eavt schema) (:avet schema) (:aevt schema)]))
+          unused-addresses (set/difference (set (map first result)) used-addresses)]
+      (when unused-addresses
+        (prn :debug :db-gc :unused-addresses unused-addresses)
+        (.transaction db (fn [tx]
+                           (doseq [addr unused-addresses]
+                             (.exec tx #js {:sql "Delete from kvs where addr = ?"
+                                            :bind #js [addr]}))))))))
+
 (defn upsert-addr-content!
   "Upsert addr+data-seq"
   [repo data delete-addrs & {:keys [client-ops-db?] :or {client-ops-db? false}}]
@@ -95,37 +118,51 @@
     (assert (some? db) "sqlite db not exists")
     (.transaction db (fn [tx]
                        (doseq [item data]
-                         (.exec tx #js {:sql "INSERT INTO kvs (addr, content) values ($addr, $content) on conflict(addr) do update set content = $content"
-                                        :bind item}))
-
-                       (doseq [addr delete-addrs]
-                         (.exec db #js {:sql "Delete from kvs where addr = ?"
-                                        :bind #js [addr]}))))))
+                         (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
+                                        :bind item}))))
+    (when (seq delete-addrs)
+      (.transaction db (fn [tx]
+                         ;; (prn :debug :delete-addrs delete-addrs)
+                         (doseq [addr delete-addrs]
+                           (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);"
+                                          :bind #js [addr]})))))))
 
 (defn restore-data-from-addr
   [repo addr & {:keys [client-ops-db?] :or {client-ops-db? false}}]
   (let [^Object db (worker-state/get-sqlite-conn repo (if client-ops-db? :client-ops :db))]
     (assert (some? db) "sqlite db not exists")
-    (when-let [content (-> (.exec db #js {:sql "select content from kvs where addr = ?"
-                                          :bind #js [addr]
-                                          :rowMode "array"})
-                           ffirst)]
-      (try
-        (let [data (sqlite-util/transit-read content)]
-          (if-let [addresses (:addresses data)]
-            (assoc data :addresses (bean/->js addresses))
-            data))
-        (catch :default _e              ; TODO: remove this once db goes to test
-          (edn/read-string content))))))
+    (when-let [result (-> (.exec db #js {:sql "select content, addresses from kvs where addr = ?"
+                                         :bind #js [addr]
+                                         :rowMode "array"})
+                          first)]
+      (let [[content addresses] (bean/->clj result)
+            addresses (when addresses
+                        (js/JSON.parse addresses))
+            data (sqlite-util/transit-read content)]
+        (if (and addresses (map? data))
+          (assoc data :addresses addresses)
+          data)))))
 
 (defn new-sqlite-storage
   [repo _opts]
   (reify IStorage
     (-store [_ addr+data-seq delete-addrs]
-      (let [data (map
+      (let [used-addrs (set (mapcat
+                             (fn [[addr data]]
+                               (cons addr
+                                     (when (map? data)
+                                       (:addresses data))))
+                             addr+data-seq))
+            delete-addrs (remove used-addrs delete-addrs)
+            data (map
                   (fn [[addr data]]
-                    #js {:$addr addr
-                         :$content (sqlite-util/transit-write data)})
+                    (let [data' (if (map? data) (dissoc data :addresses) data)
+                          addresses (when (map? data)
+                                      (when-let [addresses (:addresses data)]
+                                        (js/JSON.stringify (bean/->js addresses))))]
+                      #js {:$addr addr
+                           :$content (sqlite-util/transit-write data')
+                           :$addresses addresses}))
                   addr+data-seq)]
         (upsert-addr-content! repo data delete-addrs)))
 
@@ -136,10 +173,22 @@
   [repo]
   (reify IStorage
     (-store [_ addr+data-seq delete-addrs]
-      (let [data (map
+      (let [used-addrs (set (mapcat
+                             (fn [[addr data]]
+                               (cons addr
+                                     (when (map? data)
+                                       (:addresses data))))
+                             addr+data-seq))
+            delete-addrs (remove used-addrs delete-addrs)
+            data (map
                   (fn [[addr data]]
-                    #js {:$addr addr
-                         :$content (sqlite-util/transit-write data)})
+                    (let [data' (if (map? data) (dissoc data :addresses) data)
+                          addresses (when (map? data)
+                                      (when-let [addresses (:addresses data)]
+                                        (js/JSON.stringify (bean/->js addresses))))]
+                      #js {:$addr addr
+                           :$content (sqlite-util/transit-write data')
+                           :$addresses addresses}))
                   addr+data-seq)]
         (upsert-addr-content! repo data delete-addrs :client-ops-db? true)))
 
@@ -197,11 +246,14 @@
       (.exec db "PRAGMA locking_mode=exclusive")
       (sqlite-common-db/create-kvs-table! db)
       (sqlite-common-db/create-kvs-table! client-ops-db)
+      (db-migrate/migrate-sqlite-db db)
+      (db-migrate/migrate-sqlite-db client-ops-db)
       (search/create-tables-and-triggers! search-db)
       (let [schema (sqlite-util/get-schema repo)
             conn (sqlite-common-db/get-storage-conn storage schema)
             client-ops-conn (sqlite-common-db/get-storage-conn client-ops-storage client-op/schema-in-db)
-            initial-data-exists? (d/entity @conn :logseq.class/Root)]
+            initial-data-exists? (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 (sqlite-util/db-based-graph? repo) (not initial-data-exists?))
@@ -209,9 +261,12 @@
                 initial-data (sqlite-create-graph/build-db-initial-data config)]
             (d/transact! conn initial-data {:initial-db? true})))
 
-        (when-not (ldb/page-exists? @conn common-config/views-page-name "hidden")
-          (ldb/create-views-page! conn))
+        (try
+          (when-not (ldb/page-exists? @conn common-config/views-page-name "hidden")
+            (ldb/create-views-page! conn))
+          (catch :default _e))
 
+        ;; (gc-kvs-table! db)
         (db-migrate/migrate conn search-db)
 
         (db-listener/listen-db-changes! repo conn)))))
@@ -604,6 +659,11 @@
                                    (ldb/read-transit-str tree->file-opts)
                                    (ldb/read-transit-str context)))))
 
+  (get-debug-datoms
+   [this repo]
+   (when-let [db (worker-state/get-sqlite-conn repo)]
+     (ldb/write-transit-str (worker-export/get-debug-datoms db))))
+
   (get-all-pages
    [this repo]
    (when-let [conn (worker-state/get-datascript-conn repo)]

+ 43 - 1
src/main/frontend/worker/export.cljs

@@ -4,7 +4,10 @@
             [frontend.common.file.core :as common-file]
             [logseq.db :as ldb]
             [logseq.graph-parser.property :as gp-property]
-            [logseq.outliner.tree :as otree]))
+            [logseq.outliner.tree :as otree]
+            [cljs-bean.core :as bean]
+            [logseq.db.sqlite.util :as sqlite-util]
+            [clojure.string :as string]))
 
 (defn- safe-keywordize
   [block]
@@ -49,3 +52,42 @@
               (let [e (d/entity db (:e d))]
                 [(:block/title e)
                  (common-file/block->content repo db (:block/uuid e) {} {})])))))
+
+(defn get-debug-datoms
+  [^Object db]
+  (some->> (.exec db #js {:sql "select content from kvs"
+                          :rowMode "array"})
+           bean/->clj
+           (mapcat (fn [result]
+                     (let [result (sqlite-util/transit-read (first result))]
+                       (when (map? result)
+                         (:keys result)))))
+           (group-by first)
+           (mapcat (fn [[_id col]]
+                     (let [type (some (fn [[_e a v _t]]
+                                        (when (= a :block/type)
+                                          v)) col)
+                           ident (some (fn [[_e a v _t]]
+                                         (when (= a :db/ident)
+                                           v)) col)]
+                       (map
+                        (fn [[e a v t]]
+                          (cond
+                            (and (contains? #{:block/title :block/name} a)
+                                 (or
+                                  ;; normal page or block
+                                  (not (contains? #{"class" "property" "journal" "closed value" "hidden"} type))
+                                  ;; class/property created by user
+                                  (and ident
+                                       (contains? #{"class" "property"} type)
+                                       (not (string/starts-with? (namespace ident) "logseq")))))
+                            [e a (str "debug " e) t]
+
+                            (= a :block/uuid)
+                            [e a (str v) t]
+
+                            :else
+                            [e a v t]))
+                        col))))
+           (distinct)
+           (sort-by first)))