migrate.cljs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. (ns frontend.worker.db.migrate
  2. "DB migration"
  3. (:require [datascript.core :as d]
  4. [logseq.db.sqlite.create-graph :as sqlite-create-graph]
  5. [logseq.db.frontend.property :as db-property]
  6. [logseq.db.frontend.class :as db-class]
  7. [logseq.db :as ldb]
  8. [logseq.db.frontend.schema :as db-schema]
  9. [frontend.worker.search :as search]
  10. [cljs-bean.core :as bean]
  11. [logseq.db.sqlite.util :as sqlite-util]
  12. [logseq.common.config :as common-config]
  13. [logseq.common.util :as common-util]
  14. [logseq.db.frontend.property.build :as db-property-build]
  15. [logseq.db.frontend.order :as db-order]
  16. [logseq.common.uuid :as common-uuid]))
  17. ;; TODO: fixes/rollback
  18. (defn- replace-original-name-content-with-title
  19. [conn search-db]
  20. (search/truncate-table! search-db)
  21. (d/transact! conn
  22. [{:db/ident :block/title
  23. :db/index true}])
  24. (let [datoms (d/datoms @conn :avet :block/uuid)
  25. tx-data (mapcat
  26. (fn [d]
  27. (let [e (d/entity @conn (:e d))]
  28. (concat
  29. (when (:block/content e)
  30. [[:db/retract (:e d) :block/content]
  31. [:db/add (:e d) :block/title (:block/content e)]])
  32. (when (:block/original-name e)
  33. [[:db/retract (:e d) :block/original-name]
  34. [:db/add (:e d) :block/title (:block/original-name e)]])))) datoms)]
  35. tx-data))
  36. (defn- replace-object-and-page-type-with-node
  37. [conn _search-db]
  38. (->> (ldb/get-all-properties @conn)
  39. (filter (fn [p]
  40. (contains? #{:object :page} (:type (:block/schema p)))))
  41. (map
  42. (fn [p]
  43. {:db/id (:db/id p)
  44. :block/schema (assoc (:block/schema p) :type :node)}))))
  45. (defn- update-task-ident
  46. [conn _search-db]
  47. [{:db/id (:db/id (d/entity @conn :logseq.class/task))
  48. :db/ident :logseq.class/Task}])
  49. (defn- property-checkbox-type-non-ref
  50. [conn _search-db]
  51. (let [db @conn
  52. properties (d/q
  53. '[:find [?ident ...]
  54. :where
  55. [?p :block/schema ?s]
  56. [(get ?s :type) ?t]
  57. [(= ?t :checkbox)]
  58. [?p :db/ident ?ident]]
  59. db)
  60. datoms (mapcat #(d/datoms db :avet %) properties)
  61. schema-tx-data (map
  62. (fn [ident]
  63. [:db/retract ident :db/valueType])
  64. properties)
  65. value-tx-data (mapcat
  66. (fn [d]
  67. (let [e (:e d)
  68. a (:a d)
  69. v (:v d)
  70. ve (when (integer? v) (d/entity db v))
  71. ve-value (:property.value/content ve)]
  72. (when (some? ve-value)
  73. [[:db/add e a ve-value]
  74. [:db/retractEntity v]])))
  75. datoms)]
  76. (concat schema-tx-data value-tx-data)))
  77. (defn- update-table-properties
  78. [conn _search-db]
  79. (let [old-new-props {:logseq.property/table-sorting :logseq.property.table/sorting
  80. :logseq.property/table-filters :logseq.property.table/filters
  81. :logseq.property/table-ordered-columns :logseq.property.table/ordered-columns
  82. :logseq.property/table-hidden-columns :logseq.property.table/hidden-columns}
  83. props-tx (mapv (fn [[old new]]
  84. {:db/id (:db/id (d/entity @conn old))
  85. :db/ident new})
  86. old-new-props)]
  87. ;; Property changes need to be in their own tx for subsequent uses of properties to take effect
  88. (ldb/transact! conn props-tx {:db-migrate? true})
  89. (mapcat (fn [[old new]]
  90. (->> (d/q '[:find ?b ?prop-v :in $ ?prop :where [?b ?prop ?prop-v]] @conn old)
  91. (mapcat (fn [[id prop-value]]
  92. [[:db/retract id old]
  93. [:db/add id new prop-value]]))))
  94. old-new-props)))
  95. (defn- rename-properties
  96. [props-to-rename]
  97. (fn [conn _search-db]
  98. (when (ldb/db-based-graph? @conn)
  99. (let [props-tx (mapv (fn [[old new]]
  100. (merge {:db/id (:db/id (d/entity @conn old))
  101. :db/ident new}
  102. (when-let [new-title (get-in db-property/built-in-properties [new :title])]
  103. {:block/title new-title
  104. :block/name (common-util/page-name-sanity-lc new-title)})))
  105. props-to-rename)]
  106. ;; Property changes need to be in their own tx for subsequent uses of properties to take effect
  107. (ldb/transact! conn props-tx {:db-migrate? true})
  108. (mapcat (fn [[old new]]
  109. ;; can't use datoms b/c user properties aren't indexed
  110. (->> (d/q '[:find ?b ?prop-v :in $ ?prop :where [?b ?prop ?prop-v]] @conn old)
  111. (mapcat (fn [[id prop-value]]
  112. [[:db/retract id old]
  113. [:db/add id new prop-value]]))))
  114. props-to-rename)))))
  115. (defn- update-block-type-many->one
  116. [conn _search-db]
  117. (let [db @conn
  118. datoms (d/datoms db :avet :block/type)
  119. new-type-tx (->> (set (map :e datoms))
  120. (mapcat
  121. (fn [id]
  122. (let [types (:block/type (d/entity db id))
  123. type (if (set? types)
  124. (cond
  125. (contains? types "class")
  126. "class"
  127. (contains? types "property")
  128. "property"
  129. (contains? types "whiteboard")
  130. "whiteboard"
  131. (contains? types "journal")
  132. "journal"
  133. (contains? types "hidden")
  134. "hidden"
  135. (contains? types "page")
  136. "page"
  137. :else
  138. (first types))
  139. types)]
  140. (when type
  141. [[:db/retract id :block/type]
  142. [:db/add id :block/type type]])))))
  143. schema (:schema db)]
  144. (ldb/transact! conn new-type-tx {:db-migrate? true})
  145. (d/reset-schema! conn (update schema :block/type #(assoc % :db/cardinality :db.cardinality/one)))
  146. []))
  147. (defn- deprecate-class-parent
  148. [conn _search-db]
  149. (let [db @conn]
  150. (when (ldb/db-based-graph? db)
  151. (let [datoms (d/datoms db :avet :class/parent)]
  152. (->> (set (map :e datoms))
  153. (mapcat
  154. (fn [id]
  155. (let [value (:db/id (:class/parent (d/entity db id)))]
  156. [[:db/retract id :class/parent]
  157. [:db/add id :logseq.property/parent value]]))))))))
  158. (defn- deprecate-class-schema-properties
  159. [conn _search-db]
  160. (let [db @conn]
  161. (when (ldb/db-based-graph? db)
  162. (let [datoms (d/datoms db :avet :class/schema.properties)]
  163. (->> (set (map :e datoms))
  164. (mapcat
  165. (fn [id]
  166. (let [values (map :db/id (:class/schema.properties (d/entity db id)))]
  167. (concat
  168. [[:db/retract id :class/schema.properties]]
  169. (map
  170. (fn [value]
  171. [:db/add id :logseq.property.class/properties value])
  172. values))))))))))
  173. (defn- update-db-attrs-type
  174. [conn _search-db]
  175. (let [db @conn]
  176. (when (ldb/db-based-graph? db)
  177. (let [alias (d/entity db :block/alias)
  178. tags (d/entity db :block/tags)]
  179. [[:db/add (:db/id alias) :block/schema {:type :page
  180. :cardinality :many
  181. :view-context :page
  182. :public? true}]
  183. [:db/add (:db/id tags) :block/schema {:type :class
  184. :cardinality :many
  185. :public? true
  186. :classes #{:logseq.class/Root}}]]))))
  187. (defn- fix-view-for
  188. [conn _search-db]
  189. (let [db @conn]
  190. (when (ldb/db-based-graph? db)
  191. (let [datoms (d/datoms db :avet :logseq.property/view-for)
  192. e (d/entity db :logseq.property/view-for)
  193. fix-schema [:db/add (:db/id e) :block/schema {:type :node
  194. :hide? true
  195. :public? false}]
  196. fix-data (keep
  197. (fn [d]
  198. (if-let [id (if (= :all-pages (:v d))
  199. (:db/id (ldb/get-case-page db common-config/views-page-name))
  200. (:db/id (d/entity db (:v d))))]
  201. [:db/add (:e d) :logseq.property/view-for id]
  202. [:db/retract (:e d) :logseq.property/view-for (:v d)]))
  203. datoms)]
  204. (cons fix-schema fix-data)))))
  205. (defn- add-card-properties
  206. [conn _search-db]
  207. (let [db @conn]
  208. (when (ldb/db-based-graph? db)
  209. (let [card (d/entity db :logseq.class/Card)
  210. card-id (:db/id card)]
  211. [[:db/add card-id :logseq.property.class/properties :logseq.property.fsrs/due]
  212. [:db/add card-id :logseq.property.class/properties :logseq.property.fsrs/state]]))))
  213. (defn- add-query-property-to-query-tag
  214. [conn _search-db]
  215. (let [db @conn]
  216. (when (ldb/db-based-graph? db)
  217. (let [query (d/entity db :logseq.class/Query)
  218. query-id (:db/id query)]
  219. [[:db/add query-id :logseq.property.class/properties :logseq.property/query]]))))
  220. (defn- add-card-view
  221. [conn _search-db]
  222. (let [db @conn]
  223. (when (ldb/db-based-graph? db)
  224. (let [ident :logseq.property.view/type.card
  225. uuid' (common-uuid/gen-uuid :db-ident-block-uuid ident)
  226. property (d/entity db :logseq.property.view/type)
  227. m (cond->
  228. (db-property-build/build-closed-value-block
  229. uuid'
  230. "Card view"
  231. property
  232. {:db-ident :logseq.property.view/type.card})
  233. true
  234. (assoc :block/order (db-order/gen-key)))]
  235. [m]))))
  236. (defn- add-addresses-in-kvs-table
  237. [^Object sqlite-db]
  238. (let [columns (->> (.exec sqlite-db #js {:sql "SELECT NAME FROM PRAGMA_TABLE_INFO('kvs')"
  239. :rowMode "array"})
  240. bean/->clj
  241. (map first)
  242. set)]
  243. (when-not (contains? columns "addresses")
  244. (let [data (some->> (.exec sqlite-db #js {:sql "select addr, content from kvs"
  245. :rowMode "array"})
  246. bean/->clj
  247. (map (fn [[addr content]]
  248. (let [content' (sqlite-util/transit-read content)
  249. [content' addresses] (if (map? content')
  250. [(dissoc content' :addresses)
  251. (when-let [addresses (:addresses content')]
  252. (js/JSON.stringify (bean/->js addresses)))]
  253. [content' nil])
  254. content' (sqlite-util/transit-write content')]
  255. #js {:$addr addr
  256. :$content content'
  257. :$addresses addresses}))))]
  258. (.exec sqlite-db #js {:sql "alter table kvs add column addresses JSON"})
  259. (.transaction sqlite-db
  260. (fn [tx]
  261. (doseq [item data]
  262. (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
  263. :bind item}))))))))
  264. (def schema-version->updates
  265. [[3 {:properties [:logseq.property/table-sorting :logseq.property/table-filters
  266. :logseq.property/table-hidden-columns :logseq.property/table-ordered-columns]
  267. :classes []}]
  268. [4 {:fix (fn [conn _search-db]
  269. (let [pages (d/datoms @conn :avet :block/name)
  270. tx-data (keep (fn [d]
  271. (let [entity (d/entity @conn (:e d))]
  272. (when-not (:block/type entity)
  273. {:db/id (:e d)
  274. :block/type "page"}))) pages)]
  275. tx-data))}]
  276. [5 {:properties [:logseq.property/view-for]
  277. :classes []}]
  278. [6 {:properties [:logseq.property.asset/remote-metadata]}]
  279. [7 {:fix replace-original-name-content-with-title}]
  280. [8 {:fix replace-object-and-page-type-with-node}]
  281. [9 {:fix update-task-ident}]
  282. [10 {:fix update-table-properties}]
  283. [11 {:fix property-checkbox-type-non-ref}]
  284. [12 {:fix update-block-type-many->one}]
  285. [13 {:classes [:logseq.class/Journal]
  286. :properties [:logseq.property.journal/title-format]}]
  287. [14 {:properties [:logseq.property/parent]
  288. :fix deprecate-class-parent}]
  289. [15 {:properties [:logseq.property.class/properties]
  290. :fix deprecate-class-schema-properties}]
  291. [16 {:properties [:logseq.property.class/hide-from-node]}]
  292. [17 {:fix update-db-attrs-type}]
  293. [18 {:properties [:logseq.property.view/type]}]
  294. [19 {:classes [:logseq.class/Query]}]
  295. [20 {:fix fix-view-for}]
  296. [21 {:properties [:logseq.property.table/sized-columns]}]
  297. [22 {:properties [:logseq.property.fsrs/state :logseq.property.fsrs/due]}]
  298. [23 {:fix add-card-properties}]
  299. [24 {:classes [:logseq.class/Cards]}]
  300. [25 {:properties [:logseq.property/query]
  301. :fix add-query-property-to-query-tag}]
  302. [26 {:properties [:logseq.property.node/type]}]
  303. [27 {:properties [:logseq.property.code/mode]}]
  304. [28 {:fix (rename-properties {:logseq.property.node/type :logseq.property.node/display-type})}]
  305. [29 {:properties [:logseq.property.code/lang]}]
  306. [30 {:classes [:logseq.class/Asset]
  307. :properties [:logseq.property.asset/type :logseq.property.asset/size :logseq.property.asset/checksum]}]
  308. [31 {:properties [:logseq.property/asset]}]
  309. [32 {:properties [:logseq.property.asset/last-visit-page]}]
  310. [33 {:properties [:logseq.property.pdf/hl-image]}]
  311. [34 {:properties [:logseq.property.asset/resize-metadata]}]
  312. [35 {:fix add-card-view}]
  313. [36 {:properties [:kv/value :block/type :block/schema :block/parent
  314. :block/order :block/collapsed? :block/page
  315. :block/refs :block/path-refs :block/link
  316. :block/title :block/closed-value-property
  317. :block/created-at :block/updated-at
  318. :property/schema.classes :property.value/content]}]])
  319. (let [max-schema-version (apply max (map first schema-version->updates))]
  320. (assert (<= db-schema/version max-schema-version))
  321. (when (< db-schema/version max-schema-version)
  322. (js/console.warn (str "Current db schema-version is " db-schema/version ", max available schema-version is " max-schema-version))))
  323. (defn migrate-sqlite-db
  324. [db]
  325. (add-addresses-in-kvs-table db))
  326. (defn migrate
  327. [conn search-db]
  328. (let [db @conn
  329. version-in-db (or (:kv/value (d/entity db :logseq.kv/schema-version)) 0)]
  330. (cond
  331. (= version-in-db db-schema/version)
  332. nil
  333. (< db-schema/version version-in-db) ; outdated client, db version could be synced from server
  334. ;; FIXME: notify users to upgrade to the latest version asap
  335. nil
  336. (> db-schema/version version-in-db)
  337. (try
  338. (let [db-based? (ldb/db-based-graph? @conn)
  339. updates (keep (fn [[v updates]]
  340. (when (and (< version-in-db v) (<= v db-schema/version))
  341. updates))
  342. schema-version->updates)
  343. properties (mapcat :properties updates)
  344. new-properties (->> (select-keys db-property/built-in-properties properties)
  345. ;; property already exists, this should never happen
  346. (remove (fn [[k _]]
  347. (when (d/entity db k)
  348. (assert (str "DB migration: property already exists " k)))))
  349. (into {})
  350. sqlite-create-graph/build-initial-properties*
  351. (map (fn [b] (assoc b :logseq.property/built-in? true))))
  352. classes (mapcat :classes updates)
  353. new-classes (->> (select-keys db-class/built-in-classes classes)
  354. ;; class already exists, this should never happen
  355. (remove (fn [[k _]]
  356. (when (d/entity db k)
  357. (assert (str "DB migration: class already exists " k)))))
  358. (into {})
  359. (#(sqlite-create-graph/build-initial-classes* % (zipmap properties properties)))
  360. (map (fn [b] (assoc b :logseq.property/built-in? true))))
  361. fixes (mapcat
  362. (fn [update']
  363. (when-let [fix (:fix update')]
  364. (when (fn? fix)
  365. (fix conn search-db)))) updates)
  366. tx-data' (if db-based? (concat new-properties new-classes fixes) fixes)]
  367. (when (seq tx-data')
  368. (let [tx-data' (concat tx-data' [(sqlite-util/kv :logseq.kv/schema-version db-schema/version)])]
  369. (ldb/transact! conn tx-data' {:db-migrate? true}))
  370. (println "DB schema migrated to " db-schema/version " from " version-in-db ".")))
  371. (catch :default e
  372. (prn :error "DB migration failed:")
  373. (js/console.error e))))))