query.cljs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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.common.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! Works for relative and absolute paths and
  24. defaults to ~/logseq/graphs/ when no '/' present in name"
  25. [graph-dir]
  26. (if (string/includes? graph-dir "/")
  27. (let [resolve-path' #(if (node-path/isAbsolute %) %
  28. ;; $ORIGINAL_PWD used by bb tasks to correct current dir
  29. (node-path/join (or js/process.env.ORIGINAL_PWD ".") %))]
  30. ((juxt node-path/dirname node-path/basename) (resolve-path' graph-dir)))
  31. [(node-path/join (os/homedir) "logseq" "graphs") graph-dir]))
  32. (def spec
  33. "Options spec"
  34. {:help {:alias :h
  35. :desc "Print help"}
  36. :verbose {:alias :v
  37. :desc "Print more info"}
  38. :raw {:alias :r
  39. :desc "Print results plainly. Useful when piped to bb"}
  40. :additional-graphs {:alias :a
  41. :coerce []
  42. :desc "Additional graphs to query"}
  43. :entity {:alias :e
  44. :coerce []
  45. :desc "Lookup entities instead of query"}})
  46. (defn query-graph [graph-dir args'' options]
  47. (let [[dir db-name] (get-dir-and-db-name graph-dir)
  48. conn (sqlite-cli/open-db! dir db-name)
  49. results (if (:entity options)
  50. (map #(when-let [ent (d/entity @conn
  51. (if (string? %) (edn/read-string %) %))]
  52. (cond-> (into {:db/id (:db/id ent)} ent)
  53. (seq (:block/properties ent))
  54. (update :block/properties (fn [props] (map (fn [m] (into {} m)) props)))))
  55. (:entity options))
  56. ;; assumes no :in are in queries
  57. (let [query (into (edn/read-string (first args'')) [:in '$ '%])
  58. res (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules))]
  59. ;; Remove nesting for most queries which just have one :find binding
  60. (if (= 1 (count (first res))) (mapv first res) res)))]
  61. (when (:verbose options) (println "DB contains" (count (d/datoms @conn :eavt)) "datoms"))
  62. (if (:raw options)
  63. (prn results)
  64. (if (zero? (.-status (child-process/spawnSync "which" #js ["puget"])))
  65. (sh ["puget"] {:input (pr-str results) :stdio ["pipe" "inherit" "inherit"]})
  66. (pprint/pprint results)))))
  67. (defn -main [args]
  68. (let [{options :opts args' :args} (cli/parse-args args {:spec spec})
  69. [graph-dir & args''] args'
  70. _ (when (or (nil? graph-dir) (:help options))
  71. (println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
  72. (cli/format-opts {:spec spec})))
  73. (js/process.exit 1))
  74. graph-dirs (cond-> [graph-dir]
  75. (:additional-graphs options)
  76. (into (:additional-graphs options)))]
  77. (doseq [graph-dir graph-dirs]
  78. (query-graph graph-dir args'' options))))
  79. (when (= nbb/*file* (nbb/invoked-file))
  80. (-main *command-line-args*))