/
datafy.clj
167 lines (155 loc) · 7.86 KB
/
datafy.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
;; copyright (c) 2020-2021 Sean Corfield, all rights reserved
(ns next.jdbc.datafy
"This namespace provides datafication of several JDBC object types,
all within the `java.sql` package:
* `Connection` -- datafies as a bean.
* `DatabaseMetaData` -- datafies as a bean; six properties
are navigable to produce fully-realized datafiable result sets.
* `ParameterMetaData` -- datafies as a vector of parameter descriptions.
* `ResultSet` -- datafies as a bean; if the `ResultSet` has an associated
`Statement` and that in turn has an associated `Connection` then an
additional key of `:rows` is provided which is a datafied result set,
from `next.jdbc.result-set/datafiable-result-set` with default options.
This is provided as a convenience, purely for datafication of other
JDBC data types -- in normal `next.jdbc` usage, result sets are
datafied under full user control.
* `ResultSetMetaData` -- datafies as a vector of column descriptions.
* `Statement` -- datafies as a bean.
Because different database drivers may throw `SQLException` for various
unimplemented or unavailable properties on objects in various states,
the default behavior is to return those exceptions using the `:qualify`
option for `clojure.java.data/from-java-shallow`, so for a property
`:foo`, if its corresponding getter throws an exception, it would instead
be returned as `:foo/exception`. This behavior can be overridden by
`binding` `next.jdbc.datafy/*datafy-failure*` to any of the other options
supported: `:group`, `:omit`, or `:return`. See the `clojure.java.data`
documentation for more details."
(:require [clojure.core.protocols :as core-p]
[clojure.java.data :as j]
[next.jdbc.result-set :as rs])
(:import (java.sql Connection
DatabaseMetaData
ParameterMetaData
ResultSet ResultSetMetaData
Statement)))
(set! *warn-on-reflection* true)
(def ^:private column-meta
{:catalog (fn [^ResultSetMetaData o i] (.getCatalogName o i))
:class (fn [^ResultSetMetaData o i] (.getColumnClassName o i))
:display-size (fn [^ResultSetMetaData o i] (.getColumnDisplaySize o i))
:label (fn [^ResultSetMetaData o i] (.getColumnLabel o i))
:name (fn [^ResultSetMetaData o i] (.getColumnName o i))
:precision (fn [^ResultSetMetaData o i] (.getPrecision o i))
:scale (fn [^ResultSetMetaData o i] (.getScale o i))
:schema (fn [^ResultSetMetaData o i] (.getSchemaName o i))
:table (fn [^ResultSetMetaData o i] (.getTableName o i))
:type (fn [^ResultSetMetaData o i] (.getColumnTypeName o i))
;; the is* fields:
:nullability (fn [^ResultSetMetaData o i]
(condp = (.isNullable o i)
ResultSetMetaData/columnNoNulls :not-null
ResultSetMetaData/columnNullable :null
:unknown))
:auto-increment (fn [^ResultSetMetaData o i] (.isAutoIncrement o i))
:case-sensitive (fn [^ResultSetMetaData o i] (.isCaseSensitive o i))
:currency (fn [^ResultSetMetaData o i] (.isCurrency o i))
:definitely-writable (fn [^ResultSetMetaData o i] (.isDefinitelyWritable o i))
:read-only (fn [^ResultSetMetaData o i] (.isReadOnly o i))
:searchable (fn [^ResultSetMetaData o i] (.isSearchable o i))
:signed (fn [^ResultSetMetaData o i] (.isSigned o i))
:writable (fn [^ResultSetMetaData o i] (.isWritable o i))})
(def ^:private parameter-meta
{:class (fn [^ParameterMetaData o i] (.getParameterClassName o i))
:mode (fn [^ParameterMetaData o i]
(condp = (.getParameterMode o i)
ParameterMetaData/parameterModeIn :in
ParameterMetaData/parameterModeInOut :in-out
ParameterMetaData/parameterModeOut :out
:unknown))
:precision (fn [^ParameterMetaData o i] (.getPrecision o i))
:scale (fn [^ParameterMetaData o i] (.getScale o i))
:type (fn [^ParameterMetaData o i] (.getParameterTypeName o i))
;; the is* fields:
:nullability (fn [^ParameterMetaData o i]
(condp = (.isNullable o i)
ParameterMetaData/parameterNoNulls :not-null
ParameterMetaData/parameterNullable :null
:unknown))
:signed (fn [^ParameterMetaData o i] (.isSigned o i))})
(def ^:dynamic *datafy-failure*
"How datafication failures should be handled, based on `clojure.java.data`.
Defaults to `:qualify`, but can be `:group`, `:omit`, `:qualify`, or `:return`."
:qualify)
(defn- safe-bean [o opts]
(try
(j/from-java-shallow o (assoc opts :add-class true :exceptions *datafy-failure*))
(catch Throwable t
(let [dex (juxt type (comp str ex-message))
cause (ex-cause t)]
(with-meta (cond-> {:exception (dex t)}
cause (assoc :cause (dex cause)))
{:exception t})))))
(defn- datafy-result-set-meta-data
[^ResultSetMetaData this]
(mapv #(reduce-kv (fn [m k f] (assoc m k (f this %)))
{}
column-meta)
(range 1 (inc (.getColumnCount this)))))
(defn- datafy-parameter-meta-data
[^ParameterMetaData this]
(mapv #(reduce-kv (fn [m k f] (assoc m k (f this %)))
{}
parameter-meta)
(range 1 (inc (.getParameterCount this)))))
(extend-protocol core-p/Datafiable
Connection
(datafy [this] (safe-bean this {}))
DatabaseMetaData
(datafy [this]
(with-meta (let [data (safe-bean this {})]
(cond-> data
(not (:exception (meta data)))
;; add an opaque object that nav will "replace"
(assoc :all-tables (Object.))))
{`core-p/nav (fn [_ k v]
(condp = k
:all-tables
(rs/datafiable-result-set (.getTables this nil nil nil nil)
(.getConnection this)
{})
:catalogs
(rs/datafiable-result-set (.getCatalogs this)
(.getConnection this)
{})
:clientInfoProperties
(rs/datafiable-result-set (.getClientInfoProperties this)
(.getConnection this)
{})
:schemas
(rs/datafiable-result-set (.getSchemas this)
(.getConnection this)
{})
:tableTypes
(rs/datafiable-result-set (.getTableTypes this)
(.getConnection this)
{})
:typeInfo
(rs/datafiable-result-set (.getTypeInfo this)
(.getConnection this)
{})
v))}))
ParameterMetaData
(datafy [this] (datafy-parameter-meta-data this))
ResultSet
(datafy [this]
;; SQLite has a combination ResultSet/Metadata object...
(if (instance? ResultSetMetaData this)
(datafy-result-set-meta-data this)
(let [s (.getStatement this)
c (when s (.getConnection s))]
(cond-> (safe-bean this {})
c (assoc :rows (rs/datafiable-result-set this c {}))))))
ResultSetMetaData
(datafy [this] (datafy-result-set-meta-data this))
Statement
(datafy [this] (safe-bean this {:omit #{:moreResults}})))