next-jdbc/src/next/jdbc/prepare.clj

147 lines
5.6 KiB
Clojure
Raw Normal View History

2019-03-31 23:54:34 +00:00
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
(ns next.jdbc.prepare
2019-04-01 06:17:12 +00:00
"Mostly an implementation namespace for how PreparedStatement objects are
created by the next generation java.jdbc library.
set-parameters is public and may be useful if you have a PreparedStatement
that you wish to reuse and (re)set the parameters on it."
2019-03-31 23:54:34 +00:00
(:require [next.jdbc.protocols :as p])
(:import (java.sql Connection
PreparedStatement
ResultSet
Statement)))
(set! *warn-on-reflection* true)
(defprotocol ISQLParameter :extend-via-metadata true
"Protocol for setting SQL parameters in statement objects, which
can convert from Clojure values. The default implementation just
calls .setObject on the parameter value. It can be extended to use other
methods of PreparedStatement to convert and set parameter values."
(set-parameter [val stmt ix]
"Convert a Clojure value into a SQL value and store it as the ix'th
parameter in the given SQL statement object."))
(extend-protocol ISQLParameter
Object
(set-parameter [v ^PreparedStatement s ^long i]
(.setObject s i v))
nil
(set-parameter [_ ^PreparedStatement s ^long i]
(.setObject s i nil)))
2019-03-31 23:54:34 +00:00
(defn set-parameters
2019-04-01 06:17:12 +00:00
"Given a PreparedStatement and a vector of parameter values, update the
PreparedStatement with those parameters and return it.
Currently uses .setObject with no possibility of an override."
2019-03-31 23:54:34 +00:00
^java.sql.PreparedStatement
[^PreparedStatement ps params]
(when (seq params)
(loop [[p & more] params i 1]
(set-parameter p ps i)
2019-03-31 23:54:34 +00:00
(when more
(recur more (inc i)))))
ps)
(def ^{:private true
:doc "Map friendly :concurrency values to ResultSet constants."}
result-set-concurrency
{:read-only ResultSet/CONCUR_READ_ONLY
:updatable ResultSet/CONCUR_UPDATABLE})
(def ^{:private true
:doc "Map friendly :cursors values to ResultSet constants."}
result-set-holdability
{:hold ResultSet/HOLD_CURSORS_OVER_COMMIT
:close ResultSet/CLOSE_CURSORS_AT_COMMIT})
(def ^{:private true
:doc "Map friendly :type values to ResultSet constants."}
result-set-type
{:forward-only ResultSet/TYPE_FORWARD_ONLY
:scroll-insensitive ResultSet/TYPE_SCROLL_INSENSITIVE
:scroll-sensitive ResultSet/TYPE_SCROLL_SENSITIVE})
(defn- ^{:tag (class (into-array String []))} string-array
[return-keys]
(into-array String return-keys))
2019-04-01 00:30:10 +00:00
(defn ->factory
2019-03-31 23:54:34 +00:00
"Given a some options, return a statement factory -- a function that will
accept a connection and a SQL string and parameters, and return a
PreparedStatement representing that."
[{:keys [return-keys result-type concurrency cursors
fetch-size max-rows timeout]}]
(cond->
(cond
return-keys
(do
(when (or result-type concurrency cursors)
(throw (IllegalArgumentException.
(str ":concurrency, :cursors, and :result-type "
"may not be specified with :return-keys."))))
(if (vector? return-keys)
(let [key-names (string-array return-keys)]
(fn [^Connection con ^String sql]
(try
(try
(.prepareStatement con sql key-names)
(catch Exception _
;; assume it is unsupported and try regular generated keys:
(.prepareStatement con sql Statement/RETURN_GENERATED_KEYS)))
(catch Exception _
;; assume it is unsupported and try basic PreparedStatement:
(.prepareStatement con sql)))))
(fn [^Connection con ^String sql]
(try
(.prepareStatement con sql Statement/RETURN_GENERATED_KEYS)
(catch Exception _
;; assume it is unsupported and try basic PreparedStatement:
(.prepareStatement con sql))))))
(and result-type concurrency)
(if cursors
(fn [^Connection con ^String sql]
(.prepareStatement con sql
(get result-set-type result-type result-type)
(get result-set-concurrency concurrency concurrency)
(get result-set-holdability cursors cursors)))
(fn [^Connection con ^String sql]
(.prepareStatement con sql
(get result-set-type result-type result-type)
(get result-set-concurrency concurrency concurrency))))
(or result-type concurrency cursors)
(throw (IllegalArgumentException.
(str ":concurrency, :cursors, and :result-type "
"may not be specified independently.")))
:else
(fn [^Connection con ^String sql]
(.prepareStatement con sql)))
fetch-size (as-> f
(fn [^Connection con ^String sql]
(.setFetchSize ^PreparedStatement (f con sql) fetch-size)))
max-rows (as-> f
(fn [^Connection con ^String sql]
(.setMaxRows ^PreparedStatement (f con sql) max-rows)))
timeout (as-> f
(fn [^Connection con ^String sql]
(.setQueryTimeout ^PreparedStatement (f con sql) timeout)))))
2019-04-01 00:30:10 +00:00
(defn create
2019-03-31 23:54:34 +00:00
"Given a connection, a SQL statement, its parameters, and a statement factory,
return a PreparedStatement representing that."
^java.sql.PreparedStatement
[con sql params factory]
(set-parameters (factory con sql) params))
(extend-protocol p/Preparable
java.sql.Connection
(prepare [this sql-params opts]
(let [[sql & params] sql-params
2019-04-01 00:30:10 +00:00
factory (->factory opts)]
2019-03-31 23:54:34 +00:00
(set-parameters (factory this sql) params))))