pgwiz/src/ch/lyrion/pgwiz/inspect.clj

130 lines
4.0 KiB
Clojure

(ns ch.lyrion.pgwiz.inspect
(:require [next.jdbc :as jdbc]
[next.jdbc.result-set :as resultset]
[malli.core :as m]
[malli.transform :as mt]
[tick.alpha.api :as t]))
(def db {:dbtype "postgresql"
:dbname "sompani-dev"
:host "127.0.0.1"
:port 5432
:user "sompani_dev"
:password "sompani-dev"})
(def conn (atom (jdbc/get-connection db)))
;; Basics
(defn get-named-rows [pg-result-set]
(.next pg-result-set)
(loop [pg-meta (.getMetaData pg-result-set)
result (list)]
(let [result (cons (reduce #(assoc %1 (keyword (.getColumnName pg-meta %2))
(.getString pg-result-set %2))
{} (range 1 (inc (.getColumnCount pg-meta))))
result)]
(if (.next pg-result-set)
(recur (.getMetaData pg-result-set) result)
result))))
(defn get-exported-keys [schema table]
(get-named-rows (.getExportedKeys (.getMetaData @conn) nil schema table)))
(defn get-imported-keys [schema table]
(get-named-rows (.getImportedKeys (.getMetaData @conn) nil schema table)))
(defn get-primary-keys [schema table]
(get-named-rows (.getPrimaryKeys (.getMetaData @conn) nil schema table)))
(defn get-version-columns [schema table]
(get-named-rows (.getVersionColumns (.getMetaData @conn) nil schema table)))
(defn get-index-info [schema table unique approximate]
(get-named-rows (.getIndexInfo (.getMetaData @conn) nil schema table unique approximate)))
(defn get-table-privileges [schema table]
(get-named-rows (.getTablePrivileges (.getMetaData @conn) nil schema table)))
(defn get-columns [schema table]
(get-named-rows (.getColumns (.getMetaData @conn) nil schema table nil)))
;; Type checker
(defn interval? [x]
(instance? org.postgresql.util.PGInterval x))
(defn citext? [x]
(instance? org.postgresql.util.PGobject x))
(def registry
(merge malli.core/default-registry
{'interval? (m/fn-schema :interval interval?)
'citext? (m/fn-schema :citext citext?)}))
;; Type converter
#_(defn PGInterval->JInterval [^org.postgresql.util.PGInterval interval]
(t/+ (t/new-duration (.getSeconds interval) :seconds)
(t/new-duration (.getMinutes interval) :minutes)
(t/new-duration (.getHours interval) :hours)
(t/new-duration (.getDays interval) :days)
(t/new-duration (.getMonths interval) :months)
(t/new-duration (.getYears interval) :years)))
(defn citext->string [citext]
(str citext))
(def psql-decoders
{'citext? citext->string})
(def psql-encoders
{'citext? identity
'uuid? identity
'inst? identity})
(defn psql-transformer []
(mt/transformer
{:name :psql
:decoders (merge mt/+string-decoders+ psql-decoders)
:encoders (merge mt/+string-encoders+ psql-encoders)}))
;; Map column generation
(def typemapping
{"bool" :boolean
"text" :string
"citext" :citext
"serial" :long
"uuid" :uuid
"interval" :interval
"timestamp" :inst})
(def checker-mapping
{:string 'string?
:citext 'citext?
:boolean 'boolean?
:long 'number?
:inst 'inst?
:uuid 'uuid?
:interval 'interval?})
(defn gen-checker-mapping [type nullable?]
(if nullable?
[:or (get checker-mapping type) 'nil?]
(get checker-mapping type)))
(defn gen-mapcol [{:keys [COLUMN_NAME TYPE_NAME IS_AUTOINCREMENT IS_NULLABLE]}]
(let [datatype (get typemapping TYPE_NAME)
nullable? (or (and (= IS_NULLABLE "YES") true) false)]
[(keyword COLUMN_NAME) {:nullable nullable?
:autoincrement (or (and (= IS_AUTOINCREMENT "YES") true) false)
:type datatype}
(gen-checker-mapping datatype nullable?)]))
(defn gen-tableschema [schema table]
(reduce #(conj %1 (gen-mapcol %2))
[:map {:schema schema :table table}]
(get-columns schema table)))
;; (m/explain (gen-tableschema "company" "vc") (jdbc/execute-one! @conn ["SELECT * FROM company.vc LIMIT 1"] {:builder-fn resultset/as-unqualified-lower-maps}))