Skip to content

Instantly share code, notes, and snippets.

@olivergeorge
Last active April 8, 2018 20:14
Show Gist options
  • Save olivergeorge/27e9fced404f5ddef371ea3376456f2f to your computer and use it in GitHub Desktop.
Save olivergeorge/27e9fced404f5ddef371ea3376456f2f to your computer and use it in GitHub Desktop.
Experiment to generate sql create table statements from clojure.spec definitions
(ns condense.spec->sql
(:require [clojure.spec :as s]))
(defn varchar [max]
#(and (string? %) (<= (count %) max)))
(defn lift-type
"Simple transformation of data. #{[t v1] [t v2]...} => [t #{v1 v2}]"
[typed-vals]
(let [types (map first typed-vals)
vals (map second typed-vals)]
[(first types) (into (empty typed-vals) vals)]))
(defn promote-to-varchar
"For string set we can find max length and make it a varchar"
[[field-type field-vals :as typed-set]]
(if (= :string field-type)
[:varchar {:max (apply max (map count field-vals))}]
typed-set))
(s/def ::typed-set
(s/and (s/coll-of ::typed-val :kind set?)
#(every? #{(ffirst %)} (map first %))
(s/conformer lift-type)
(s/conformer promote-to-varchar)))
(s/def ::typed-val
(s/or :string string?
:integer integer?
:timestamp inst?))
(s/def ::typed-pred
(s/or :string #{`string?}
:integer #{`integer?}
:timestamp #{`inst?}
:varchar (s/cat :pred #{`varchar}
:max integer?)))
(s/def ::typed-thing
(s/or :pred ::typed-pred
:val ::typed-val
:set ::typed-set))
(defn spec->type [k]
(let [f (s/form k)
[_ [field-type field-val]] (s/conform ::typed-thing f)]
(case field-type
:string "varchar2(4000)"
:integer "int"
:timestamp "datetime"
:varchar (str "varchar2(" (:max field-val) ")"))))
(defn spec->sql-table [map-spec]
(let [[_ & {:keys [req-un opt-un]}] (s/form map-spec)]
(concat
(for [k req-un]
[(name k) (spec->type k) "not null"])
(for [k opt-un]
[(name k) (spec->type k)]))))
(do
(s/def ::name string?)
(s/def ::address (varchar 256))
(s/def ::age integer?)
(s/def ::gender #{"M" "F"})
(s/def ::example-table (s/keys :req-un [::name ::age ::gender]
:opt-un [::address]))
(= (spec->sql-table ::example-table)
[["name" "varchar2(4000)" "not null"]
["age" "int" "not null"]
["gender" "varchar2(1)" "not null"]
["address" "varchar2(256)"]]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment