Experiment to generate sql create table statements from clojure.spec definitions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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