migrate.cljs 17 KB

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