227 lines
7.9 KiB
Clojure
227 lines
7.9 KiB
Clojure
|
(ns repl-sessions.pg-stuff
|
||
|
(:require [clojure.string :as str]
|
||
|
[next.jdbc :as jdbc]
|
||
|
[honey.sql :as sql]
|
||
|
[next.jdbc.sql :as nsql]
|
||
|
[next.jdbc.date-time :as jdbc-date-time]
|
||
|
[next.jdbc.result-set :as rs]
|
||
|
[next.jdbc.plan]
|
||
|
[cheshire.core :as json]
|
||
|
))
|
||
|
|
||
|
(defn pg-url [db-name]
|
||
|
(str "jdbc:pgsql://localhost:5432/" db-name "?user=postgres"))
|
||
|
|
||
|
(defn recreate-db! [name]
|
||
|
(let [ds (jdbc/get-datasource (pg-url "postgres"))]
|
||
|
(jdbc/execute! ds [(str "DROP DATABASE IF EXISTS " name)])
|
||
|
(jdbc/execute! ds [(str "CREATE DATABASE " name)])))
|
||
|
|
||
|
(recreate-db! "souk")
|
||
|
|
||
|
(defn sql-ident [v]
|
||
|
(if (sequential? v)
|
||
|
(str/join "." (map identifier v))
|
||
|
(str "\""
|
||
|
(if (keyword? v)
|
||
|
(subs (str v) 1)
|
||
|
v)
|
||
|
"\"")))
|
||
|
|
||
|
(defn sql-kw [k]
|
||
|
(str/upper-case
|
||
|
(str/replace
|
||
|
(if (keyword? k)
|
||
|
(name k)
|
||
|
k)
|
||
|
#"-" " ")))
|
||
|
|
||
|
(defn sql-str [& ss]
|
||
|
(str "'" (str/replace (apply str ss) #"'" "''") "'"))
|
||
|
|
||
|
(defn sql-list
|
||
|
([items]
|
||
|
(sql-list "(" ")" ", " items))
|
||
|
([before after separator items]
|
||
|
(str before (apply str (str/join separator items)) after)))
|
||
|
|
||
|
(defn strs [& items]
|
||
|
(str/join " " items))
|
||
|
|
||
|
(defn sql [& items]
|
||
|
(apply strs
|
||
|
(map (fn [x]
|
||
|
(cond
|
||
|
(vector? x)
|
||
|
(case (first x)
|
||
|
:ident (sql-ident (second x))
|
||
|
:kw (sql-kw (second x))
|
||
|
:str (sql-str (second x))
|
||
|
:raw (second x)
|
||
|
:list (sql-list (map sql (next x)))
|
||
|
:commas (sql-list "" "" ", " (map sql (next x)))
|
||
|
:fn (str (second x)
|
||
|
(sql-list (map sql (nnext x))))
|
||
|
(apply sql x))
|
||
|
(keyword? x)
|
||
|
(sql-ident x)
|
||
|
(symbol? x)
|
||
|
(sql-kw x)
|
||
|
(string? x)
|
||
|
(sql-str x)
|
||
|
(sequential? x)
|
||
|
(sql-list "(" ")" ", " (map sql x))))
|
||
|
items)))
|
||
|
|
||
|
(sql 'create-table :activitystreams/Person
|
||
|
(list
|
||
|
[:souk/id 'text 'primary-key]
|
||
|
[:souk/properties 'jsonb 'default "{}"] ))
|
||
|
|
||
|
(def set-ts-trigger-def "CREATE OR REPLACE FUNCTION trigger_set_timestamp()\nRETURNS TRIGGER AS $$\nBEGIN\n NEW.\"meta/updated-at\" = NOW();\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;")
|
||
|
(def default-properties
|
||
|
[[:rdf/id 'text 'primary-key]
|
||
|
[:rdf/props 'jsonb 'default "{}"]
|
||
|
[:meta/created-at 'timestamp-with-time-zone 'default [:fn 'now] 'not-null]
|
||
|
[:meta/updated-at 'timestamp-with-time-zone]])
|
||
|
|
||
|
(let [ds (jdbc/get-datasource (pg-url "souk"))]
|
||
|
(jdbc/execute! ds [set-ts-trigger-def])
|
||
|
(jdbc/execute! ds [(sql 'drop-table
|
||
|
'if-exists
|
||
|
:activitystreams/Person)])
|
||
|
(jdbc/execute! ds [(sql 'create-table :activitystreams/Person
|
||
|
(concat
|
||
|
default-properties
|
||
|
[[:activitystreams/name 'text 'not-null]
|
||
|
[:activitystreams/preferredUsername 'text 'not-null]
|
||
|
[:activitystreams/url 'text 'not-null]
|
||
|
[:activitystreams/summary 'text]
|
||
|
[:ldp/inbox 'text 'not-null]
|
||
|
[:activitystreams/outbox 'text 'not-null]
|
||
|
[:activitystreams/published 'timestamp-with-time-zone]]))])
|
||
|
(jdbc/execute! ds [(sql 'create-trigger :set-timestamp
|
||
|
'before-update
|
||
|
'on :activitystreams/Person
|
||
|
'for-each-row
|
||
|
'execute-procedure [:fn 'trigger_set_timestamp])]))
|
||
|
(def table-columns
|
||
|
(into {}
|
||
|
(let [ds (jdbc/get-datasource (pg-url "souk"))
|
||
|
opts {}]
|
||
|
(with-open [con (jdbc/get-connection ds opts)]
|
||
|
(let [md (.getMetaData con)] ; produces java.sql.DatabaseMetaData
|
||
|
(doall
|
||
|
(for [{:keys [pg_class/TABLE_NAME]} (-> md
|
||
|
;; return a java.sql.ResultSet describing all tables and views:
|
||
|
(.getTables nil nil nil (into-array ["TABLE" "VIEW"]))
|
||
|
(rs/datafiable-result-set ds opts))]
|
||
|
[(keyword TABLE_NAME)
|
||
|
(map (comp keyword :COLUMN_NAME) (rs/datafiable-result-set (.getColumns md nil nil TABLE_NAME nil) ds opts)
|
||
|
)
|
||
|
])))))))
|
||
|
(to-clj (expand (:body (json-fetch "https://toot.cat/users/plexus")))
|
||
|
clojure-prefixes)
|
||
|
|
||
|
(defn pg-coerce [val]
|
||
|
(cond
|
||
|
(instance? java.time.ZonedDateTime val)
|
||
|
(.toOffsetDateTime val)
|
||
|
#_[:raw (strs (sql-kw 'timestamp-with-time-zone)
|
||
|
(sql-str
|
||
|
|
||
|
(.toLocalDate ^java.time.ZonedDateTime val) " "
|
||
|
(let [t (.toLocalTime ^java.time.ZonedDateTime val)]
|
||
|
(format "%d:%02d:%02d" (.getHour t)
|
||
|
(.getMinute t) (.getSecond t)))
|
||
|
(.getZone ^java.time.ZonedDateTime val)))]
|
||
|
:else
|
||
|
val))
|
||
|
|
||
|
(defn insert-sql [entity]
|
||
|
(let [{:rdf/keys [type]} entity
|
||
|
cols (get table-columns type)
|
||
|
props (seq (select-keys entity cols))]
|
||
|
(into [(sql 'insert-into type
|
||
|
(cons :rdf/props
|
||
|
(map key props))
|
||
|
'values
|
||
|
(repeat (inc (count props)) '?)
|
||
|
'on-conflict [:raw "(\"rdf/id\")"]
|
||
|
'do
|
||
|
'update-set
|
||
|
(into [:commas]
|
||
|
(map (fn [[k]]
|
||
|
[k '= '?]))
|
||
|
props))]
|
||
|
(cons
|
||
|
(json/encode (apply dissoc entity :rdf/type (map key props)))
|
||
|
(concat
|
||
|
(map (comp pg-coerce val) props)
|
||
|
(map (comp pg-coerce val) props))))))
|
||
|
|
||
|
(let [ds (jdbc/get-datasource (pg-url "souk"))]
|
||
|
(jdbc/execute! ds (insert-sql (assoc repl-sessions.json-ld-stuff/plexus-profile
|
||
|
:activitystreams/name "John Doe")))
|
||
|
)
|
||
|
|
||
|
(defrecord MyMapResultSetBuilder [^java.sql.ResultSet rs rsmeta cols]
|
||
|
rs/RowBuilder
|
||
|
(->row [this] (transient {}))
|
||
|
(column-count [this] (count cols))
|
||
|
(with-column [this row i]
|
||
|
(rs/with-column-value this row (nth cols (dec i))
|
||
|
(if (= java.sql.Types/TIMESTAMP_WITH_TIMEZONE (.getColumnType rsmeta i))
|
||
|
(.getObject rs ^Integer i ^Class java.time.OffsetDateTime)
|
||
|
(rs/read-column-by-index (.getObject rs ^Integer i) rsmeta i))))
|
||
|
(with-column-value [this row col v]
|
||
|
(assoc! row col v))
|
||
|
(row! [this row] (persistent! row))
|
||
|
rs/ResultSetBuilder
|
||
|
(->rs [this] (transient []))
|
||
|
(with-row [this mrs row]
|
||
|
(conj! mrs row))
|
||
|
(rs! [this mrs] (persistent! mrs)))
|
||
|
|
||
|
(defn my-builder
|
||
|
[rs opts]
|
||
|
(let [rsmeta (.getMetaData rs)
|
||
|
cols (rs/get-unqualified-column-names rsmeta opts)]
|
||
|
(def rs rs)
|
||
|
(def meta rsmeta)
|
||
|
(def cols cols)
|
||
|
(->MyMapResultSetBuilder rs rsmeta cols)))
|
||
|
(.getColumnType meta 3)
|
||
|
|
||
|
(let [ds (jdbc/get-datasource (pg-url "souk"))]
|
||
|
(jdbc/execute-one! ds [(sql 'select '* 'from :activitystreams/Person)]
|
||
|
|
||
|
{:builder-fn my-builder})
|
||
|
)
|
||
|
|
||
|
|
||
|
(pg-coerce (java.time.ZonedDateTime/parse "2017-04-11T00:00Z"))
|
||
|
|
||
|
{:activitystreams/followers "@id",
|
||
|
:activitystreams/published "xsd:dateTime",
|
||
|
:mastodon/devices "@id",
|
||
|
:json-ld/type nil,
|
||
|
:activitystreams/outbox "@id",
|
||
|
:activitystreams/following "@id",
|
||
|
:activitystreams/endpoints "@id",
|
||
|
:activitystreams/name nil,
|
||
|
:activitystreams/icon "@id",
|
||
|
:security/publicKey "@id",
|
||
|
:mastodon/featured "@id",
|
||
|
:activitystreams/manuallyApprovesFollowers nil,
|
||
|
:activitystreams/summary nil,
|
||
|
:activitystreams/image "@id",
|
||
|
:activitystreams/tag "@id",
|
||
|
:mastodon/discoverable nil,
|
||
|
:json-ld/id nil,
|
||
|
:activitystreams/preferredUsername nil,
|
||
|
:activitystreams/url "@id",
|
||
|
:ldp/inbox "@id",
|
||
|
:activitystreams/attachment "@id",
|
||
|
:mastodon/featuredTags "@id"}
|