Procházet zdrojové kódy

enhance: entity queries per Tienson's feedback

Can use uuid string as shorthand identifier. Can make properties
readable with option. Also wire up local query to use default catch
handler
Gabriel Horner před 2 měsíci
rodič
revize
5adc5dcdc6

+ 12 - 10
deps/cli/README.md

@@ -61,30 +61,32 @@ dev:db-export woot woot.edn && dev:db-create woot2 woot.edn
 dev:db-diff woot woot2
 ...
 
-# Local query using `d/entity` ids like a :db/ident
-$ logseq query woot :logseq.class/Tag
-({:block/uuid #uuid "00000002-5389-0208-3000-000000000000",
-  :block/updated-at 1751985393459,
+# Query a graph locally using `d/entity` id(s) like an integer or a :db/ident
+# Can also specify a uuid string to fetch an entity
+$ logseq query woot 10 :logseq.class/Tag
+({:db/id 10,
+  :db/ident :logseq.kv/graph-git-sha,
+  :kv/value "f736895b1b-dirty"}
+ {:block/uuid #uuid "00000002-5389-0208-3000-000000000000",
+  :block/updated-at 1751990934670,
   :logseq.property.class/extends #{{:db/id 1}},
-  :block/refs #{{:db/id 1} {:db/id 23} {:db/id 40}},
-  :block/created-at 1751985393459,
+  :block/created-at 1751990934670,
   :logseq.property/built-in? true,
   :block/tags #{{:db/id 2}},
   :block/title "Tag",
   :db/id 2,
   :db/ident :logseq.class/Tag,
-  :block/path-refs #{{:db/id 1} {:db/id 23} {:db/id 40}},
   :block/name "tag"})
 
-# Local query using datalog
+# Query a graph using a datalog query
 $ logseq query woot '[:find (pull ?b [*]) :where [?b :kv/value]]'
 [{:db/id 5, :db/ident :logseq.kv/db-type, :kv/value "db"}
  {:db/id 6,
   :db/ident :logseq.kv/schema-version,
   :kv/value {:major 65, :minor 7}}
 
-# Api query using a simple query
-# Api query can also take a datalog query like the local query
+# Query the current graph using the api server
+# An api query can be a datalog query or a simple query
 $ logseq query '(task DOING)' -a my-token
  [{:journalDay 20250717,
    :name "jul 17th, 2025",

+ 1 - 1
deps/cli/src/logseq/cli.cljs

@@ -74,7 +74,7 @@
     :spec cli-spec/search}
    {:cmds ["query"] :desc "Query DB graph(s)"
     :fn (lazy-load-fn 'logseq.cli.commands.query/query)
-    :args->opts [:graph :queries] :coerce {:queries []} :no-keyword-opts true
+    :args->opts [:graph :args] :coerce {:args []} :no-keyword-opts true
     :spec cli-spec/query}
    {:cmds ["export-edn"] :desc "Export DB graph as EDN"
     :fn (lazy-load-fn 'logseq.cli.commands.export-edn/export)

+ 62 - 25
deps/cli/src/logseq/cli/commands/query.cljs

@@ -5,8 +5,11 @@
             [clojure.pprint :as pprint]
             [clojure.string :as string]
             [datascript.core :as d]
+            [datascript.impl.entity :as de]
             [logseq.cli.util :as cli-util]
+            [logseq.common.util :as common-util]
             [logseq.db.common.sqlite-cli :as sqlite-cli]
+            [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.rules :as rules]
             [promesa.core :as p]))
 
@@ -24,32 +27,66 @@
                               res)]
                 (pprint/pprint results)))
             (cli-util/api-handle-error-response resp)))
