;; copyright (c) 2018-2019 Sean Corfield, all rights reserved (ns next.jdbc.prepare "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." (:require [next.jdbc.protocols :as p]) (:import (java.sql Connection PreparedStatement ResultSet Statement))) (set! *warn-on-reflection* true) (defprotocol SettableParameter :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 SettableParameter Object (set-parameter [v ^PreparedStatement s ^long i] (.setObject s i v)) nil (set-parameter [_ ^PreparedStatement s ^long i] (.setObject s i nil))) (defn set-parameters "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." ^java.sql.PreparedStatement [^PreparedStatement ps params] (when (seq params) (loop [[p & more] params i 1] (set-parameter p ps i) (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)) (defn ->factory "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))))) (defn create "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 factory (->factory opts)] (set-parameters (factory this sql) params))))