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 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 ) ) )
( 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-03-31 03:36:53 +00:00
( prepare ^ PreparedStatement [ this sql-params opts ] ) )
( defprotocol Transactable
( -transact [ this body-fn opts ] ) )
2019-01-11 03:23:35 +00:00
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*
2019-03-31 03:36:53 +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. "
2019-01-26 09:35:31 +00:00
[ { :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 ) ) ) ) )
( defn- prepare-fn*
2019-03-31 03:36:53 +00:00
" Given a connection , a SQL statement , its parameters , and a statement factory ,
2019-01-26 09:35:31 +00:00
return a PreparedStatement representing that. "
2019-03-31 03:36:53 +00:00
^ PreparedStatement
2019-01-26 10:05:25 +00:00
[ con sql params factory ]
2019-01-26 09:35:31 +00:00
( 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
2019-01-11 02:33:12 +00:00
( defn transact*
2019-01-08 07:03:20 +00:00
""
2019-03-31 03:36:53 +00:00
[ ^ Connection con f opts ]
2019-01-10 06:06:49 +00:00
( let [ { :keys [ isolation read-only? rollback-only? ] } opts
2019-03-31 03:36:53 +00:00
old-autocommit ( .getAutoCommit con )
old-isolation ( .getTransactionIsolation con )
old-readonly ( .isReadOnly con ) ]
( io!
( when isolation
( .setTransactionIsolation con ( isolation isolation-levels ) ) )
( when read-only?
( .setReadOnly con true ) )
( .setAutoCommit con false )
( try
( let [ result ( f con ) ]
( if rollback-only?
( .rollback con )
( .commit con ) )
result )
( catch Throwable t
( try
( .rollback con )
( catch Throwable rb
;; combine both exceptions
( throw ( ex-info ( str "Rollback failed handling \""
( .getMessage t )
"\"" )
{ :rollback rb
:handling t } ) ) ) )
( throw t ) )
( finally ; tear down
;; 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 con old-autocommit )
( catch Exception _ ) )
( when isolation
2019-01-08 07:03:20 +00:00
( try
2019-03-31 03:36:53 +00:00
( .setTransactionIsolation con old-isolation )
( catch Exception _ ) ) )
( when read-only?
( try
( .setReadOnly con old-readonly )
( catch Exception _ ) ) ) ) ) ) ) )
( extend-protocol Transactable
Connection
( -transact [ this body-fn opts ]
( transact* this body-fn opts ) )
DataSource
( -transact [ this body-fn opts ]
( with-open [ con ( get-connection this opts ) ]
( transact* con body-fn opts ) ) )
Object
( -transact [ this body-fn opts ]
( -transact ( get-datasource this ) body-fn opts ) ) )
2019-01-08 07:03:20 +00:00
2019-03-31 06:11:37 +00:00
( defmacro with-transaction
2019-03-31 03:36:53 +00:00
[ [ sym connectable opts ] & body ]
` ( -transact ~ connectable ( 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- ^ 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-03-31 03:36:53 +00:00
( defn- make-connection
" Given a DataSource and a map of options , get a connection and update it
as specified by the options. "
^ Connection
[ ^ DataSource datasource opts ]
( let [ ^ Connection connection ( .getConnection datasource ) ]
( when ( contains? opts :auto-commit? )
( .setAutoCommit connection ( boolean ( :auto-commit? opts ) ) ) )
( when ( contains? opts :read-only? )
( .setReadOnly connection ( boolean ( :read-only? opts ) ) ) )
connection ) )
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-03-31 03:36:53 +00:00
( get-connection [ this opts ] ( make-connection this 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 ]
2019-03-31 03:36:53 +00:00
( let [ [ sql & params ] sql-params
factory ( pre-prepare* opts ) ]
( set-parameters ( factory this sql ) params ) ) ) )
2019-01-08 07:03:20 +00:00
( defn- get-column-names
""
2019-03-31 06:31:35 +00:00
[ ^ ResultSet rs opts ]
2019-01-08 07:03:20 +00:00
( let [ ^ ResultSetMetaData rsmeta ( .getMetaData rs )
idxs ( range 1 ( inc ( .getColumnCount rsmeta ) ) ) ]
2019-03-31 06:31:35 +00:00
( if-let [ ident-fn ( :identifiers opts ) ]
( mapv ( fn [ ^ Integer i ]
( keyword ( when-let [ qualifier ( not-empty ( .getTableName rsmeta i ) ) ]
( ident-fn qualifier ) )
( ident-fn ( .getColumnLabel rsmeta i ) ) ) )
idxs )
( mapv ( fn [ ^ Integer i ]
( keyword ( not-empty ( .getTableName rsmeta i ) )
( .getColumnLabel rsmeta i ) ) )
idxs ) ) ) )
2019-01-08 07:03:20 +00:00
( 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-03-31 03:36:53 +00:00
Supports Associative ( again , keywords are treated as strings ) . If you assoc ,
a full row will be realized ( via seq/into ) .
2019-01-10 06:06:49 +00:00
2019-03-31 03:36:53 +00:00
Supports Seqable which realizes a full row of the data. "
2019-03-31 06:31:35 +00:00
[ ^ ResultSet rs opts ]
( let [ cols ( delay ( get-column-names rs opts ) ) ]
2019-01-08 07:03:20 +00:00
( 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
""
2019-03-31 06:31:35 +00:00
[ ^ PreparedStatement stmt f init opts ]
2019-03-31 06:12:37 +00:00
( if-let [ ^ ResultSet rs ( if ( .execute stmt )
( .getResultSet stmt )
2019-03-31 06:31:35 +00:00
( when ( :return-keys opts )
2019-03-31 06:12:37 +00:00
( try
( .getGeneratedKeys stmt )
( catch Exception _ ) ) ) ) ]
2019-03-31 06:31:35 +00:00
( let [ rs-map ( mapify-result-set rs opts ) ]
2019-01-10 19:35:17 +00:00
( loop [ init ' init ]
( if ( .next rs )
( let [ result ( f init ' rs-map ) ]
( if ( reduced? result )
@ result
( recur result ) ) )
init ' ) ) )
2019-03-31 06:12:37 +00:00
( f init { ::update-count ( .getUpdateCount stmt ) } ) ) )
2019-01-10 19:35:17 +00:00
2019-01-11 03:23:35 +00:00
( extend-protocol Executable
Connection
2019-03-31 06:31:35 +00:00
( -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-03-31 06:31:35 +00:00
( with-open [ stmt ( prepare-fn* this
( first sql-params )
( rest sql-params )
factory ) ]
( reduce-stmt stmt f init opts ) ) ) ) ) )
2019-01-11 03:23:35 +00:00
DataSource
2019-03-31 06:31:35 +00:00
( -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-03-31 06:31:35 +00:00
( with-open [ stmt ( prepare-fn* con
( first sql-params )
( rest sql-params )
factory ) ]
( reduce-stmt stmt f init opts ) ) ) ) ) ) )
2019-01-11 03:23:35 +00:00
PreparedStatement
2019-03-31 06:31:35 +00:00
( -execute [ this _ opts ]
2019-01-11 03:23:35 +00:00
( reify clojure.lang.IReduceInit
2019-03-31 06:12:37 +00:00
;; we can't tell if this PreparedStatement will return generated
;; keys so we pass a truthy value to at least attempt it if we
;; do not get a ResultSet back from the execute call
2019-03-31 06:31:35 +00:00
( reduce [ _ f init ]
( reduce-stmt this f init ( assoc opts :return-keys true ) ) ) ) )
2019-01-11 03:23:35 +00:00
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-03-31 06:12:37 +00:00
( defn reducible!
2019-03-31 03:36:53 +00:00
" 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-03-31 06:13:01 +00:00
( defn- into-map [ row ] ( into { } row ) )
2019-01-26 08:21:03 +00:00
2019-03-31 06:13:01 +00:00
( defn execute!
2019-01-26 08:21:03 +00:00
""
2019-03-31 06:13:01 +00:00
( [ connectable sql-params ] ( execute! connectable sql-params { } ) )
( [ connectable sql-params opts ]
( into [ ]
( map ( :row-fn opts into-map ) )
( reducible! connectable sql-params opts ) ) ) )
2019-01-26 08:21:03 +00:00
2019-03-31 06:13:01 +00:00
( defn execute-one!
2019-01-26 08:21:03 +00:00
""
2019-03-31 06:13:01 +00:00
( [ connectable sql-params ] ( execute-one! connectable sql-params { } ) )
( [ connectable sql-params opts ]
( reduce ( fn [ _ row ] ( reduced ( ( :row-fn opts into-map ) row ) ) )
nil
( reducible! 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-03-31 06:13:01 +00:00
( def db-spec { :dbtype "derby" :dbname "perf" :create true } )
( def db-spec { :dbtype "mysql" :dbname "worldsingles" :user "root" :password "visual" } )
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-03-31 06:13:01 +00:00
( execute! con [ "DROP TABLE fruit" ] )
;; h2
( execute! con [ "CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)" ] )
;; mysql
( execute! con [ "CREATE TABLE fruit (id int auto_increment, name varchar(32), appearance varchar(32), cost int, grade real, primary key (id))" ] )
( execute! 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)" ] )
( execute! 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)" ]
{ :return-keys true } )
2019-01-11 02:30:05 +00:00
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 = ?" )
2019-03-31 06:13:01 +00:00
( .setObject 1 "red" ) )
2019-01-26 08:27:15 +00:00
rs ( .executeQuery ps )
_ ( .next rs )
value ( .getObject rs "name" ) ]
( .close ps )
value ) )
( quick-bench ( select* con ) )
2019-01-26 10:05:25 +00:00
;; almost same as the Java example above
2019-01-11 02:47:48 +00:00
( quick-bench
( reduce ( fn [ rs m ] ( reduced ( :name m ) ) )
nil
2019-03-31 06:13:01 +00:00
( reducible! con [ "select * from fruit where appearance = ?" "red" ] ) ) )
2019-01-26 08:21:03 +00:00
( quick-bench
2019-03-31 06:13:01 +00:00
( execute-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
2019-03-31 06:13:01 +00:00
( execute! 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
2019-03-31 06:13:01 +00:00
( reducible! 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
2019-03-31 06:13:01 +00:00
( reducible! ( set-parameters ps [ "red" ] ) ) ) ] ) )
2019-01-11 02:30:05 +00:00
;; 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
2019-03-31 06:13:01 +00:00
( reducible! ( set-parameters ps [ "red" ] ) ) )
2019-01-10 19:35:17 +00:00
( reduce ( fn [ _ row ] ( reduced ( :name row ) ) )
nil
2019-03-31 06:13:01 +00:00
( reducible! ( 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-03-31 06:13:01 +00:00
( execute-one! con [ "select * from fruit where appearance = ?" "red" ] ) )
2019-01-11 02:47:48 +00:00
;; test assoc works
2019-03-31 06:13:01 +00:00
( execute-one! con
[ "select * from fruit where appearance = ?" "red" ]
{ :row-fn # ( assoc % :test :value ) } )
2019-03-31 06:31:35 +00:00
2019-01-26 08:21:03 +00:00
;; test assoc works
2019-03-31 06:13:01 +00:00
( execute! con
[ "select * from fruit where appearance = ?" "red" ]
{ :row-fn # ( assoc % :test :value ) } )
( with-transaction [ t con { :rollback-only? true } ]
( execute! t [ "INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (5,'Pear','green',49,47)" ] )
( execute! t [ "select * from fruit where name = ?" "Pear" ] ) )
2019-03-31 06:31:35 +00:00
( execute! con [ "select * from fruit where name = ?" "Pear" ] ) )