|
5 | 5 | [datascript.core :as d] |
6 | 6 | [datascript.storage :as storage]) |
7 | 7 | (:import |
| 8 | + [java.lang AutoCloseable] |
| 9 | + [java.lang.reflect InvocationHandler Method Proxy] |
8 | 10 | [java.sql Connection DriverManager ResultSet SQLException Statement] |
9 | 11 | [javax.sql DataSource] |
10 | | - [java.lang.reflect InvocationHandler Method Proxy])) |
| 12 | + [java.util.concurrent.locks Condition Lock ReentrantLock])) |
11 | 13 |
|
12 | 14 | (defmacro with-conn [[conn datasource] & body] |
13 | 15 | (let [conn (vary-meta conn assoc :tag Connection)] |
|
143 | 145 | :binary? (boolean (and (:freeze-bytes opts) (:thaw-bytes opts))))] |
144 | 146 | (merge {:ddl (ddl opts)} opts))) |
145 | 147 |
|
146 | | -(defn make |
| 148 | +(defn make |
| 149 | + "Create new DataScript storage from javax.sql.DataSource. |
| 150 | + |
| 151 | + Mandatory opts: |
| 152 | + |
| 153 | + :dbtype :: keyword, one of :h2, :mysql, :postgresql or :sqlite |
| 154 | + |
| 155 | + Optional opts: |
| 156 | + |
| 157 | + :batch-size :: int, default 1000 |
| 158 | + :table :: string, default \"datascript\" |
| 159 | + :ddl :: custom DDL to create :table. Must have `addr, int` and `content, text` columns |
| 160 | + :freeze-str :: (fn [any]) -> str, serialize DataScript segments, default pr-str |
| 161 | + :thaw-str :: (fn [str]) -> any, deserialize DataScript segments, default clojure.edn/read-string |
| 162 | + :freeze-bytes :: (fn [any]) -> bytes, same idea as freeze-str, but for binary serialization |
| 163 | + :thaw-bytes :: (fn [bytes]) -> any |
| 164 | + |
| 165 | + :freeze-str and :thaw-str, :freeze-bytes and :thaw-bytes should come in pairs, and are mutually exclusive |
| 166 | + (it’s either binary or string serialization)" |
147 | 167 | ([datasource] |
148 | 168 | {:pre [(instance? DataSource datasource)]} |
149 | 169 | (make datasource {})) |
|
170 | 190 |
|
171 | 191 | 'datascript.storage/-delete |
172 | 192 | (fn [_ addr-seq] |
173 | | - (with-open [conn datasource] |
| 193 | + (with-conn [conn datasource] |
174 | 194 | (delete-impl conn opts addr-seq)))})))) |
175 | 195 |
|
176 | | -(defn swap-return! [*atom f & args] |
177 | | - (let [*res (volatile! nil)] |
178 | | - (swap! *atom |
179 | | - (fn [atom] |
180 | | - (let [[res atom'] (apply f atom args)] |
181 | | - (vreset! *res res) |
182 | | - atom'))) |
183 | | - @*res)) |
184 | | - |
185 | | -(defrecord Pool [*atom ^DataSource datasource opts] |
186 | | - java.lang.AutoCloseable |
| 196 | +(defn close |
| 197 | + "If storage was created with DataSource that also implements AutoCloseable, |
| 198 | + it will close that DataSource" |
| 199 | + [storage] |
| 200 | + (let [datasource (:datasource storage)] |
| 201 | + (when (instance? AutoCloseable datasource) |
| 202 | + (.close ^AutoCloseable datasource)))) |
| 203 | + |
| 204 | +(defmacro with-lock [lock & body] |
| 205 | + `(let [^Lock lock# ~lock] |
| 206 | + (try |
| 207 | + (.lock lock#) |
| 208 | + ~@body |
| 209 | + (finally |
| 210 | + (.unlock lock#))))) |
| 211 | + |
| 212 | +(defrecord Pool [*atom ^Lock lock ^Condition condition ^DataSource datasource opts] |
| 213 | + AutoCloseable |
187 | 214 | (close [_] |
188 | 215 | (let [[{:keys [taken free]} _] (swap-vals! *atom #(-> % (update :taken empty) (update :idle empty)))] |
189 | 216 | (doseq [conn (concat free taken)] |
190 | 217 | (try |
191 | | - (.close conn) |
| 218 | + (.close ^Connection conn) |
192 | 219 | (catch Exception e |
193 | 220 | (.printStackTrace e)))))) |
194 | 221 |
|
195 | 222 | DataSource |
196 | | - (getConnection [_] |
197 | | - (let [conn (swap-return! *atom |
198 | | - (fn [atom] |
199 | | - (if-some [conn (peek (:idle atom))] |
200 | | - [conn (-> atom |
201 | | - (update :taken conj conn) |
202 | | - (update :idle pop))] |
203 | | - [nil atom]))) |
204 | | - conn (or conn |
205 | | - (let [conn (.getConnection datasource)] |
206 | | - (swap! *atom update :taken conj conn) |
207 | | - conn)) |
208 | | - conn ^Connection conn |
| 223 | + (getConnection [this] |
| 224 | + (let [^Connection conn (with-lock lock |
| 225 | + (loop [] |
| 226 | + (let [atom @*atom] |
| 227 | + (cond |
| 228 | + ;; idle connections available |
| 229 | + (> (count (:idle atom)) 0) |
| 230 | + (let [conn (peek (:idle atom))] |
| 231 | + (swap! *atom #(-> % |
| 232 | + (update :taken conj conn) |
| 233 | + (update :idle pop))) |
| 234 | + conn) |
| 235 | + |
| 236 | + ;; has space for new connection |
| 237 | + (< (count (:taken atom)) (:max-conn opts)) |
| 238 | + (let [conn (.getConnection datasource)] |
| 239 | + (swap! *atom update :taken conj conn) |
| 240 | + conn) |
| 241 | + |
| 242 | + ;; already at limit |
| 243 | + :else |
| 244 | + (do |
| 245 | + (.await condition) |
| 246 | + (recur)))))) |
209 | 247 | *closed? (volatile! false)] |
210 | 248 | (Proxy/newProxyInstance |
211 | 249 | (.getClassLoader Connection) |
|
220 | 258 | (.rollback conn) |
221 | 259 | (.setAutoCommit conn true)) |
222 | 260 | (vreset! *closed? true) |
223 | | - (when-some [conn (swap-return! *atom |
224 | | - (fn [atom] |
225 | | - (if (>= (count (:idle atom)) (:max-conn opts)) |
226 | | - [conn (update atom :taken disj conn)] |
227 | | - [nil (-> atom |
228 | | - (update :taken disj conn) |
229 | | - (update :idle conj conn))])))] |
230 | | - (.close conn)) |
| 261 | + (with-lock lock |
| 262 | + (if (< (count (:idle @*atom)) (:max-idle-conn opts)) |
| 263 | + ;; normal return to pool |
| 264 | + (do |
| 265 | + (swap! *atom #(-> % |
| 266 | + (update :taken disj conn) |
| 267 | + (update :idle conj conn))) |
| 268 | + (.signal condition)) |
| 269 | + ;; excessive idle conn |
| 270 | + (do |
| 271 | + (swap! *atom update :taken disj conn) |
| 272 | + (.close conn)))) |
231 | 273 | nil) |
232 | 274 |
|
233 | 275 | "isClosed" |
|
237 | 279 | (.invoke method conn args))))))))) |
238 | 280 |
|
239 | 281 | (defn pool |
240 | | - ([datasource] |
241 | | - (pool datasource {})) |
242 | | - ([datasource opts] |
243 | | - (Pool. |
244 | | - (atom {:taken #{} |
245 | | - :idle []}) |
246 | | - datasource |
247 | | - (merge |
248 | | - {:max-conn 4} |
249 | | - opts)))) |
| 282 | + "Simple connection pool. |
| 283 | + |
| 284 | + Accepts javax.sql.DataSource, returns javax.sql.DataSource implementation |
| 285 | + that creates java.sql.Connection on demand, up to :max-conn, and keeps up |
| 286 | + to :max-idle-conn when no demand. |
| 287 | + |
| 288 | + Implements AutoCloseable, which closes all pooled connections." |
| 289 | + (^DataSource [datasource] |
| 290 | + (pool datasource {})) |
| 291 | + (^DataSource [datasource opts] |
| 292 | + {:pre [(instance? DataSource datasource)]} |
| 293 | + (let [lock (ReentrantLock.)] |
| 294 | + (Pool. |
| 295 | + (atom {:taken #{} |
| 296 | + :idle []}) |
| 297 | + lock |
| 298 | + (.newCondition lock) |
| 299 | + datasource |
| 300 | + (merge |
| 301 | + {:max-idle-conn 4 |
| 302 | + :max-conn 10} |
| 303 | + opts))))) |
0 commit comments