-        (p/catch (fn [err]
-                   (js/console.error "Error:" err)
-                   (js/process.exit 1))))))
+        (p/catch cli-util/command-catch-handler))))
+
+(defn- readable-properties
+  "Expands an entity's properties and tags to be readable. Similar to
+   db-test/readable-properties but to be customized for CLI use"
+  [ent]
+  (->> (db-property/properties ent)
+       (mapv (fn [[k v]]
+               [k
+                (cond
+                  (#{:block/tags :logseq.property.class/extends} k)
+                  (mapv :db/ident v)
+                  (and (set? v) (every? de/entity? v))
+                  (set (map db-property/property-value-content v))
+                  (de/entity? v)
+                  (or (:db/ident v) (db-property/property-value-content v))
+                  :else
+                  v)]))
+       (into {})))
+
+(defn- local-entities-query
+  "Queries by calling d/entity"
+  [db properties-expand args]
+  (map #(when-let [ent (d/entity db
+                                 (cond
+                                   (and (string? %) (common-util/uuid-string? %))
+                                   [:block/uuid (uuid %)]
+                                   (string? %)
+                                   (edn/read-string %)
+                                   :else
+                                   %))]
+          (let [m (into {:db/id (:db/id ent)} ent)]
+            (if properties-expand
+              (merge m (readable-properties m))
+              m)))
+       args))
+
+(defn- local-query
+  [{{:keys [graph args graphs properties-readable]} :opts}]
+  (let [graphs' (into [graph] graphs)]
+    (doseq [graph' graphs']
+      (if (fs/existsSync (cli-util/get-graph-dir graph'))
+        (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
+              query* (when (string? (first args)) (common-util/safe-read-string {:log-error? false} (first args)))
+              ;; If datalog query detected run it or else default to entity lookups
+              results (if (and (vector? query*) (= :find (first query*)))
+                          ;; assumes no :in are in queries
+                        (let [query' (into query* [:in '$ '%])
+                              res (d/q query' @conn (rules/extract-rules rules/db-query-dsl-rules))]
+                          ;; Remove nesting for most queries which just have one :find binding
+                          (if (= 1 (count (first res))) (mapv first res) res))
+                        (local-entities-query @conn properties-readable args))]
+          (when (> (count graphs') 1)
+            (println "Results for graph" (pr-str graph')))
+          (pprint/pprint results))
+        (println "Graph" (pr-str graph') "does not exist")))))
 
 (defn query
-  [{{:keys [graph queries graphs api-query-token]} :opts}]
+  [{{:keys [graph args api-query-token]} :opts :as m}]
   (if api-query-token
     ;; graph can be query since it's not used for api-query
-    (api-query (or graph (first queries)) api-query-token)
-    (let [graphs' (into [graph] graphs)]
-      (doseq [graph' graphs']
-        (if (fs/existsSync (cli-util/get-graph-dir graph'))
-          (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
-                query* (when (string? (first queries)) (edn/read-string (first queries)))
-                ;; If datalog query detected run it or else default to entity lookups
-                results (if (and (vector? query*) (= :find (first query*)))
-                          ;; assumes no :in are in queries
-                          (let [query' (into query* [:in '$ '%])
-                                res (d/q query' @conn (rules/extract-rules rules/db-query-dsl-rules))]
-                            ;; Remove nesting for most queries which just have one :find binding
-                            (if (= 1 (count (first res))) (mapv first res) res))
-                          (map #(when-let [ent (d/entity @conn
-                                                         (if (string? %) (edn/read-string %) %))]
-                                  (into {:db/id (:db/id ent)} ent))
-                               queries))]
-            (when (> (count graphs') 1)
-              (println "Results for graph" (pr-str graph')))
-            (pprint/pprint results))
-          (println "Graph" (pr-str graph') "does not exist"))))))
+    (api-query (or graph (first args)) api-query-token)
+    (local-query m)))

+ 5 - 2
deps/cli/src/logseq/cli/spec.cljs

@@ -1,6 +1,6 @@
 (ns logseq.cli.spec
-  "Specs for commands. Normally these would live alongside their commands but are separate
-   because command namespaces are lazy loaded")
+  "Babashka.cli specs for commands. Normally these would live alongside their
+  commands but are separate because command namespaces are lazy loaded")
 
 (def export-edn
   {:include-timestamps? {:alias :T
@@ -25,6 +25,9 @@
   {:graphs {:alias :g
             :coerce []
             :desc "Additional graphs to query"}
+   :properties-readable {:alias :p
+                         :coerce :boolean
+                         :desc "Make properties on entity queries show property values instead of ids"}
    :api-query-token {:alias :a
                      :desc "Query current graph with api server token"}})