2019-01-08 07:03:20 +00:00
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
2019-01-08 04:38:58 +00:00
2019-01-08 07:03:20 +00:00
( ns next.jdbc
""
2019-01-10 21:15:40 +00:00
( :require [ clojure.set :as set ] )
2019-01-10 06:06:49 +00:00
( :import ( java.lang AutoCloseable )
( java.sql Connection DriverManager
PreparedStatement
ResultSet ResultSetMetaData
SQLException Statement )
2019-01-08 07:03:20 +00:00
( javax.sql DataSource )
( java.util Properties ) ) )
2019-01-10 06:06:49 +00:00
( comment
" Key areas of interaction :
1 a. Making a DataSource -- turn everything connectable into a DataSource
1 b. Given a DataSource , we can getConnection ( )
2 . Preparing a Statement -- connection + SQL + params ( + options )
( multiple param groups means addBatch ( ) calls )
3 . Execute a ( Prepared ) Statement to produce a ResultSet ( or update count )
( can execute batch of prepared statements and get multiple results ) "
" Additional areas :
1 . with-db-connection -- given 'something ' , get a connection , execute the
body , and close the connection ( if we opened it ) .
2 . with-db-transaction -- given 'something ' , get a connection , start a
transaction , execute the body , commit/rollback , and close the connection
( if we opened it else restore connection state ) . "
" Database metadata can tell us :
0 . If get generated keys is supported!
1 . If batch updates are supported
2 . If save points are supported
3 . If various concurrency/holdability/etc options are supported " )
2019-01-08 07:03:20 +00:00
( set! *warn-on-reflection* true )
2019-01-10 07:05:21 +00:00
( defprotocol Sourceable
2019-01-26 09:35:31 +00:00
( get-datasource ^ DataSource [ this ] ) )
2019-01-08 07:03:20 +00:00
( defprotocol Connectable
2019-01-26 09:59:37 +00:00
( get-connection ^ AutoCloseable [ this opts ] ) )
2019-01-11 03:23:35 +00:00
( defprotocol Executable
( -execute ^ clojure.lang.IReduceInit [ this sql-params opts ] ) )
2019-01-08 07:03:20 +00:00
( defprotocol Preparable
2019-01-26 09:35:31 +00:00
( prepare ^ PreparedStatement [ this sql-params opts ] )
( prepare-fn ^ PreparedStatement [ this sql-params factory ] ) )
2019-01-11 02:30:05 +00:00
2019-01-11 03:23:35 +00:00
( defn execute!
" General SQL execution function.
Returns a reducible that , when reduced , runs the SQL and yields the result. "
( [ stmt ] ( -execute stmt [ ] { } ) )
( [ connectable sql-params & [ opts ] ]
( -execute connectable sql-params opts ) ) )
2019-01-10 19:32:46 +00:00
( defn set-parameters
""
^ PreparedStatement
[ ^ PreparedStatement ps params ]
2019-01-11 02:46:24 +00:00
( when ( seq params )
2019-01-11 02:47:48 +00:00
( loop [ [ p & more ] params i 1 ]
( .setObject ps i p )
2019-01-11 02:46:24 +00:00
( when more
( recur more ( inc i ) ) ) ) )
ps )
2019-01-10 19:32:46 +00:00
2019-01-26 09:35:31 +00:00
( 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- pre-prepare*
" Given a some options , return 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 java.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 java.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-01-10 06:06:49 +00:00
( defn- prepare*
" Given a connection , a SQL statement , its parameters , and some options ,
return a PreparedStatement representing that. "
2019-01-26 09:35:31 +00:00
[ con [ sql & params ] opts ]
( set-parameters ( ( pre-prepare* opts ) con sql ) params ) )
( defn- prepare-fn*
" Given a connection , a SQL statement , its parameters , and some options ,
return a PreparedStatement representing that. "
[ con [ sql & params ] factory ]
( set-parameters ( factory con sql ) params ) )
2019-01-08 07:03:20 +00:00
( def ^ :private isolation-levels
"Transaction isolation levels."
2019-01-10 07:05:21 +00:00
{ :none Connection/TRANSACTION_NONE
:read-committed Connection/TRANSACTION_READ_COMMITTED
:read-uncommitted Connection/TRANSACTION_READ_UNCOMMITTED
:repeatable-read Connection/TRANSACTION_REPEATABLE_READ
:serializable Connection/TRANSACTION_SERIALIZABLE } )
2019-01-08 07:03:20 +00:00
( def ^ :private isolation-kws
"Map transaction isolation constants to our keywords."
( set/map-invert isolation-levels ) )
( defn get-isolation-level
" Given an actual JDBC connection , return the current transaction
isolation level , if known. Return :unknown if we do not recognize
the isolation level. "
[ ^ Connection jdbc ]
( isolation-kws ( .getTransactionIsolation jdbc ) :unknown ) )
( defn committable! [ con commit? ]
( when-let [ state ( :transacted con ) ]
( reset! state commit? ) )
con )
2019-01-11 02:33:12 +00:00
( defn transact*
2019-01-08 07:03:20 +00:00
""
2019-01-10 06:06:49 +00:00
[ con transacted f opts ]
( let [ { :keys [ isolation read-only? rollback-only? ] } opts
2019-01-08 07:03:20 +00:00
committable? ( not rollback-only? ) ]
( if transacted
;; should check isolation level; maybe implement save points?
( f con )
2019-01-26 09:59:37 +00:00
( with-open [ ^ AutoCloseable t-con ( assoc ( get-connection con opts )
2019-01-11 02:24:55 +00:00
;; FIXME: not a record/map!
2019-01-10 06:06:49 +00:00
:transacted ( atom committable? ) ) ]
2019-01-10 07:05:21 +00:00
( let [ ^ Connection jdbc t-con
2019-01-08 07:03:20 +00:00
old-autocommit ( .getAutoCommit jdbc )
old-isolation ( .getTransactionIsolation jdbc )
old-readonly ( .isReadOnly jdbc ) ]
( io!
( when isolation
( .setTransactionIsolation jdbc ( isolation isolation-levels ) ) )
( when read-only?
( .setReadOnly jdbc true ) )
( .setAutoCommit jdbc false )
( try
( let [ result ( f t-con ) ]
( if @ ( :transacted t-con )
( .commit jdbc )
( .rollback jdbc ) )
result )
( catch Throwable t
( try
( .rollback jdbc )
( catch Throwable rb
;; combine both exceptions
( throw ( ex-info ( str "Rollback failed handling \""
( .getMessage t )
"\"" )
{ :rollback rb
:handling t } ) ) ) )
( throw t ) )
( finally ; tear down
( committable! t-con committable? )
;; the following can throw SQLExceptions but we do not
;; want those to replace any exception currently being
;; handled -- and if the connection got closed, we just
;; want to ignore exceptions here anyway
( try
( .setAutoCommit jdbc old-autocommit )
( catch Exception _ ) )
( when isolation
( try
( .setTransactionIsolation jdbc old-isolation )
( catch Exception _ ) ) )
( when read-only?
( try
( .setReadOnly jdbc old-readonly )
( catch Exception _ ) ) ) ) ) ) ) ) ) ) )
( defmacro in-transaction
[ [ sym con opts ] & body ]
2019-01-11 02:33:12 +00:00
` ( transact* ~ con ( fn [ ~ sym ] ~@ body ) ~ opts ) )
2019-01-08 07:03:20 +00:00
( def ^ :private classnames
" Map of subprotocols to classnames. dbtype specifies one of these keys.
The subprotocols map below provides aliases for dbtype.
Most databases have just a single class name for their driver but we
support a sequence of class names to try in order to allow for drivers
that change their names over time ( e.g. , MySQL ) . "
{ "derby" "org.apache.derby.jdbc.EmbeddedDriver"
"h2" "org.h2.Driver"
"h2:mem" "org.h2.Driver"
"hsqldb" "org.hsqldb.jdbcDriver"
"jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver"
"mysql" [ "com.mysql.cj.jdbc.Driver"
"com.mysql.jdbc.Driver" ]
"oracle:oci" "oracle.jdbc.OracleDriver"
"oracle:thin" "oracle.jdbc.OracleDriver"
"postgresql" "org.postgresql.Driver"
"pgsql" "com.impossibl.postgres.jdbc.PGDriver"
"redshift" "com.amazon.redshift.jdbc.Driver"
"sqlite" "org.sqlite.JDBC"
"sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver" } )
( def ^ :private aliases
"Map of schemes to subprotocols. Used to provide aliases for dbtype."
{ "hsql" "hsqldb"
"jtds" "jtds:sqlserver"
"mssql" "sqlserver"
"oracle" "oracle:thin"
"oracle:sid" "oracle:thin"
"postgres" "postgresql" } )
( def ^ :private host-prefixes
" Map of subprotocols to non-standard host-prefixes.
Anything not listed is assumed to use //. "
{ "oracle:oci" "@"
"oracle:thin" "@" } )
( def ^ :private ports
"Map of subprotocols to ports."
{ "jtds:sqlserver" 1433
"mysql" 3306
"oracle:oci" 1521
"oracle:sid" 1521
"oracle:thin" 1521
"postgresql" 5432
"sqlserver" 1433 } )
( def ^ :private dbname-separators
"Map of schemes to separators. The default is / but a couple are different."
{ "mssql" ";DATABASENAME="
"sqlserver" ";DATABASENAME="
"oracle:sid" ":" } )
( defn- modify-connection
" Given a database connection and a map of options , update the connection
as specified by the options. "
2019-01-10 07:05:21 +00:00
^ Connection
[ ^ Connection connection opts ]
2019-01-08 07:03:20 +00:00
( when ( and connection ( contains? opts :auto-commit? ) )
( .setAutoCommit connection ( boolean ( :auto-commit? opts ) ) ) )
( when ( and connection ( contains? opts :read-only? ) )
( .setReadOnly connection ( boolean ( :read-only? opts ) ) ) )
connection )
( defn- ^ Properties as-properties
" Convert any seq of pairs to a java.utils.Properties instance.
Uses as-sql-name to convert both keys and values into strings. "
[ m ]
( let [ p ( Properties. ) ]
( doseq [ [ k v ] m ]
( .setProperty p ( name k ) ( str v ) ) )
p ) )
( defn- get-driver-connection
" Common logic for loading the DriverManager and the designed JDBC driver
class and obtaining the appropriate Connection object. "
2019-01-10 07:05:21 +00:00
[ url etc ]
2019-01-10 07:30:46 +00:00
;; force DriverManager to be loaded
( DriverManager/getLoginTimeout )
2019-01-26 09:59:37 +00:00
( DriverManager/getConnection url ( as-properties etc ) ) )
2019-01-08 07:03:20 +00:00
2019-01-10 07:30:46 +00:00
( defn- spec->url+etc
2019-01-08 07:03:20 +00:00
""
2019-01-10 06:06:49 +00:00
[ { :keys [ dbtype dbname host port classname ] :as db-spec } ]
2019-01-08 07:03:20 +00:00
( let [ ;; allow aliases for dbtype
subprotocol ( aliases dbtype dbtype )
host ( or host "127.0.0.1" )
port ( or port ( ports subprotocol ) )
db-sep ( dbname-separators dbtype "/" )
url ( cond ( = "h2:mem" dbtype )
( str "jdbc:" subprotocol ":" dbname ";DB_CLOSE_DELAY=-1" )
( # { "derby" "h2" "hsqldb" "sqlite" } subprotocol )
( str "jdbc:" subprotocol ":" dbname )
:else
( str "jdbc:" subprotocol ":"
( host-prefixes subprotocol "//" )
host
( when port ( str ":" port ) )
db-sep dbname ) )
etc ( dissoc db-spec :dbtype :dbname ) ]
2019-01-10 07:05:21 +00:00
;; verify the datasource is loadable
( if-let [ class-name ( or classname ( classnames subprotocol ) ) ]
( do
( if ( string? class-name )
( clojure.lang.RT/loadClassForName class-name )
( loop [ [ clazz & more ] class-name ]
( when-let [ load-failure
( try
( clojure.lang.RT/loadClassForName clazz )
nil
( catch Exception e
e ) ) ]
( if ( seq more )
( recur more )
( throw load-failure ) ) ) ) ) )
( throw ( ex-info ( str "Unknown dbtype: " dbtype ) db-spec ) ) )
2019-01-10 07:30:46 +00:00
[ url etc ] ) )
( defn- string->url+etc
2019-01-08 07:03:20 +00:00
""
[ s ]
2019-01-10 07:30:46 +00:00
[ s { } ] )
( defn- url+etc->datasource
""
2019-01-26 09:35:31 +00:00
[ [ url etc ] ]
2019-01-11 03:23:35 +00:00
( reify DataSource
2019-01-11 02:30:05 +00:00
( getConnection [ _ ]
2019-01-10 07:30:46 +00:00
( get-driver-connection url etc ) )
2019-01-11 02:30:05 +00:00
( getConnection [ _ username password ]
2019-01-10 07:30:46 +00:00
( get-driver-connection url
( assoc etc
:username username
2019-01-26 08:21:03 +00:00
:password password ) ) ) ) )
2019-01-08 07:03:20 +00:00
2019-01-11 03:23:35 +00:00
( extend-protocol Sourceable
clojure.lang.Associative
2019-01-26 09:35:31 +00:00
( get-datasource [ this ]
( url+etc->datasource ( spec->url+etc this ) ) )
2019-01-11 03:23:35 +00:00
DataSource
2019-01-26 09:35:31 +00:00
( get-datasource [ this ] this )
2019-01-11 03:23:35 +00:00
String
2019-01-26 09:35:31 +00:00
( get-datasource [ this ]
( url+etc->datasource ( string->url+etc this ) ) ) )
2019-01-11 03:23:35 +00:00
( extend-protocol Connectable
DataSource
2019-01-26 09:59:37 +00:00
( get-connection [ this opts ] ( -> ( .getConnection this )
( modify-connection opts ) ) )
2019-01-11 03:23:35 +00:00
Object
2019-01-26 09:59:37 +00:00
( get-connection [ this opts ] ( get-connection ( get-datasource this ) opts ) ) )
2019-01-11 03:23:35 +00:00
( extend-protocol Preparable
Connection
( prepare [ this sql-params opts ]
( prepare* this sql-params opts ) )
2019-01-26 09:35:31 +00:00
( prepare-fn [ this sql-params factory ]
( prepare-fn* this sql-params factory ) )
2019-01-11 03:23:35 +00:00
DataSource
( prepare [ this sql-params opts ]
2019-01-26 08:21:03 +00:00
( prepare ( .getConnection this ) sql-params opts ) )
2019-01-26 09:35:31 +00:00
( prepare-fn [ this sql-params factory ]
( prepare-fn ( .getConnection this ) sql-params factory ) )
2019-01-11 03:23:35 +00:00
Object
( prepare [ this sql-params opts ]
2019-01-26 09:35:31 +00:00
( prepare ( get-datasource this ) sql-params opts ) )
( prepare-fn [ this sql-params factory ]
( prepare-fn ( get-datasource this ) sql-params factory ) ) )
2019-01-08 07:03:20 +00:00
( defn- get-column-names
""
[ ^ ResultSet rs ]
( let [ ^ ResultSetMetaData rsmeta ( .getMetaData rs )
idxs ( range 1 ( inc ( .getColumnCount rsmeta ) ) ) ]
( mapv ( fn [ ^ Integer i ]
2019-01-10 19:33:19 +00:00
( keyword ( .getTableName rsmeta i )
( .getColumnLabel rsmeta i ) ) )
2019-01-08 07:03:20 +00:00
idxs ) ) )
( defn- mapify-result-set
" Given a result set , return an object that wraps the current row as a hash
map. Note that a result set is mutable and the current row will change behind
this wrapper so operations need to be eager ( and fairly limited ) .
2019-01-10 06:06:49 +00:00
2019-01-08 07:03:20 +00:00
Supports ILookup ( keywords are treated as strings ) .
2019-01-10 06:06:49 +00:00
2019-01-11 03:23:35 +00:00
Supports Associative ( again , keywords are treated as strings ) .
2019-01-10 06:06:49 +00:00
Supports Seqable which realizes a full row of the data.
2019-01-08 07:03:20 +00:00
Later we may realize a new hash map when assoc ( and other , future , operations
are performed on the result set row ) . "
2019-01-10 19:35:17 +00:00
[ ^ ResultSet rs ]
2019-01-08 07:03:20 +00:00
( let [ cols ( delay ( get-column-names rs ) ) ]
( reify
2019-01-10 06:06:49 +00:00
2019-01-08 07:03:20 +00:00
clojure.lang.ILookup
( valAt [ this k ]
( try
( .getObject rs ( name k ) )
( catch SQLException _ ) ) )
( valAt [ this k not-found ]
( try
( .getObject rs ( name k ) )
( catch SQLException _
not-found ) ) )
2019-01-10 06:06:49 +00:00
2019-01-08 07:03:20 +00:00
clojure.lang.Associative
( containsKey [ this k ]
( try
( .getObject rs ( name k ) )
true
( catch SQLException _
false ) ) )
( entryAt [ this k ]
( try
( clojure.lang.MapEntry. k ( .getObject rs ( name k ) ) )
( catch SQLException _ ) ) )
2019-01-10 21:16:14 +00:00
( assoc [ this k v ]
( assoc ( into { } ( seq this ) ) k v ) )
2019-01-10 06:06:49 +00:00
2019-01-08 07:03:20 +00:00
clojure.lang.Seqable
( seq [ this ]
2019-01-10 06:06:49 +00:00
( seq ( mapv ( fn [ ^ Integer i ]
( clojure.lang.MapEntry. ( nth @ cols ( dec i ) )
( .getObject rs i ) ) )
( range 1 ( inc ( count @ cols ) ) ) ) ) ) ) ) )
2019-01-08 07:03:20 +00:00
2019-01-10 19:35:17 +00:00
( defn- reduce-stmt
""
[ ^ PreparedStatement stmt f init ]
( if ( .execute stmt )
( let [ rs ( .getResultSet stmt )
rs-map ( mapify-result-set rs ) ]
( loop [ init ' init ]
( if ( .next rs )
( let [ result ( f init ' rs-map ) ]
( if ( reduced? result )
@ result
( recur result ) ) )
init ' ) ) )
2019-01-11 07:25:29 +00:00
( f init ( .getUpdateCount stmt ) ) ) )
2019-01-10 19:35:17 +00:00
2019-01-11 03:23:35 +00:00
( extend-protocol Executable
Connection
( -execute [ this sql-params opts ]
2019-01-26 09:35:31 +00:00
( let [ factory ( pre-prepare* opts ) ]
( reify clojure.lang.IReduceInit
( reduce [ _ f init ]
( with-open [ stmt ( prepare-fn this sql-params factory ) ]
( reduce-stmt stmt f init ) ) ) ) ) )
2019-01-11 03:23:35 +00:00
DataSource
( -execute [ this sql-params opts ]
2019-01-26 09:35:31 +00:00
( let [ factory ( pre-prepare* opts ) ]
( reify clojure.lang.IReduceInit
( reduce [ _ f init ]
2019-01-26 09:59:37 +00:00
( with-open [ con ( get-connection this opts ) ]
2019-01-26 09:35:31 +00:00
( with-open [ stmt ( prepare-fn con sql-params factory ) ]
( reduce-stmt stmt f init ) ) ) ) ) ) )
2019-01-11 03:23:35 +00:00
PreparedStatement
( -execute [ this _ _ ]
( reify clojure.lang.IReduceInit
( reduce [ _ f init ] ( reduce-stmt this f init ) ) ) )
Object
( -execute [ this sql-params opts ]
2019-01-26 09:35:31 +00:00
( -execute ( get-datasource this ) sql-params opts ) ) )
2019-01-08 07:03:20 +00:00
2019-01-10 19:44:19 +00:00
( defn query
""
[ connectable sql-params & [ opts ] ]
2019-01-26 08:21:03 +00:00
( into [ ]
( map ( or ( :row-fn opts ) ( partial into { } ) ) )
( execute! connectable sql-params opts ) ) )
( defn query-one
""
[ connectable sql-params & [ opts ] ]
( reduce ( fn [ _ row ] ( reduced ( ( or ( :row-fn opts ) ( partial into { } ) ) row ) ) )
nil
( execute! connectable sql-params opts ) ) )
( defn command!
""
[ connectable sql-params & [ opts ] ]
( reduce + 0 ( execute! connectable sql-params opts ) ) )
2019-01-10 19:44:19 +00:00
2019-01-08 07:03:20 +00:00
( comment
2019-01-10 06:06:49 +00:00
( def db-spec { :dbtype "h2:mem" :dbname "perf" } )
2019-01-10 07:30:46 +00:00
( def con db-spec )
2019-01-26 09:35:31 +00:00
( def con ( get-datasource db-spec ) )
2019-01-26 09:59:37 +00:00
( get-connection con { } )
( def con ( get-connection ( get-datasource db-spec ) { } ) )
( def con ( get-connection db-spec { } ) )
2019-01-26 08:21:03 +00:00
( command! con [ "DROP TABLE fruit" ] )
( command! con [ "CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)" ] )
( command! con [ "INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)" ] )
2019-01-11 02:30:05 +00:00
2019-01-11 02:47:48 +00:00
( println con )
2019-01-26 08:21:03 +00:00
( .close con )
2019-01-11 02:30:05 +00:00
2019-01-10 06:06:49 +00:00
( require ' [ criterium.core :refer [ bench quick-bench ] ] )
2019-01-11 02:30:05 +00:00
2019-01-10 07:05:21 +00:00
;; calibrate
2019-01-10 06:06:49 +00:00
( quick-bench ( reduce + ( take 10 e6 ( range ) ) ) )
2019-01-11 02:30:05 +00:00
2019-01-26 08:27:15 +00:00
;; raw java
( defn select* [ ^ Connection con ]
( let [ ps ( doto ( .prepareStatement con "SELECT * FROM fruit WHERE appearance = ?" )
( .setObject 1 "red" ) )
rs ( .executeQuery ps )
_ ( .next rs )
value ( .getObject rs "name" ) ]
( .close ps )
value ) )
( quick-bench ( select* con ) )
2019-01-11 02:47:48 +00:00
;; same as the Java example in java.jdbc perf test
( quick-bench
( reduce ( fn [ rs m ] ( reduced ( :name m ) ) )
nil
( execute! con [ "select * from fruit where appearance = ?" "red" ] ) ) )
2019-01-26 08:21:03 +00:00
( quick-bench
( query-one con
[ "select * from fruit where appearance = ?" "red" ]
{ :row-fn :name } ) )
2019-01-11 02:47:48 +00:00
;; simple query
2019-01-10 21:16:29 +00:00
( quick-bench
( query con [ "select * from fruit where appearance = ?" "red" ] ) )
2019-01-11 02:30:05 +00:00
2019-01-11 02:47:48 +00:00
;; with a prepopulated prepared statement
( with-open [ ps ( prepare con [ "select * from fruit where appearance = ?" "red" ] { } ) ]
2019-01-10 19:35:17 +00:00
( quick-bench
2019-01-11 02:47:48 +00:00
[ ( reduce ( fn [ _ row ] ( reduced ( :name row ) ) )
nil
( execute! ps ) ) ] ) )
2019-01-11 02:30:05 +00:00
2019-01-11 02:47:48 +00:00
;; same as above but setting parameters inside the benchmark
2019-01-11 02:30:05 +00:00
( with-open [ ps ( prepare con [ "select * from fruit where appearance = ?" ] { } ) ]
( quick-bench
[ ( reduce ( fn [ _ row ] ( reduced ( :name row ) ) )
nil
( execute! ( set-parameters ps [ "red" ] ) ) ) ] ) )
;; this takes more than twice the time of the one above which seems strange
2019-01-10 19:35:17 +00:00
( with-open [ ps ( prepare con [ "select * from fruit where appearance = ?" ] { } ) ]
( quick-bench
[ ( reduce ( fn [ _ row ] ( reduced ( :name row ) ) )
nil
( execute! ( set-parameters ps [ "red" ] ) ) )
( reduce ( fn [ _ row ] ( reduced ( :name row ) ) )
nil
( execute! ( set-parameters ps [ "fuzzy" ] ) ) ) ] ) )
2019-01-11 02:47:48 +00:00
;; full first row
2019-01-10 06:06:49 +00:00
( quick-bench
2019-01-26 08:21:03 +00:00
( query-one con [ "select * from fruit where appearance = ?" "red" ] ) )
2019-01-11 02:47:48 +00:00
;; test assoc works
2019-01-26 08:21:03 +00:00
( query-one con
[ "select * from fruit where appearance = ?" "red" ]
{ :row-fn # ( assoc % :test :value ) } )
;; test assoc works
( query con
[ "select * from fruit where appearance = ?" "red" ]
{ :row-fn # ( assoc % :test :value ) } ) )