/
types.clj
197 lines (150 loc) · 7.18 KB
/
types.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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
(ns
^{:doc "Opinionated database type conversions."
:author "Paweł Wilk"
:added "1.0.0"}
io.randomseed.utils.db.types
(:require [potemkin.namespaces :as p]
[next.jdbc.result-set :as rs]
[next.jdbc.prepare :as jp]
[clj-uuid :as uuid]
[phone-number.core :as phone])
(:import [java.sql Blob Connection PreparedStatement Timestamp]
[javax.sql DataSource]
[java.lang.reflect Method]
[java.util UUID Calendar GregorianCalendar TimeZone]
[java.io Closeable]
[inet.ipaddr.ipv4 IPv4Address]
[inet.ipaddr.ipv6 IPv6Address]
[com.google.i18n.phonenumbers Phonenumber$PhoneNumber]))
(set! *warn-on-reflection* true)
(defonce ^TimeZone utc-time-zone
(TimeZone/getTimeZone "UTC"))
;; Type conversions when reading from a DB
(defonce
^{:arglists '([])
:doc "Extends `next.jdbc.result-set/ReadableColumn` protocol to support date/time
conversions so `java.sql.Date` data are passed as-is and `java.sql.Timestamp`
data are converted to `java.sql.Timestamp`."}
add-reader-date
(fn []
(extend-protocol rs/ReadableColumn
java.sql.Date
(read-column-by-label [^java.sql.Date v _] ^java.sql.Date v)
(read-column-by-index [^java.sql.Date v _2 _3] ^java.sql.Date v)
java.sql.Timestamp
(read-column-by-label [^Timestamp v _] (.toInstant ^Timestamp v))
(read-column-by-index [^Timestamp v _2 _3] (.toInstant ^Timestamp v)))))
(defonce
^{:arglists '([])
:doc "Extends `next.jdbc.result-set/ReadableColumn` protocol to support binary large
object (BLOB) conversions, so `java.sql.Blob` data are converted to an array of
bytes (`bytes[]`)."}
add-reader-blob
(fn []
(extend-protocol rs/ReadableColumn
java.sql.Blob
(read-column-by-label [^Blob v _] (.getBytes ^Blob v (long 1) ^long (.length ^Blob v)))
(read-column-by-index [^Blob v _2 _3] (.getBytes ^Blob v (long 1) ^long (.length ^Blob v))))))
(defn add-all-readers
"Adds all opinionated readers by calling `add-reader-date` and `add-reader-blob`."
[]
(add-reader-date)
(add-reader-blob))
;; Type conversions when writing to a DB
(defonce
^{:arglists '([])
:doc "Extends `next.jdbc.prepare/SettableParameter` protocol to support date/time
conversions so `java.sql.Date` data are saved using `.setDate` method and
`java.sql.Timestamp`, `java.time.Instant`, `java.time.ZonedTime`,
`java.time.LocalDate`, `java.time.LocalDateTime` and `java.util.Date` are saved with
`.setTimestamp`. Also note that `java.time.Instant`, `java.time.ZonedDateTime` and
`java.util.Date` are explicitly converted to UTC before saving."}
add-setter-date
(fn []
(extend-protocol jp/SettableParameter
java.sql.Date
(set-parameter [^java.sql.Date v ^PreparedStatement ps ^long i]
(.setDate ^PreparedStatement ps i ^java.sql.Date v))
java.sql.Timestamp
(set-parameter [^Timestamp v ^PreparedStatement ps ^long i]
(.setTimestamp ^PreparedStatement ps i ^java.sql.Timestamp v))
java.time.Instant
(set-parameter [^java.time.Instant v ^PreparedStatement ps ^long i]
(.setTimestamp ^PreparedStatement ps i
^Timestamp (Timestamp/from ^java.time.Instant v)
^Calendar (Calendar/getInstance ^TimeZone utc-time-zone)))
java.time.ZonedDateTime
(set-parameter [^java.time.LocalDate v ^PreparedStatement ps ^long i]
(.setTimestamp ^PreparedStatement ps i
^Timestamp (Timestamp/from ^java.time.Instant (.toInstant ^java.time.ZonedDateTime v))
^Calendar (Calendar/getInstance ^TimeZone utc-time-zone)))
java.time.LocalDate
(set-parameter [^java.time.LocalDate v ^PreparedStatement ps ^long i]
(.setTimestamp ^PreparedStatement ps i
^Timestamp (Timestamp/valueOf ^java.time.LocalDateTime (.atStartOfDay ^java.time.LocalDate v))
^Calendar (Calendar/getInstance)))
java.time.LocalDateTime
(set-parameter [^java.time.LocalDateTime v ^PreparedStatement ps ^long i]
(.setTimestamp ^PreparedStatement ps i
^Timestamp (Timestamp/valueOf ^java.time.LocalDateTime v)
^Calendar (Calendar/getInstance)))
java.util.Date
(set-parameter [^java.util.Date v ^PreparedStatement ps ^long i]
(.setTimestamp ^PreparedStatement ps i
^Timestamp (Timestamp/from ^java.time.Instant (.toInstant ^java.util.Date v))
^Calendar (Calendar/getInstance ^TimeZone utc-time-zone))))))
(defonce
^{:arglists '([])
:doc "Extends `next.jdbc.prepare/SettableParameter` protocol to support IP address
conversions so `inet.ipaddr.ipv4.IPv4Address` data are converted to `inet.ipaddr.ipv6.IPv6Address`,
then to an array of bytes and then saved using `.setBytes` method. Similarly,
`inet.ipaddr.ipv6.IPv6Address` data are converted to an array of bytes and then saved with `.setBytes`."}
add-setter-ip-address
(fn []
(extend-protocol jp/SettableParameter
IPv4Address
(set-parameter [^IPv4Address v ^PreparedStatement ps ^long i]
(.setBytes ^PreparedStatement ps i (.getBytes ^IPv6Address (.toIPv6 ^IPv4Address v))))
IPv6Address
(set-parameter [^IPv6Address v ^PreparedStatement ps ^long i]
(.setBytes ^PreparedStatement ps i (.getBytes ^IPv6Address v))))))
(defonce
^{:arglists '([])
:doc "Extends `next.jdbc.prepare/SettableParameter` protocol to support UUID
conversions so `java.util.UUID` data are converted to an array of bytes and then
saved using `.setBytes` method."}
add-setter-uuid
(fn []
(extend-protocol jp/SettableParameter
java.util.UUID
(set-parameter [^UUID v ^PreparedStatement ps ^long i]
(.setBytes ^PreparedStatement ps i ^bytes (uuid/to-byte-array ^UUID v))))))
(defonce
^{:arglists '([])
:doc "Extends `next.jdbc.prepare/SettableParameter` protocol to support phone number
conversions so `com.google.i18n.phonenumbers/Phonenumber$PhoneNumber` data are converted to
strings (in E.164 format) and then saved."}
add-setter-phone-number
(fn []
(extend-protocol jp/SettableParameter
Phonenumber$PhoneNumber
(set-parameter [^Phonenumber$PhoneNumber v ^PreparedStatement ps ^long i]
(.setString ^PreparedStatement ps i ^String (phone/format v nil :phone-number.format/e164))))))
(defn add-all-setters
"Adds all opinionated setters by calling `add-setter-date` and `add-setter-ip-address`."
[]
(add-setter-date)
(add-setter-ip-address)
(add-setter-phone-number)
(add-setter-uuid))
(defn add-all-accessors
"Adds all opinionated readers and setters by calling `add-all-readers` and `add-all-setters`."
[]
(add-all-readers)
(add-all-setters))
;; Exposed aliases for builder and conversion functions
(p/import-vars [io.randomseed.utils.db
to-lisp-simple to-snake-simple to-lisp to-snake
to-lisp-slashed to-snake-slashed
opts-simple-map opts-map opts-simple-vec opts-vec
opts-slashed-map opts-slashed-vec])