(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}))