All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xtdb.c1_import.clj Maven / Gradle / Ivy

The newest version!
(ns xtdb.c1-import
  (:require [clojure.set :as set]
            [clojure.spec.alpha :as s]
            [clojure.tools.logging :as log]
            [cognitect.transit :as transit]
            [integrant.core :as ig]
            [xtdb.api :as xt]
            [xtdb.cli :as cli]
            [xtdb.node :as xtn]
            [xtdb.util :as util])
  (:import clojure.lang.MapEntry
           java.lang.AutoCloseable
           (java.nio.file ClosedWatchServiceException Files OpenOption Path StandardOpenOption StandardWatchEventKinds WatchEvent WatchEvent$Kind)
           java.util.HashSet
           xtdb.types.ClojureForm))

(defmethod ig/prep-key :xtdb/c1-import [_ opts]
  (into {:submit-client (ig/ref ::xtn/submit-client)}
        (-> opts
            (update :export-log-path
                    (fn [path]
                      (let [conformed-path (s/conform ::util/path path)]
                        (assert (not (s/invalid? conformed-path))
                                (format "invalid export log path: '%s'" conformed-path))
                        conformed-path))))))

(def ^java.util.concurrent.ThreadFactory thread-factory
  (util/->prefix-thread-factory "c1-import"))

(defn- xform-doc [doc]
  (-> doc
      (set/rename-keys {:crux.db/id :xt/id})
      (->> (into {}
                 (map (fn [[k v]]
                        (MapEntry/create k
                                         (cond-> v
                                           (and (list? v) (= (first v) 'fn))
                                           (ClojureForm.)))))))))

(defn- submit-file! [submit-client, ^Path log-file]
  (with-open [is (Files/newInputStream log-file (into-array OpenOption #{StandardOpenOption/READ}))]
    (let [rdr (transit/reader is :json)]
      (loop []
        (when-let [[tx-status tx tx-ops] (try
                                           (transit/read rdr)
                                           (catch Throwable _))]
          (when (Thread/interrupted)
            (throw (InterruptedException.)))

          (xt/submit-tx submit-client
                        (case tx-status
                          :commit (for [[tx-op {:keys [eid doc start-valid-time end-valid-time]}] tx-ops]
                                    ;; HACK: what to do if the user has a separate :xt/id  key?
                                    (case tx-op
                                      :put [:put-docs {:into :xt_docs, :valid-from start-valid-time, :valid-to end-valid-time}
                                            (xform-doc doc)]
                                      :delete [:delete-docs {:from :xt_docs, :valid-from start-valid-time, :valid-to end-valid-time} eid]
                                      :evict [:erase-docs :xt_docs eid]))
                          :abort [[:abort]])
                        {:system-time (:xtdb.api/tx-time tx)})
          (recur))))))

(defmethod ig/init-key :xtdb/c1-import [_ {:keys [^Path export-log-path submit-client]}]
  (let [watch-svc (.newWatchService (.getFileSystem export-log-path))
        watch-key (.register export-log-path watch-svc (into-array WatchEvent$Kind [StandardWatchEventKinds/ENTRY_CREATE]))

        t (doto (.newThread thread-factory
                            (fn []
                              (let [seen-files (HashSet.)]
                                (letfn [(process-file! [^Path file]
                                          (when-not (.contains seen-files file)
                                            (.add seen-files file)
                                            (log/infof "processing '%s'..." (str file))
                                            (submit-file! submit-client file)
                                            (log/infof "processed '%s'." (str file))))]
                                  (try
                                    (run! process-file!
                                          (->> (.toList (Files/list export-log-path))
                                               (sort-by #(let [[_ idx] (re-matches #"log\.(\d+)\.transit\.json" (str (.getFileName ^Path %)))]
                                                           (Long/parseLong idx)))))
                                    (while true
                                      (doseq [^WatchEvent watch-ev (.pollEvents (.take watch-svc))
                                              :let [^Path created-file (.context watch-ev)]]
                                        (process-file! (.resolve export-log-path created-file)))
                                      (.reset watch-key))
                                    (catch InterruptedException _)
                                    (catch ClosedWatchServiceException _)
                                    (catch Throwable t
                                      (log/error t "fail")))))))
            (.start))]
    (reify AutoCloseable
      (close [_]
        (.close watch-svc)
        (.interrupt t)
        (.join t)))))

(defmethod ig/halt-key! :xtdb/c1-import [_ c1-importer]
  (util/try-close c1-importer))

;; duplicated from xtdb.cli
(defn- shutdown-hook-promise []
  (let [main-thread (Thread/currentThread)
        shutdown? (promise)]
    (.addShutdownHook (Runtime/getRuntime)
                      (Thread. (fn []
                                 (let [shutdown-ms 10000]
                                   (deliver shutdown? true)
                                   (shutdown-agents)
                                   (.join main-thread shutdown-ms)
                                   (if (.isAlive main-thread)
                                     (do
                                       (log/warn "could not stop node cleanly after" shutdown-ms "ms, forcing exit")
                                       (.halt (Runtime/getRuntime) 1))

                                     (log/info "Node stopped."))))
                               "xtdb.shutdown-hook-thread"))
    shutdown?))

(defn -main [& args]
  (util/install-uncaught-exception-handler!)

  (let [{::cli/keys [errors help], sys-opts ::cli/node-opts} (cli/parse-args args)]
    (cond
      errors (binding [*out* *err*]
               (doseq [error errors]
                 (println error))
               (System/exit 1))

      help (println help)

      :else (with-open [_submit-client (xtn/start-node sys-opts)]
              (log/info "Importer started")
              @(shutdown-hook-promise)))

    (shutdown-agents)))




© 2015 - 2025 Weber Informatics LLC | Privacy Policy