Merge branch 'feat/db' into perf/app-start

This commit is contained in:
Tienson Qin
2025-04-07 20:06:45 +08:00
84 changed files with 1509 additions and 787 deletions

View File

@@ -430,6 +430,9 @@
{:type :coll
:hide? true
:public? false}
;; ignore this property when rtc,
;; since users frequently click the sort button to view table content temporarily,
;; but this action does not need to be synchronized with other clients.
:rtc {:rtc/ignore-attr-when-init-upload true
:rtc/ignore-attr-when-init-download true
:rtc/ignore-attr-when-syncing true}}
@@ -438,47 +441,32 @@
:schema
{:type :map
:hide? true
:public? false}
:rtc {:rtc/ignore-attr-when-init-upload true
:rtc/ignore-attr-when-init-download true
:rtc/ignore-attr-when-syncing true}}
:public? false}}
:logseq.property.table/hidden-columns {:title "View hidden columns"
:schema
{:type :keyword
:cardinality :many
:hide? true
:public? false}
:rtc {:rtc/ignore-attr-when-init-upload true
:rtc/ignore-attr-when-init-download true
:rtc/ignore-attr-when-syncing true}}
:public? false}}
:logseq.property.table/ordered-columns {:title "View ordered columns"
:schema
{:type :coll
:hide? true
:public? false}
:rtc {:rtc/ignore-attr-when-init-upload true
:rtc/ignore-attr-when-init-download true
:rtc/ignore-attr-when-syncing true}}
:public? false}}
:logseq.property.table/sized-columns {:title "View columns settings"
:schema
{:type :map
:hide? true
:public? false}
:rtc {:rtc/ignore-attr-when-init-upload true
:rtc/ignore-attr-when-init-download true
:rtc/ignore-attr-when-syncing true}}
:public? false}}
:logseq.property.table/pinned-columns {:title "Table view pinned columns"
:schema
{:type :property
:cardinality :many
:hide? true
:public? false}
:rtc {:rtc/ignore-attr-when-init-upload true
:rtc/ignore-attr-when-init-download true
:rtc/ignore-attr-when-syncing true}}
:public? false}}
:logseq.property/view-for {:title "This view belongs to"
:schema
{:type :node

View File

@@ -65,26 +65,32 @@
(into [property-tx]
(closed-values->blocks property))))
(defn- build-property-value-block
(defn build-property-value-block
"Builds a property value entity given a block map/entity, a property entity or
ident and its property value"
[block property value & {:keys [block-uuid]}]
ident and its property value. Takes the following options:
* :block-uuid - :block/uuid for property value entity
* :properties - Additional properties and attributes to add to entity"
[block property value & {:keys [block-uuid properties]}]
(let [block-id (or (:db/id block) (:db/ident block))]
(-> (merge
{:block/uuid (or block-uuid (common-uuid/gen-uuid))
:block/page (if (:block/page block)
(:db/id (:block/page block))
(cond->
(merge
{:block/uuid (or block-uuid (common-uuid/gen-uuid))
:block/page (if (:block/page block)
(:db/id (:block/page block))
;; page block
block-id)
:block/parent block-id
:logseq.property/created-from-property (if (= (:db/ident property) :logseq.property/default-value)
block-id
(or (:db/id property) {:db/ident (:db/ident property)}))
:block/order (db-order/gen-key)}
(if (db-property-type/property-value-content? (:logseq.property/type property) property)
{:logseq.property/value value}
{:block/title value}))
common-util/block-with-timestamps)))
block-id)
:block/parent block-id
:logseq.property/created-from-property (if (= (:db/ident property) :logseq.property/default-value)
block-id
(or (:db/id property) {:db/ident (:db/ident property)}))
:block/order (db-order/gen-key)}
(if (db-property-type/property-value-content? (:logseq.property/type property) property)
{:logseq.property/value value}
{:block/title value}))
true
common-util/block-with-timestamps
properties
(merge properties))))
(defn build-property-values-tx-m
"Builds a map of property names to their property value blocks to be
@@ -99,7 +105,7 @@
(let [block' (if (:db/id block) block (assoc block :db/id [:block/uuid (:block/uuid block)]))]
(->> properties
(map (fn [[k v]]
(let [property-map (if (map? k) k {:db/ident k})
(let [{:keys [property-value-properties] :as property-map} (if (map? k) k {:db/ident k})
gen-uuid-value-prefix (when pure?
(or (:db/ident block) (:block/uuid block)))]
(assert (:db/ident property-map) "Key in map must have a :db/ident")
@@ -108,15 +114,20 @@
(if (set? v)
(set (map #(build-property-value-block
block' property-map %
(when pure?
{:block-uuid
(common-uuid/gen-uuid :builtin-block-uuid (str gen-uuid-value-prefix "-" %))}))
(cond-> {}
property-value-properties
(assoc :properties property-value-properties)
pure?
(assoc :block-uuid
(common-uuid/gen-uuid :builtin-block-uuid (str gen-uuid-value-prefix "-" %)))))
v))
(build-property-value-block block' property-map v
(when pure?
{:block-uuid
(common-uuid/gen-uuid
:builtin-block-uuid (str gen-uuid-value-prefix "-" v))})))])))
(cond-> {}
property-value-properties
(assoc :properties property-value-properties)
pure?
(assoc :block-uuid
(common-uuid/gen-uuid :builtin-block-uuid (str gen-uuid-value-prefix "-" v))))))])))
(into {}))))
(defn build-properties-with-ref-values

View File

@@ -99,6 +99,30 @@
"Provides the next temp :db/id to use in a create-graph transact!"
#(swap! current-db-id dec))
(defn- build-property-map-for-pvalue-tx
"Returns a property map if the given property pair should have a property value entity constructured
or nil if it should not. Property maps must at least contain the :db/ident and :logseq.property/type keys"
[k v new-block properties-config all-idents]
(if-let [built-in-type (get-in db-property/built-in-properties [k :schema :type])]
(if (and (db-property-type/value-ref-property-types built-in-type)
;; closed values are referenced by their :db/ident so no need to create values
(not (get-in db-property/built-in-properties [k :closed-values])))
{:db/ident k
:logseq.property/type built-in-type}
(when-let [built-in-type' (get (or (:build/properties-ref-types new-block)
;; Reasonable default for properties like logseq.property/default-value
{:entity :number})
built-in-type)]
{:db/ident k
:logseq.property/type built-in-type'}))
(when (and (db-property-type/value-ref-property-types (get-in properties-config [k :logseq.property/type]))
;; Don't build property value entity if values are :block/uuid refs
(if (set? v) (not (vector? (first v))) (not (vector? v))))
(let [prop-type (get-in properties-config [k :logseq.property/type])]
{:db/ident (get-ident all-idents k)
:original-property-id k
:logseq.property/type prop-type}))))
(defn- ->property-value-tx-m
"Given a new block and its properties, creates a map of properties which have values of property value tx.
This map is used for both creating the new property values and then adding them to a block.
@@ -106,28 +130,18 @@
[new-block properties properties-config all-idents]
(->> properties
(keep (fn [[k v]]
(if-let [built-in-type (get-in db-property/built-in-properties [k :schema :type])]
(if (and (db-property-type/value-ref-property-types built-in-type)
;; closed values are referenced by their :db/ident so no need to create values
(not (get-in db-property/built-in-properties [k :closed-values])))
(let [property-map {:db/ident k
:logseq.property/type built-in-type}]
[property-map v])
(when-let [built-in-type' (get (or (:build/properties-ref-types new-block)
;; Reasonable default for properties like logseq.property/default-value
{:entity :number})
built-in-type)]
(let [property-map {:db/ident k
:logseq.property/type built-in-type'}]
[property-map v])))
(when (and (db-property-type/value-ref-property-types (get-in properties-config [k :logseq.property/type]))
;; TODO: Support translate-property-value without this hack
(not (vector? v)))
(let [prop-type (get-in properties-config [k :logseq.property/type])
property-map {:db/ident (get-ident all-idents k)
:original-property-id k
:logseq.property/type prop-type}]
[property-map v])))))
(when-let [property-map (build-property-map-for-pvalue-tx k v new-block properties-config all-idents)]
[(let [pvalue-attrs (when (:build/property-value v)
(merge (:build/properties v)
{:block/tags (mapv #(hash-map :db/ident (get-ident all-idents %))
(:build/tags v))}
(select-keys v [:block/created-at :block/updated-at])))]
(cond-> property-map
(and (:build/property-value v) (seq pvalue-attrs))
(assoc :property-value-properties pvalue-attrs)))
(if (:build/property-value v)
(or (:logseq.property/value v) (:block/title v))
v)])))
(db-property-build/build-property-values-tx-m new-block)))
(defn- extract-basic-content-refs

View File

@@ -12,7 +12,8 @@
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.property :as db-property]
[logseq.db.sqlite.build :as sqlite-build]))
[logseq.db.sqlite.build :as sqlite-build]
[medley.core :as medley]))
;; Export fns
;; ==========
@@ -37,54 +38,84 @@
{:build/journal (:block/journal-day page-entity)}
{:block/title (block-title page-entity)}))
(defn- buildable-property-value-entity
"Converts property value to a buildable version"
[property-ent pvalue {:keys [property-value-uuids?]}]
(cond (and (not property-value-uuids?) (ldb/internal-page? pvalue))
(defn- build-pvalue-entity-for-build-page
[pvalue]
(cond (ldb/internal-page? pvalue)
;; Should page properties be pulled here?
[:build/page (cond-> (shallow-copy-page pvalue)
(seq (:block/tags pvalue))
(assoc :build/tags (->build-tags (:block/tags pvalue))))]
(and (not property-value-uuids?) (entity-util/journal? pvalue))
[:build/page {:build/journal (:block/journal-day pvalue)}]
:else
(if (contains? #{:node :date} (:logseq.property/type property-ent))
;; Idents take precedence over uuid because they are keep data graph-agnostic
(if (:db/ident pvalue)
(:db/ident pvalue)
;; Use metadata distinguish from block references that don't exist like closed values
^::existing-property-value? [:block/uuid (:block/uuid pvalue)])
(or (:db/ident pvalue)
;; nbb-compatible version of db-property/property-value-content
(or (block-title pvalue)
(:logseq.property/value pvalue))))))
(entity-util/journal? pvalue)
[:build/page {:build/journal (:block/journal-day pvalue)}]))
(defn- build-pvalue-entity-default [ent-properties pvalue options]
(if (or (seq ent-properties) (seq (:block/tags pvalue)))
(cond-> {:build/property-value :block
:block/title (or (block-title pvalue)
(:logseq.property/value pvalue))}
(seq (:block/tags pvalue))
(assoc :build/tags (->build-tags (:block/tags pvalue)))
(seq ent-properties)
(assoc :build/properties ent-properties)
(:include-timestamps? options)
(merge (select-keys pvalue [:block/created-at :block/updated-at])))
;; nbb-compatible version of db-property/property-value-content
(or (block-title pvalue)
(:logseq.property/value pvalue))))
(defn- buildable-properties
"Originally copied from db-test/readable-properties. Modified so that property values are
valid sqlite.build EDN"
[db ent-properties properties-config options]
(->> ent-properties
(map (fn [[k v]]
[k
(if (and (:block/closed-value-property v) (not (db-property/logseq-property? k)))
(if-let [closed-uuid (some #(when (= (:value %) (db-property/property-value-content v))
(:uuid %))
(get-in properties-config [k :build/closed-values]))]
[:block/uuid closed-uuid]
(throw (ex-info (str "No closed value found for content: " (pr-str (db-property/property-value-content v))) {:properties properties-config})))
(cond
(de/entity? v)
(buildable-property-value-entity (d/entity db k) v options)
(and (set? v) (every? de/entity? v))
(let [property-ent (d/entity db k)]
(set (map #(buildable-property-value-entity property-ent % options) v)))
:else
v))]))
(into {})))
(letfn [(build-pvalue-entity
[db' property-ent pvalue properties-config' {:keys [property-value-uuids?] :as options'}]
(if-let [build-page (and (not property-value-uuids?) (build-pvalue-entity-for-build-page pvalue))]
build-page
(if (contains? #{:node :date} (:logseq.property/type property-ent))
;; Idents take precedence over uuid because they are keep data graph-agnostic
(if (:db/ident pvalue)
(:db/ident pvalue)
;; Use metadata distinguish from block references that don't exist like closed values
^::existing-property-value? [:block/uuid (:block/uuid pvalue)])
(or (:db/ident pvalue)
(let [ent-properties* (->> (apply dissoc (db-property/properties pvalue)
:logseq.property/value :logseq.property/created-from-property
db-property/public-db-attribute-properties)
;; TODO: Allow user properties when sqlite.build supports it
(medley/filter-keys db-property/internal-property?))
ent-properties (when (and (not (:block/closed-value-property pvalue)) (seq ent-properties*))
(buildable-properties db' ent-properties* properties-config' options'))]
(build-pvalue-entity-default ent-properties pvalue options'))))))]
(->> ent-properties
(map (fn [[k v]]
[k
;; handle user closed value properties. built-ins have idents and shouldn't be handled here
(if (and (not (db-property/logseq-property? k))
(or (:block/closed-value-property v)
(and (set? v) (:block/closed-value-property (first v)))))
(let [find-closed-uuid (fn [val]
(or (some #(when (= (:value %) (db-property/property-value-content val))
(:uuid %))
(get-in properties-config [k :build/closed-values]))
(throw (ex-info (str "No closed value found for content: " (pr-str (db-property/property-value-content val))) {:properties properties-config}))))]
(if (set? v)
(set (map #(vector :block/uuid (find-closed-uuid %)) v))
[:block/uuid (find-closed-uuid v)]))
(cond
(de/entity? v)
(build-pvalue-entity db (d/entity db k) v properties-config options)
(and (set? v) (every? de/entity? v))
(let [property-ent (d/entity db k)]
(set (map #(build-pvalue-entity db property-ent % properties-config options) v)))
:else
v))]))
(into {}))))
(defn- build-export-properties
"The caller of this fn is responsible for building :build/:property-classes unless shallow-copy?"
[db user-property-idents {:keys [include-properties? include-timestamps? include-uuid? shallow-copy?] :as options}]
[db user-property-idents {:keys [include-properties? include-timestamps? include-uuid? shallow-copy? include-alias?] :as options}]
(let [properties-config-by-ent
(->> user-property-idents
(map (fn [ident]
@@ -98,6 +129,8 @@
(assoc :block/uuid (:block/uuid property) :build/keep-uuid? true)
include-timestamps?
(merge (select-keys property [:block/created-at :block/updated-at]))
(and (not shallow-copy?) include-alias? (:block/alias property))
(assoc :block/alias (set (map #(vector :block/uuid (:block/uuid %)) (:block/alias property))))
(and (not shallow-copy?) (:logseq.property/classes property))
(assoc :build/property-classes (mapv :db/ident (:logseq.property/classes property)))
(seq closed-values)
@@ -129,8 +162,7 @@
(defn- build-export-class
"The caller of this fn is responsible for building any classes or properties from this fn
unless shallow-copy?"
[class-ent {:keys [include-parents? include-uuid? shallow-copy? include-timestamps?]
:or {include-parents? true}}]
[class-ent {:keys [include-uuid? shallow-copy? include-timestamps? include-alias?]}]
(cond-> (select-keys class-ent [:block/title :block/collapsed?])
include-uuid?
(assoc :block/uuid (:block/uuid class-ent) :build/keep-uuid? true)
@@ -139,11 +171,10 @@
(and (:logseq.property.class/properties class-ent) (not shallow-copy?))
(assoc :build/class-properties
(mapv :db/ident (:logseq.property.class/properties class-ent)))
(and (not shallow-copy?) (:block/alias class-ent))
(and (not shallow-copy?) include-alias? (:block/alias class-ent))
(assoc :block/alias (set (map #(vector :block/uuid (:block/uuid %)) (:block/alias class-ent))))
;; It's caller's responsibility to ensure parent is included in final export
(and include-parents?
(not shallow-copy?)
(and (not shallow-copy?)
(:logseq.property/parent class-ent)
(not= :logseq.class/Root (:db/ident (:logseq.property/parent class-ent))))
(assoc :build/class-parent
@@ -364,12 +395,26 @@
:classes (apply merge (map :classes uuid-block-pages))
:pages-and-blocks (mapv #(select-keys % [:page :blocks]) uuid-block-pages)}))
(defn sort-pages-and-blocks
"Provide a reliable sort order since this tends to be large. Helps with diffing
and readability"
[pages-and-blocks]
(vec
(sort-by #(or (get-in % [:page :block/title])
(some-> (get-in % [:page :build/journal]) str)
(str (get-in % [:page :block/uuid])))
pages-and-blocks)))
(defn- finalize-export-maps
"Given final export maps, merges them, adds any missing class parents and merges those in"
"Given final export maps, merges them, adds any missing class parents and merges those in.
If :pages-and-blocks exist, sorts them in order to have reliable sort order"
[db & export-maps]
(let [final-export* (apply merge-export-maps export-maps)
class-parents-export (some->> (:classes final-export*) (build-class-parents-export db))]
(merge-export-maps final-export* class-parents-export)))
class-parents-export (some->> (:classes final-export*) (build-class-parents-export db))
merged-map (merge-export-maps final-export* class-parents-export)]
(cond-> merged-map
(:pages-and-blocks merged-map)
(update :pages-and-blocks sort-pages-and-blocks))))
(defn- build-block-export
"Exports block for given block eid"
@@ -387,7 +432,7 @@
(merge {::block (:node node-export)}
block-export)))
(defn- build-page-blocks-export [db page-entity {:keys [properties classes blocks ontology-page?] :as options}]
(defn- build-page-blocks-export [db page-entity {:keys [properties classes blocks ontology-page? include-alias?] :as options}]
(let [options' (cond-> (dissoc options :classes :blocks :graph-ontology)
(:exclude-ontology? options)
(assoc :properties (get-in options [:graph-ontology :properties])))
@@ -400,7 +445,7 @@
(:node page-ent-export)
(merge (dissoc (:node page-ent-export) :block/title)
(shallow-copy-page page-entity)
(when (:block/alias page-entity)
(when (and include-alias? (:block/alias page-entity))
{:block/alias (set (map #(vector :block/uuid (:block/uuid %)) (:block/alias page-entity)))})))
page-blocks-export {:pages-and-blocks [{:page page :blocks blocks}]
:properties properties
@@ -439,7 +484,9 @@
page-export (finalize-export-maps db page-export* uuid-block-export content-ref-export)]
page-export))
(defn build-view-nodes-export* [db nodes opts]
(defn- build-nodes-export
"Export a mix of pages and blocks"
[db nodes opts]
(let [node-pages (filter entity-util/page? nodes)
pages-export
(merge
@@ -452,9 +499,7 @@
(->> node-blocks
(group-by :block/page)
(map (fn [[parent-page-ent blocks]]
(merge (build-blocks-export db
(sort-by :block/order blocks)
(merge opts {:include-children? false}))
(merge (build-blocks-export db (sort-by :block/order blocks) opts)
{:page (shallow-copy-page parent-page-ent)}))))
pages-to-blocks-export
{:properties (apply merge (map :properties pages-to-blocks))
@@ -474,7 +519,29 @@
{:keys [content-ref-uuids content-ref-ents] :as content-ref-export}
(build-content-ref-export db (into nodes property-value-ents))
{:keys [pvalue-uuids] :as nodes-export}
(build-view-nodes-export* db nodes {:include-uuid-fn content-ref-uuids})
(build-nodes-export db nodes {:include-uuid-fn content-ref-uuids :include-children? false})
uuid-block-export (build-uuid-block-export db pvalue-uuids content-ref-ents {})
view-nodes-export (finalize-export-maps db nodes-export uuid-block-export content-ref-export)]
view-nodes-export))
(defn- build-selected-nodes-export
"Exports given nodes selected by a user. Nodes can be a mix of blocks and pages"
[db eids]
(let [top-level-nodes (map #(d/entity db %) eids)
children-nodes (->> top-level-nodes
;; Remove pages b/c when selected their children are not highlighted
(remove entity-util/page?)
(mapcat #(rest (ldb/get-block-and-children db (:block/uuid %))))
(remove :logseq.property/created-from-property))
nodes (concat top-level-nodes children-nodes)
property-value-ents (mapcat #(->> (apply dissoc (db-property/properties %) db-property/public-db-attribute-properties)
vals
(filter de/entity?))
nodes)
{:keys [content-ref-uuids content-ref-ents] :as content-ref-export}
(build-content-ref-export db (into nodes property-value-ents))
{:keys [pvalue-uuids] :as nodes-export}
(build-nodes-export db nodes {:include-uuid-fn content-ref-uuids :include-children? true})
uuid-block-export (build-uuid-block-export db pvalue-uuids content-ref-ents {})
view-nodes-export (finalize-export-maps db nodes-export uuid-block-export content-ref-export)]
view-nodes-export))
@@ -607,16 +674,6 @@
:blocks (sqlite-build/update-each-block blocks remove-uuid-if-not-ref)})
pages-and-blocks))))))
(defn sort-pages-and-blocks
"Provide a reliable sort order since this tends to be large. Helps with diffing
and readability"
[pages-and-blocks]
(vec
(sort-by #(or (get-in % [:page :block/title])
(some-> (get-in % [:page :build/journal]) str)
(str (get-in % [:page :block/uuid])))
pages-and-blocks)))
(defn- add-ontology-for-include-namespaces
"Adds :properties to export for given namespace parents. Current use case is for :exclude-namespaces
so no need to add :classes yet"
@@ -644,7 +701,8 @@
* :exclude-built-in-pages? - When set, built-in pages are excluded from export
* :exclude-files? - When set, files are excluded from export"
[db {:keys [exclude-files?] :as options*}]
(let [options (merge options* {:property-value-uuids? true})
(let [options (merge options* {:property-value-uuids? true
:include-alias? true})
content-ref-uuids (get-graph-content-ref-uuids db options)
ontology-options (merge options {:include-uuid? true})
ontology-export (build-graph-ontology-export db ontology-options)
@@ -702,7 +760,10 @@
;; Only looks one-level deep in properties e.g. not inside :build/page
;; Doesn't find :block/link refs
ref-uuids
(->> (concat (mapcat get-pvalue-uuids (vals classes))
(->> (concat (mapcat #(map second (:block/alias %)) (vals classes))
(mapcat #(map second (:block/alias %)) (vals properties))
(mapcat #(map second (:block/alias (:page %))) pages-and-blocks)
(mapcat get-pvalue-uuids (vals classes))
(mapcat get-pvalue-uuids (vals properties))
(mapcat (comp get-pvalue-uuids :page) pages-and-blocks)
(mapcat #(sqlite-build/extract-from-blocks (:blocks %) get-pvalue-uuids) pages-and-blocks))
@@ -736,6 +797,8 @@
(build-page-export db (:page-id options))
:view-nodes
(build-view-nodes-export db (:node-ids options))
:selected-nodes
(build-selected-nodes-export db (:node-ids options))
:graph-ontology
(build-graph-ontology-export db {})
:graph

View File

@@ -205,4 +205,43 @@
(->> (d/q '[:find [?b ...] :in $ ?page-id :where [?b :block/page ?page-id]]
@conn [:block/uuid property-uuid])
(map #(:block/title (d/entity @conn %)))))
"Property page has correct blocks")))
"Property page has correct blocks")))
(deftest property-value-with-properties-and-tags
(let [conn (db-test/create-conn-with-blocks
{:properties {:p1 {:logseq.property/type :default}}
:classes {:C1 {}}
:pages-and-blocks
[{:page {:block/title "page1"}
:blocks [{:block/title "block has pvalue with built-in tag"
:build/properties
{:p1 {:build/property-value :block
:block/title "t1"
:build/tags [:logseq.class/Task]}}}
{:block/title "block has pvalue with user tag"
:build/properties
{:p1 {:build/property-value :block
:block/title "u1"
:build/tags [:C1]}}}
{:block/title "Todo query",
:build/tags [:logseq.class/Query],
:build/properties
{:logseq.property/query
{:build/property-value :block
:block/title "{:query (task Todo)}"
:build/properties
{:logseq.property.code/lang "clojure"
:logseq.property.node/display-type :code}}}}]}]})]
(is (= {:logseq.property.node/display-type :code
:logseq.property.code/lang "clojure"}
(-> (db-test/find-block-by-content @conn "{:query (task Todo)}")
db-test/readable-properties
(dissoc :logseq.property/created-from-property))))
(is (= {:block/tags [:logseq.class/Task]}
(-> (db-test/find-block-by-content @conn "t1")
db-test/readable-properties
(dissoc :logseq.property/created-from-property))))
(is (= {:block/tags [:user.class/C1]}
(-> (db-test/find-block-by-content @conn "u1")
db-test/readable-properties
(dissoc :logseq.property/created-from-property))))))

View File

@@ -57,17 +57,21 @@
(sqlite-export/build-export @import-conn {:export-type :page :page-id (:db/id page2)})))
(defn- import-second-time-assertions [conn conn2 page-title original-data
& {:keys [transform-expected-blocks]
& {:keys [transform-expected-blocks build-journal]
:or {transform-expected-blocks (fn [bs] (into bs bs))}}]
(let [page (db-test/find-page-by-title @conn2 page-title)
imported-page (export-page-and-import-to-another-graph conn conn2 page-title)
updated-page (db-test/find-page-by-title @conn2 page-title)
expected-page-and-blocks
(update-in (:pages-and-blocks original-data) [0 :blocks] transform-expected-blocks)]
(update-in (:pages-and-blocks original-data) [0 :blocks] transform-expected-blocks)
filter-imported-page (if build-journal
#(= build-journal (get-in % [:page :build/journal]))
#(= (get-in % [:page :block/title]) page-title))]
(assert (first expected-page-and-blocks))
;; Assume first page is one being imported for now
(is (= (first expected-page-and-blocks)
(first (:pages-and-blocks imported-page)))
(first (filter filter-imported-page (:pages-and-blocks imported-page))))
"Blocks are appended to existing page")
(is (= (:block/created-at page) (:block/created-at updated-page))
"Existing page didn't get re-created")
@@ -323,7 +327,8 @@
(is (= (-> (:pages-and-blocks original-data)
(medley/dissoc-in [1 :blocks 0 :build/properties])
;; shallow block means this page doesn't get included
butlast)
butlast
sort-pages-and-blocks)
(:pages-and-blocks imported-page))
"Page's blocks are imported")
@@ -412,7 +417,7 @@
(is (= (:pages-and-blocks original-data) (:pages-and-blocks imported-page))
"Page's blocks are imported")
(import-second-time-assertions conn conn2 journal-title original-data)))
(import-second-time-assertions conn conn2 journal-title original-data {:build-journal 20250210})))
(deftest import-page-with-different-property-types
(let [block-object-uuid (random-uuid)
@@ -458,7 +463,8 @@
"Page's classes are imported")
(is (= (-> (:pages-and-blocks original-data)
;; adjust shallow block
(medley/dissoc-in [1 :blocks 0 :build/tags]))
(medley/dissoc-in [1 :blocks 0 :build/tags])
sort-pages-and-blocks)
(:pages-and-blocks imported-page))
"Page's blocks are imported")
@@ -527,7 +533,44 @@
imported-nodes (sqlite-export/build-export @conn2 {:export-type :view-nodes
:node-ids (get-node-ids @conn2)})]
(is (= (:pages-and-blocks original-data) (:pages-and-blocks imported-nodes)))
(is (= (sort-pages-and-blocks (:pages-and-blocks original-data)) (:pages-and-blocks imported-nodes)))
(is (= (expand-properties (:properties original-data)) (:properties imported-nodes)))
(is (= (expand-classes (:classes original-data)) (:classes imported-nodes)))))
(deftest import-selected-nodes
(let [original-data
;; Test a mix of pages and blocks
{:properties {:user.property/p1 {:logseq.property/type :default}}
:classes {:user.class/class1 {}}
:pages-and-blocks [{:page {:block/title "page1"}
:blocks [{:block/title "b1"
:build/properties {:user.property/p1 "ok"}
:build/children [{:block/title "b2"}]}
{:block/title "b3"
:build/tags [:user.class/class1]
:build/children [{:block/title "b4"}]}]}
{:page {:block/title "page2"}
:blocks [{:block/title "dont export"}]}]}
conn (db-test/create-conn-with-blocks original-data)
get-node-ids (fn [db]
(->> [(db-test/find-block-by-content db "b1")
(db-test/find-page-by-title db "b3")
(db-test/find-page-by-title db "page2")]
(remove nil?)
(mapv #(vector :block/uuid (:block/uuid %)))))
conn2 (db-test/create-conn)
{:keys [init-tx block-props-tx] :as _txs}
(-> (sqlite-export/build-export @conn {:export-type :selected-nodes :node-ids (get-node-ids @conn)})
(sqlite-export/build-import @conn2 {}))
;; _ (cljs.pprint/pprint _txs)
_ (d/transact! conn2 init-tx)
_ (d/transact! conn2 block-props-tx)
_ (validate-db @conn2)
imported-nodes (sqlite-export/build-export @conn2 {:export-type :selected-nodes :node-ids (get-node-ids @conn2)})]
(is (= (->> (:pages-and-blocks original-data)
(map #(if (= (get-in % [:page :block/title]) "page2") (dissoc % :blocks) %)))
(:pages-and-blocks imported-nodes)))
(is (= (expand-properties (:properties original-data)) (:properties imported-nodes)))
(is (= (expand-classes (:classes original-data)) (:classes imported-nodes)))))
@@ -558,6 +601,7 @@
:logseq.property/default-value 42})}
:user.property/default-closed
{:logseq.property/type :default
:db/cardinality :db.cardinality/many
:build/closed-values [{:value "joy" :uuid closed-value-uuid}
{:value "sad" :uuid (random-uuid)}]}
:user.property/checkbox {:logseq.property/type :checkbox}
@@ -587,10 +631,25 @@
:user.property/node #{[:block/uuid page-pvalue-uuid]}}}
:blocks [{:block/title "b1"
:build/properties {:user.property/num 1
:user.property/default-closed [:block/uuid closed-value-uuid]
:user.property/default-closed #{[:block/uuid closed-value-uuid]}
:user.property/date [:block/uuid journal-uuid]}}
{:block/title "b2" :build/properties {:user.property/node #{[:block/uuid page-object-uuid]}}}
{:block/title "b3" :build/properties {:user.property/node #{[:block/uuid page-object-uuid]}}}]}
{:block/title "b3" :build/properties {:user.property/node #{[:block/uuid page-object-uuid]}}}
{:block/title "Example advanced query",
:build/tags [:logseq.class/Query],
:build/properties
{:logseq.property/query
{:build/property-value :block
:block/title "{:query (task Todo)}"
:build/properties
{:logseq.property.code/lang "clojure"
:logseq.property.node/display-type :code}}}}
{:block/title "block has property value with tags and properties"
:build/properties
{:user.property/url
{:build/property-value :block
:block/title "https://example.com"
:build/tags [:user.class/MyClass]}}}]}
{:page {:block/title "page object"
:block/uuid page-object-uuid
:build/keep-uuid? true}