query.cljs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. (ns query
  2. "A script that queries any db graph from the commandline e.g.
  3. $ yarn -s nbb-logseq script/query.cljs db-name '[:find (pull ?b [:block/name :block/title]) :where [?b :block/created-at]]'"
  4. (:require ["child_process" :as child-process]
  5. ["os" :as os]
  6. ["path" :as node-path]
  7. [babashka.cli :as cli]
  8. [clojure.edn :as edn]
  9. [clojure.pprint :as pprint]
  10. [clojure.string :as string]
  11. [datascript.core :as d]
  12. [logseq.db.frontend.rules :as rules]
  13. [logseq.db.sqlite.cli :as sqlite-cli]
  14. [nbb.core :as nbb]))
  15. (defn- sh
  16. "Run shell cmd synchronously and print to inherited streams by default. Aims
  17. to be similar to babashka.tasks/shell"
  18. [cmd opts]
  19. (child-process/spawnSync (first cmd)
  20. (clj->js (rest cmd))
  21. (clj->js (merge {:stdio "inherit"} opts))))
  22. (defn- get-dir-and-db-name
  23. "Gets dir and db name for use with open-db!"
  24. [graph-dir]
  25. (if (string/includes? graph-dir "/")
  26. (let [graph-dir'
  27. (node-path/join (or js/process.env.ORIGINAL_PWD ".") graph-dir)]
  28. ((juxt node-path/dirname node-path/basename) graph-dir'))
  29. [(node-path/join (os/homedir) "logseq" "graphs") graph-dir]))
  30. (def spec
  31. "Options spec"
  32. {:help {:alias :h
  33. :desc "Print help"}
  34. :verbose {:alias :v
  35. :desc "Print more info"}
  36. :raw {:alias :r
  37. :desc "Print results plainly. Useful when piped to bb"}
  38. :entity {:alias :e
  39. :coerce []
  40. :desc "Lookup entities instead of query"}})
  41. (defn -main [args]
  42. (let [{options :opts args' :args} (cli/parse-args args {:spec spec})
  43. [graph-dir & args''] args'
  44. _ (when (or (nil? graph-dir) (:help options))
  45. (println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
  46. (cli/format-opts {:spec spec})))
  47. (js/process.exit 1))
  48. [dir db-name] (get-dir-and-db-name graph-dir)
  49. conn (sqlite-cli/open-db! dir db-name)
  50. results (if (:entity options)
  51. (map #(when-let [ent (d/entity @conn
  52. (if (string? %) (edn/read-string %) %))]
  53. (cond-> (into {:db/id (:db/id ent)} ent)
  54. (seq (:block/properties ent))
  55. (update :block/properties (fn [props] (map (fn [m] (into {} m)) props)))))
  56. (:entity options))
  57. ;; assumes no :in are in queries
  58. (let [query (into (edn/read-string (first args'')) [:in '$ '%])
  59. res (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules))]
  60. ;; Remove nesting for most queries which just have one :find binding
  61. (if (= 1 (count (first res))) (mapv first res) res)))]
  62. (when (:verbose options) (println "DB contains" (count (d/datoms @conn :eavt)) "datoms"))
  63. (if (:raw options)
  64. (prn results)
  65. (if (zero? (.-status (child-process/spawnSync "which" #js ["puget"])))
  66. (sh ["puget"] {:input (pr-str results) :stdio ["pipe" "inherit" "inherit"]})
  67. (pprint/pprint results)))))
  68. (when (= nbb/*file* (nbb/invoked-file))
  69. (-main *command-line-args*))