fix: :and ref query should return nodes that having at least one ref (#12085)

This commit also adds a new :self-ref rule.

`[[page]]` will uses :self-ref,
`(or [[page1]] [[page2]])` will be `(or [:self-ref page1] [:self-ref
page2]`.
`(and [[page1]] [[page2]])` will be `(and [:page-ref page1] [:page-ref
page2] (or [:self-ref page1] [:self-ref page2]))` to ensure the query
result nodes to have at least one page reference.
This commit is contained in:
Tienson Qin
2025-09-04 17:08:03 +08:00
committed by GitHub
parent 56b54d5f40
commit fa7cd07192
6 changed files with 50 additions and 18 deletions

2
deps/db/bb.edn vendored
View File

@@ -29,7 +29,7 @@
:doc "Lint datalog rules for parsability and unbound variables"
:task (datalog/lint-rules
(set
(concat (mapcat val (merge file-rules/rules rules/rules))
(concat (mapcat val (merge file-rules/rules (dissoc rules/rules :self-ref)))
;; TODO: Update linter to handle false positive on ?str-val for :property
(rules/extract-rules (dissoc file-rules/query-dsl-rules :property))
;; TODO: Update linter to handle false positive on :task, :priority, :*property* rules

View File

@@ -455,8 +455,9 @@
entities-result (if query?
(keep (fn [id]
(let [e (d/entity db id)]
(when-not (contains? query-entity-ids (:db/id (:block/parent e)))
e))) query-entity-ids)
(when-not (= :logseq.property/query (:db/ident (:logseq.property/created-from-property e)))
e)))
query-entity-ids)
(get-view-entities db view-id opts))
entities (if (= feat-type :linked-references)
(:ref-blocks entities-result)

View File

@@ -32,6 +32,11 @@
[?e1 :block/alias ?e2]
[?e2 :block/alias ?e3]]]
:self-ref
'[(self-ref ?b ?page-name)
[?br :block/name ?page-name]
[?b :block/refs ?br]]
:has-ref
'[[(has-ref ?b ?r)
[?b :block/refs ?r]]

View File

@@ -381,11 +381,11 @@ independent of format as format specific heading characters are stripped"
(->>
(d/q
'[:find [(pull ?block ?block-attrs) ...]
:in $ % [?ref-page ...] ?block-attrs
:in $ [?ref-page ...] ?block-attrs
:where
(has-ref ?block ?ref-page)]
[?r :block/name ?ref-page]
[?block :block/refs ?r]]
db
(rules/extract-rules rules/db-query-dsl-rules [:parent :has-ref])
pages
(butlast file-model/file-graph-block-attrs))
(remove (fn [block] (= page-id (:db/id (:block/page block)))))

View File

@@ -486,6 +486,13 @@
{:query (list 'page-ref '?b page-name)
:rules [:page-ref]}))
(defn- build-self-ref
[e]
(let [page-name (-> (page-ref/get-page-name! e)
(util/page-name-sanity-lc))]
{:query (list 'self-ref '?b page-name)
:rules [:self-ref]}))
(defn- build-block-content [e]
{:query (list 'block-content '?b e)
:rules [:block-content]})
@@ -532,7 +539,7 @@ Some bindings in this fn:
* fe - the query operator e.g. `property`"
([e env]
(build-query e (assoc env :vars (atom {})) 0))
([e {:keys [blocks? sample] :as env :or {blocks? (atom nil)}} level]
([e {:keys [form blocks? sample current-filter] :as env :or {blocks? (atom nil)}} level]
; {:post [(or (nil? %) (map? %))]}
(let [fe (first e)
fe (when fe
@@ -547,7 +554,7 @@ Some bindings in this fn:
(when (or
(:db-graph? env)
(and page-ref?
(not (contains? #{'page-property 'page-tags} (:current-filter env))))
(not (contains? #{'page-property 'page-tags} current-filter)))
(contains? #{'between 'property 'private-property 'todo 'task 'priority 'page} fe)
(and (not page-ref?) (string? e)))
(reset! blocks? true))
@@ -559,8 +566,13 @@ Some bindings in this fn:
{:query [e]
:rules []}
(and (= fe 'and) (every? page-ref/page-ref? (rest e)))
(build-query (concat e [(cons 'or (rest e))]) env level)
page-ref?
(build-page-ref e)
(if (or (= current-filter 'or) (= form e))
(build-self-ref e)
(build-page-ref e))
(string? e) ; block content full-text search, could be slow
(build-block-content e)
@@ -688,7 +700,8 @@ Some bindings in this fn:
sample (atom nil)
form (simplify-query form)
{result :query rules :rules}
(when form (build-query form {:sort-by sort-by
(when form (build-query form {:form form
:sort-by sort-by
:blocks? blocks?
:db-graph? db-graph?
:sample sample
@@ -701,14 +714,19 @@ Some bindings in this fn:
(keyword (first result)))]
(add-bindings! (if (= key :and) (rest result) result) opts)))
extract-rules (fn [rules]
(rules/extract-rules rules/db-query-dsl-rules rules {:deps rules/rules-dependencies}))]
(rules/extract-rules rules/db-query-dsl-rules rules {:deps rules/rules-dependencies}))
rules' (if db-graph?
(let [rules' (if (contains? (set rules) :page-ref)
(conj (set rules) :self-ref)
rules)]
(extract-rules rules'))
(->> (concat (map file-rules/query-dsl-rules (remove #{:page-ref} rules))
(when (some #{:page-ref :self-ref} rules)
(extract-rules [:self-ref :page-ref])))
(remove nil?)
vec))]
{:query result'
:rules (if db-graph?
(extract-rules rules)
(->> (concat (map file-rules/query-dsl-rules (remove #{:page-ref} rules))
(when (some #{:page-ref} rules)
(extract-rules [:page-ref])))
vec))
:rules rules'
:sort-by @sort-by
:blocks? (boolean @blocks?)
:sample sample})))

View File

@@ -598,6 +598,7 @@ prop-d:: [[nada]]"}])
:file/content "foo:: bar
- b1 [[page 1]] [[tag2]]
- b2 [[page 2]] [[tag1]]
- b4 [[page 4]] [[tag4]]
- b3"}])
(testing "page-ref queries"
@@ -625,9 +626,16 @@ prop-d:: [[nada]]"}])
(dsl-query "(or [[tag2]] [[page 2]])")))
"OR query")
(comment
;; FIXME: load-test-files doesn't save `b4` to the db when DB_GRAPH=1
(is (= ["b1" "b4"]
(map testable-content
(dsl-query "(or [[tag2]] [[page 4]])")))
"OR query"))
(is (= ["b1"]
(map testable-content
(dsl-query "(or [[tag2]] [[page 3]])")))
(dsl-query "(or [[tag2]] [[page not exists]])")))
"OR query with nonexistent page should return meaningful results")
(is (= (if js/process.env.DB_GRAPH #{"b1" "bar" "b3"} #{"b1" "foo:: bar" "b3"})