فهرست منبع

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 2 ماه پیش
والد
کامیت
5adc5dcdc6
4فایلهای تغییر یافته به همراه80 افزوده شده و 38 حذف شده
  1. 12 10
      deps/cli/README.md
  2. 1 1
      deps/cli/src/logseq/cli.cljs
  3. 62 25
      deps/cli/src/logseq/cli/commands/query.cljs
  4. 5 2
      deps/cli/src/logseq/cli/spec.cljs

+ 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"}})