pax_global_header 0000666 0000000 0000000 00000000064 11765226254 0014524 g ustar 00root root 0000000 0000000 52 comment=ffb284804d0fb56f86b2ea4a7c23d884702226c7
clojure-java.jdbc-ad7f7f6/ 0000775 0000000 0000000 00000000000 11765226254 0015515 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/.gitignore 0000664 0000000 0000000 00000000230 11765226254 0017500 0 ustar 00root root 0000000 0000000 *.jar
.classpath
.project
.settings
bin
classes
clojure_test_derby
clojure_test_hsqldb.*
derby.log
target
test-all.sh
settings.xml
/clojure_test_sqlite
clojure-java.jdbc-ad7f7f6/CHANGES.txt 0000664 0000000 0000000 00000011201 11765226254 0017321 0 ustar 00root root 0000000 0000000 Changes in 0.2.2:
* Handle Oracle unknown row count affected [JDBC-33](http://dev.clojure.org/jira/browse/JDBC-33)
* Handle jdbc: prefix in string db-specs [JDBC-32](http://dev.clojure.org/jira/browse/JDBC-32)
* Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://dev.clojure.org/jira/browse/JDBC-31)
Changes in 0.2.1:
* Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://dev.clojure.org/jira/browse/JDBC-29)
* Make do-prepared-return-keys (for Korma team) [JDBC-30](http://dev.clojure.org/jira/browse/JDBC-30)
Changes in 0.2.0:
* Merge internal namespace into main jdbc namespace and update symbol visibility / naming.
Changes in 0.1.4:
* Unwrap RTE for nested transaction exception (we already unwrapped top-level transaction RTEs).
* Remove reflection warning unwrapping RunTimeException (Alan Malloy)
Changes in 0.1.3:
* Fix JDBC-26 (fully) by adding transaction/generated keys support for SQLite3 (based on patch from Nelson Morris)
Changes in 0.1.2:
* Fix JDBC-23 by handling prepared statement params correctly (Ghadi Shayban)
* Fix JDBC-26 by adding support for SQLite3 (based on patch from Nelson Morris)
* Fix JDBC-27 by replacing replicate with repeat (Jonas Enlund)
* Ensure MS SQL Server passes tests with both Microsoft and jTDS drivers
* Build server now tests derby, hsqldb and sqlite by default
* Update README per Stuart Sierra's outline for contrib projects
Changes in 0.1.1:
* Fix JDBC-21 by adding support for db-spec as URI (Phil Hagelberg).
* Fix JDBC-22 by deducing driver class name from subprotocol (Phil Hagelberg).
* Add Postgres dependency so tests can be automcated (Phil Hagelberg).
* Add ability to specify test databases via TEST_DBS environment variable (Phil Hagelberg).
Changes in 0.1.0:
* Fix JDBC-15 by removing dependence on deprecated structmap.
Changes in 0.0.7:
* Fix JDBC-9 by renaming duplicate columns instead of throwing an exception.
- thanx to Peter Siewert!
* Fix JDBC-16 by ensuring do-prepared works with no param-groups provided.
* Fix JDBC-17 by adding type hints to remove more reflection warnings.
- thanx to Stuart Sierra!
Documentation:
* Address JDBC-4 by documenting how to do connection pooling.
Changes in 0.0.6:
* Move former tests to test-utilities namespace - these do not touch a database
* Convert old "test" examples into real tests against real databases
- tested locally against MySQL, Apache Derby, HSQLDB
- build system should run against Apache Derby, HSQLSB
- will add additional databases later
* Fix JDBC-12 by removing batch when doing a single update
* Remove wrapping of exceptions in transactions to make it easier to work with SQLExceptions
Changes in 0.0.5:
* Add prepare-statement function to ease creation of PreparedStatement with common options:
- see docstring for details
* with-query-results now allows the SQL/params vector to be:
- a PreparedStatement object, followed by any parameters the SQL needs
- a SQL query string, followed by any parameters it needs
- options (for prepareStatement), a SQL query string, followed by any parameters it needs
* Add support for databases that cannot return generated keys (e.g., HSQLDB)
- insert operations silently return the insert counts instead of generated keys
- it is the user's responsibility to handle this if you're using such a database!
Changes in 0.0.4:
* Fix JDBC-2 by allowing :table-spec {string} at the end of create-table arguments:
(sql/create-table :foo [:col1 "int"] ["col2" :int] :table-spec "ENGINE=MyISAM")
* Fix JDBC-8 by removing all reflection warnings
* Fix JDBC-11 by no longer committing the transaction when an Error occurs
* Clean up as-... functions to reduce use of (binding)
* Refactor do-prepared*, separating out return keys logic and parameter setting logic
- in preparation for exposing more hooks in PreparedStatement creation / manipulation
Changes in 0.0.3:
* Fix JDBC-10 by using .executeUpdate when generating keys (MS SQL Server, PostgreSQL compatibility issue)
Changes in 0.0.2:
* Fix JDBC-7 Clojure 1.2 compatibility (thanx to Aaron Bedra!)
Changes in 0.0.1 (compared to clojure.contrib.sql):
* Exposed print-... functions for exception printing; no longer writes exceptions to *out*
* Add clojure.java.jdbc/resultset-seq (to replace clojure.core/resultset-seq which should be deprecated)
* Add support for naming and quoting strategies - see http://clojure.github.com/java.jdbc/doc/clojure/java/jdbc/NameMapping.html
- The formatting is a bit borked, Tom F knows about this and is working on an enhancement to auto-doc to improve it
* Add ability to return generated keys from single insert operations, add insert-record function
* Clojure 1.3 compatibility
clojure-java.jdbc-ad7f7f6/README.md 0000664 0000000 0000000 00000015144 11765226254 0017001 0 ustar 00root root 0000000 0000000 clojure.java.jdbc
========================================
A Clojure wrapper for JDBC-based access to databases.
Formerly known as clojure.contrib.sql.
Releases and Dependency Information
========================================
Latest stable release: 0.2.2
* [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22)
* [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~)
[Leiningen](https://github.com/technomancy/leiningen) dependency information:
[org.clojure/java.jdbc "0.2.2"]
[Maven](http://maven.apache.org/) dependency information:
org.clojure
java.jdbc
0.2.2
Example Usage
========================================
(require '[clojure.java.jdbc :as sql])
(def mysql-db {:subprotocol "mysql"
:subname "//127.0.0.1:3306/clojure_test"
:user "clojure_test"
:password "clojure_test"})
(sql/with-connection mysql-db
(sql/insert-records :fruit
{:name "Apple" :appearance "rosy" :cost 24}
{:name "Orange" :appearance "round" :cost 49}))
(sql/with-connection mysql-db
(sql/with-query-results rows
["SELECT * FROM fruit WHERE appearance = ?" "rosy"]
(:cost (first rows))))
For more detail see the [generated documentation on github](http://clojure.github.com/java.jdbc/).
Developer Information
========================================
* [GitHub project](https://github.com/clojure/java.jdbc)
* [Bug Tracker](http://dev.clojure.org/jira/browse/JDBC)
* [Continuous Integration](http://build.clojure.org/job/java.jdbc/)
* [Compatibility Test Matrix](http://build.clojure.org/job/java.jdbc-test-matrix/)
* Testing:
* Currently by default tests run only against Derby and HSQLDB, the in-process databases.
* To test against PostgreSQL, first create the user and database:
$ sudo -u postgres createuser clojure_test -P # password: clojure_test
$ sudo -u postgres createdb clojure_test -O clojure_test
* Or similarly with MySQL:
$ mysql -u root
mysql> create database clojure_test;
mysql> grant all on clojure_test.* to clojure_test identified by "clojure_test";
* Then run the tests with the TEST_DBS environment variable:
$ TEST_DBS=mysql,postgres mvn test
Change Log
====================
* Release 0.2.2 on 2012-06-10
* Handle Oracle unknown row count affected [JDBC-33](http://dev.clojure.org/jira/browse/JDBC-33)
* Handle jdbc: prefix in string db-specs [JDBC-32](http://dev.clojure.org/jira/browse/JDBC-32)
* Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://dev.clojure.org/jira/browse/JDBC-31)
* Release 0.2.1 on 2012-05-10
* Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://dev.clojure.org/jira/browse/JDBC-29)
* Make do-prepared-return-keys (for Korma team) [JDBC-30](http://dev.clojure.org/jira/browse/JDBC-30)
* Release 0.2.0 on 2012-04-23
* Merge internal namespace into main jdbc namespace [JDBC-19](http://dev.clojure.org/jira/browse/JDBC-19)
* Release 0.1.4 on 2012-04-15
* Unwrap RTE for nested transaction exceptions (we already
unwrapped top-level transaction RTEs).
* Remove reflection warning unwrapping RunTimeException (Alan Malloy)
* Release 0.1.3 on 2012-02-29
* Fix generated keys inside transactions for SQLite3 [JDBC-26](http://dev.clojure.org/jira/browse/JDBC-26)
* Release 0.1.2 on 2012-02-29
* Handle prepared statement params correctly [JDBC-23](http://dev.clojure.org/jira/browse/JDBC-23)
* Add support for SQLite3 [JDBC-26](http://dev.clojure.org/jira/browse/JDBC-26)
* Replace replicate (deprecated) with repeat [JDBC-27](http://dev.clojure.org/jira/browse/JDBC-27)
* Ensure MS SQL Server passes tests with both Microsoft and jTDS drivers
* Build server now tests derby, hsqldb and sqlite by default
* Update README per Stuart Sierra's outline for contrib projects
* Release 0.1.1 on 2011-11-02
* Accept string or URI in connection definition [JDBC-21](http://dev.clojure.org/jira/browse/JDBC-21)
* Allow driver, port and subprotocol to be deduced [JDBC-22](http://dev.clojure.org/jira/browse/JDBC-22)
* Release 0.1.0 on 2011-10-16
* Remove dependence on deprecated structmap [JDBC-15](http://dev.clojure.org/jira/browse/JDBC-15)
* Release 0.0.7 on 2011-10-11
* Rename duplicate columns [JDBC-9](http://dev.clojure.org/jira/browse/JDBC-9)
* Ensure do-preared traps invalid SQL [JDBC-16](http://dev.clojure.org/jira/JDBC-16)
* Release 0.0.6 on 2011-08-04
* Improve exception handling (unwrap RTE)
* Don't use batch for update (causes exceptions on Apache Derby) [JDBC-12](http://dev.clojure.org/jire/JDBC-12)
* Add test suite
* Release 0.0.5 on 2011-07-18
* Expose prepare-statement API
* Allow with-query-results to accept a PreparedStatement or options for creating one, instead of SQL query string and parameters
* Support databases that cannot return generated keys
* Release 0.0.4 on 2011-07-17
* Allow :table-spec {string} in create-table [JDBC-4](http://dev.clojure.org/jire/JDBC-4)
* Remove reflection warnings [JDBC-8](http://dev.clojure.org/jire/JDBC-8)
* Ensure transactions are not committed when Error occurs [JDBC-11](http://dev.clojure.org/jire/JDBC-11)
* Release 0.0.3 on 2011-07-01
* Key generation compatibility with MS SQL Server, PostgreSQL [JDBC-10](http://dev.clojure.org/jira/browse/JDBC-10)
* Release 0.0.2 on 2011-06-07
* Clojure 1.2 compatibility [JDBC-7](http://dev.clojure.org/jira/browse/JDBC-7)
* Release 0.0.1 on 2011-05-07
* Initial release
* Changes from clojure.contrib.sql:
* Expose print-... functions; no longer write exceptions to **\*out\***
* Define resultset-seq to replace clojure.core/resultset-seq
* Add naming / quoting strategies (see [name mapping documentation](http://clojure.github.com/java.jdbc/doc/clojure/java/jdbc/NameMapping.html)
* Return generated keys from insert operations, where possible
* Add insert-record function
* Clojure 1.3 compatibility
Copyright and License
========================================
Copyright (c) Stephen Gilardi, Sean Corfield, 2011-2012. All rights reserved. The use and
distribution terms for this software are covered by the Eclipse Public
License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
be found in the file epl-v10.html at the root of this distribution.
By using this software in any fashion, you are agreeing to be bound by
the terms of this license. You must not remove this notice, or any
other, from this software.
clojure-java.jdbc-ad7f7f6/doc/ 0000775 0000000 0000000 00000000000 11765226254 0016262 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/doc/clojure/ 0000775 0000000 0000000 00000000000 11765226254 0017725 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/doc/clojure/java/ 0000775 0000000 0000000 00000000000 11765226254 0020646 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/ 0000775 0000000 0000000 00000000000 11765226254 0021550 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/ConnectionPooling.md 0000664 0000000 0000000 00000003411 11765226254 0025520 0 ustar 00root root 0000000 0000000 # Connection Pooling
clojure.java.jdbc does not provide connection pooling directly but it is relatively easy to add to your project. The following example shows how to configure connection pooling use c3p0.
## Add the c3p0 dependency
If you're using Leiningen, you can just add the following to your dependencies:
[c3p0/c3p0 "0.9.1.2"]
In Maven, it would be:
c3p0
c3p0
0.9.1.2
## Create the pooled datasource from your db-spec
Define your db-spec as usual, for example (for MySQL):
(def db-spec
{:classname "com.mysql.jdbc.Driver"
:subprotocol "mysql"
:subname "//127.0.0.1:3306/mydb"
:user "myaccount"
:password "secret"})
Import the c3p0 class as part of your namespace declaration, for example:
(ns example.db
(:import com.mchange.v2.c3p0.ComboPooledDataSource))
Define a function that creates a pooled datasource:
(defn pool
[spec]
(let [cpds (doto (ComboPooledDataSource.)
(.setDriverClass (:classname spec))
(.setJdbcUrl (str "jdbc:" (:subprotocol spec) ":" (:subname spec)))
(.setUser (:user spec))
(.setPassword (:password spec))
;; expire excess connections after 30 minutes of inactivity:
(.setMaxIdleTimeExcessConnections (* 30 60))
;; expire connections after 3 hours of inactivity:
(.setMaxIdleTime (* 3 60 60)))]
{:datasource cpds}))
Now you can create a single connection pool:
(def pooled-db (delay (pool db-spec)))
(defn db-connection [] @pooled-db)
And then call (db-connection) wherever you need access to it. clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/NameMapping.md 0000664 0000000 0000000 00000010274 11765226254 0024272 0 ustar 00root root 0000000 0000000 # Mapping Keywords to/from Entity Names
Entity names in SQL may be specified as strings or as keywords. It's convenient to represent records as Clojure maps with keywords for the keys but this means that a mapping is required when moving from Clojure to SQL and back again. Historically, clojure.contrib.sql simply called (name) on keywords passed in and used clojure.core/resultset-seq to convert Java ResultSet objects back to Clojure maps, which had the side-effect of lowercasing all entity names as they became keywords. Whilst that is still the default behavior of clojure.java.jdbc, it is now possible to override this behavior in a couple of ways.
## Quoted Identifiers
The first problem that the old approach exposed was when table names or column names were the same as a SQL keyword. Databases provide a way to quote identifier names so that entity names do not get treated as SQL keywords (often referred to as 'stropping'). Unfortunately, the way quoting works tends to be vendor-specific so that Microsoft SQL Server accepts \[name\] and "name" whilst MySQL traditionally uses \`name\` (although recent versions also accept "name"). In order to support multiple approaches to quoted, clojure.java.jdbc now has a function *as-quoted-identifier* and a macro *with-quoted-identifiers* to allow you to specify how quoting should be handled.
A quote spec is either a single character or a pair of characters in a vector. For the former, the entity name is wrapped in a pair of that character. For the latter, the entity name is wrapped in the specified pair:
(as-quoted-identifier \` :name) ;; produces "`name`"
(as-quoted-identifier [[ ]] :name) ;; produces "[name]"
Any code can be wrapped in the *with-quoted-identifiers* macro to influence how keywords are mapped to entity names:
(sql/with-quoted-identifiers \`
(sql/insert-record
:fruit
{ :name "Pear" :appearance "green" :cost 99}))
;; INSERT INTO `fruit` ( `name`, `appearance`, `cost` )
;; VALUES ( 'Pear', 'green', 99 )
Note that if a string is used for an entity name, rather than a keyword, it is passed through unchanged, on the assumption that if you're explicitly passing strings, you want to control exactly what goes into the SQL.
## Naming Strategies
The second problem with the old approach was that in returned results, all entity names were mapped to lowercase so any case sensitivity in the original entity names was lost. In addition to the option to determine a quoting strategy, clojure.java.jdbc now has functions *as-named-identifier*, *as-named-keyword* and a macro *with-naming-strategy* to allow you to specify how entity name / keyword mapping should be performed in both directions.
A naming strategy may be a single function or a map containing the keys `:entity` and/or `:keyword`, whose values are functions. If a single function `f` is provided, it is treated as `{ :entity f }`, i.e., an entity naming strategy. The `:entity` mapping is used on keywords that need to be converted to entity names. The `:keyword` mapping is used on entity names that need to be converted to keywords.
(as-named-identifier clojure.string/upper-case :name) ;; produces "NAME"
(def quote-dash
{ :entity (partial as-quoted-str \`) :keyword #(.replace % "_" "-") })
(as-named-identifier quote-dash :name) ;; produces "`name`"
(as-named-keyword quote-dash "item_id") ;; produces :item-id
The default naming strategy is `{ :entity identity :keyword clojure.string/lower-case }`. clojure.java.jdbc provides its own version of *resultset-seq* that respects the current naming strategy (for entity names mapped to keywords). You cannot specify a naming strategy that produces strings instead of keywords (but you could use a keyword naming strategy of *identity* to preserve case and spelling).
## Convenience Functions
In addition to *as-quoted-identifier*, *as-named-identifier* and *as-named-keyword* described above, clojure.java.jdbc exposes *as-identifier* which maps strings/keywords to entity names under the current naming strategy and *as-keyword* which maps strings/keywords to keywords under the current naming strategy.
The core of the quoting strategy is also exposed as *as-quoted-str* (shown in the `quote-dash` example above). clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/UsingDDL.md 0000664 0000000 0000000 00000002441 11765226254 0023504 0 ustar 00root root 0000000 0000000 # Manipulating Tables with DDL
Currently you can create and drop tables using clojure.java.jdbc. To see how to manipulate data with SQL, see [Manipulating Data with SQL](https://github.com/clojure/java.jdbc/blob/master/doc/clojure/java/jdbc/UsingSQL.md).
## Creating a table
To create a table, use *create-table* with the table name and a vector for each column spec. Currently, table-level specifications are not supported.
(defn create-fruit
"Create a table"
[]
(sql/create-table
:fruit
[:name "varchar(32)" "PRIMARY KEY"]
[:appearance "varchar(32)"]
[:cost :int]
[:grade :real]))
## Dropping a table
To drop a table, use *drop-table* with the table name.
(defn drop-fruit
"Drop a table"
[]
(try
(sql/drop-table :fruit)
(catch Exception _)))
## Accessing table metadata
To retrieve the metadata for a table, you can operate on the connection itself. In future, functions may be added to make this easier.
(defn db-get-tables
"Demonstrate getting table info"
[]
(sql/with-connection db
(into []
(sql/resultset-seq
(-> (sql/connection)
(.getMetaData)
(.getTables nil nil nil (into-array ["TABLE" "VIEW"])))))))
clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/UsingSQL.md 0000664 0000000 0000000 00000016262 11765226254 0023546 0 ustar 00root root 0000000 0000000 # Manipulating Data with SQL
Here are some examples of using clojure.java.jdbc to manipulate data with SQL.
These examples assume a simple table called fruit (see [Manipulating Tables with DDL](https://github.com/clojure/java.jdbc/blob/master/doc/clojure/java/jdbc/UsingDDL.md)).
## Inserting multiple rows
If you want to insert complete rows, you can use *insert-rows* and provide the values as a simple vector for each row. Every column's value must be present in the same order the columns are declared in the table. This performs a single insert statement. If you attempt to insert a single row, a map of the generated keys will be returned.
(defn insert-rows-fruit
"Insert complete rows"
[]
(sql/insert-rows
:fruit
["Apple" "red" 59 87]
["Banana" "yellow" 29 92.2]
["Peach" "fuzzy" 139 90.0]
["Orange" "juicy" 89 88.6]))
## Inserting partial rows
If you want to insert rows but only specify some columns' values, you can use *insert-values* and provide the names of the columns following by vectors containing values for those columns. This performs a single insert statement. If you attempt to insert a single row, a map of the generated keys will be returned.
(defn insert-values-fruit
"Insert rows with values for only specific columns"
[]
(sql/insert-values
:fruit
[:name :cost]
["Mango" 722]
["Feijoa" 441]))
## Inserting a record
If you want to insert a single record, you can use *insert-record* and specify the columns and their values as a map. This performs a single insert statement. A map of the generated keys will be returned.
(defn insert-record-fruit
"Insert a single record, map from keys specifying columsn to values"
[]
(sql/insert-record
:fruit
{:name "Pear" :appearance "green" :cost 99}))
## Inserting multiple records
If you want to insert multiple records, you can use *insert-records* and specify each record as a map of columns and their values. This performs a separate insert statement for each record. The generated keys are returned in a sequence of maps.
(defn insert-records-fruit
"Insert records, maps from keys specifying columns to values"
[]
(sql/insert-records
:fruit
{:name "Pomegranate" :appearance "fresh" :cost 585}
{:name "Kiwifruit" :grade 93}))
## Using transactions
You can write multiple operations in a transaction to ensure they are either all performed, or all rolled back.
(defn db-write
"Write initial values to the database as a transaction"
[]
(sql/with-connection db
(sql/transaction
(drop-fruit)
(create-fruit)
(insert-rows-fruit)
(insert-values-fruit)
(insert-records-fruit)))
nil)
## Reading and processing rows
To execute code against each row in a result set, use *with-query-results* with SQL.
(defn db-read
"Read the entire fruit table"
[]
(sql/with-connection db
(sql/with-query-results res
["SELECT * FROM fruit"]
(doseq [rec res]
(println rec)))))
(defn db-read-all
"Return all the rows of the fruit table as a vector"
[]
(sql/with-connection db
(sql/with-query-results res
["SELECT * FROM fruit"]
(into [] res))))
(defn db-grade-range
"Print rows describing fruit that are within a grade range"
[min max]
(sql/with-connection db
(sql/with-query-results res
[(str "SELECT name, cost, grade "
"FROM fruit "
"WHERE grade >= ? AND grade <= ?")
min max]
(doseq [rec res]
(println rec)))))
(defn db-grade-a
"Print rows describing all grade a fruit (grade between 90 and 100)"
[]
(db-grade-range 90 100))
## Updating values across a table
To update column values based on a SQL predicate, use *update-values* with a SQL where clause and a map of columns to new values. The result is a sequence of update counts, indicating the number of records affected by each update (in this case, a single update and therefore a single count in the sequence).
(defn db-update-appearance-cost
"Update the appearance and cost of the named fruit"
[name appearance cost]
(sql/update-values
:fruit
["name=?" name]
{:appearance appearance :cost cost}))
(defn db-update
"Update two fruits as a transaction"
[]
(sql/with-connection db
(sql/transaction
(db-update-appearance-cost "Banana" "bruised" 14)
(db-update-appearance-cost "Feijoa" "green" 400)))
nil)
## Updating values or Inserting records conditionally
If you want to update existing records that match a SQL predicate or insert a new record if no existing records match, use *update-or-insert-values* with a SQL where clause and a map of columns to values. This calls *update-values* first and if no rows were updated, this calls *insert-values*. The result is either the sequence of update counts from the update or the sequence of generated key maps from the insert.
(defn db-update-or-insert
"Updates or inserts a fruit"
[record]
(sql/with-connection db
(sql/update-or-insert-values
:fruit
["name=?" (:name record)]
record)))
## Exception Handling and Transaction Rollback
Transactions are rolled back if an exception is thrown, as shown in these examples.
(defn db-exception
"Demonstrate rolling back a partially completed transaction on exception"
[]
(sql/with-connection db
(sql/transaction
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"]
["Pear" "bruised"])
;; at this point the insert-values call is complete, but the transaction
;; is not. the exception will cause it to roll back leaving the database
;; untouched.
(throw (Exception. "sql/test exception")))))
(defn db-sql-exception
"Demonstrate an sql exception"
[]
(sql/with-connection db
(sql/transaction
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"]
["Pear" "bruised"]
["Apple" "strange" "whoops"]))))
(defn db-batchupdate-exception
"Demonstrate a batch update exception"
[]
(sql/with-connection db
(sql/transaction
(sql/do-commands
"DROP TABLE fruit"
"DROP TABLE fruit"))))
(defn db-rollback
"Demonstrate a rollback-only trasaction"
[]
(sql/with-connection db
(sql/transaction
(prn "is-rollback-only" (sql/is-rollback-only))
(sql/set-rollback-only)
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"]
["Pear" "bruised"])
(prn "is-rollback-only" (sql/is-rollback-only))
(sql/with-query-results res
["SELECT * FROM fruit"]
(doseq [rec res]
(println rec))))
(prn)
(sql/with-query-results res
["SELECT * FROM fruit"]
(doseq [rec res]
(println rec)))))
clojure-java.jdbc-ad7f7f6/epl.html 0000664 0000000 0000000 00000030536 11765226254 0017172 0 ustar 00root root 0000000 0000000
Eclipse Public License - Version 1.0
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial
code and documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program
originate from and are distributed by that particular Contributor. A
Contribution 'originates' from a Contributor if it was added to the
Program by such Contributor itself or anyone acting on such
Contributor's behalf. Contributions do not include additions to the
Program which: (i) are separate modules of software distributed in
conjunction with the Program under their own license agreement, and (ii)
are not derivative works of the Program.
"Contributor" means any person or entity that distributes
the Program.
"Licensed Patents" mean patent claims licensable by a
Contributor which are necessarily infringed by the use or sale of its
Contribution alone or when combined with the Program.
"Program" means the Contributions distributed in accordance
with this Agreement.
"Recipient" means anyone who receives the Program under
this Agreement, including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each
Contributor hereby grants Recipient a non-exclusive, worldwide,
royalty-free copyright license to reproduce, prepare derivative works
of, publicly display, publicly perform, distribute and sublicense the
Contribution of such Contributor, if any, and such derivative works, in
source code and object code form.
b) Subject to the terms of this Agreement, each
Contributor hereby grants Recipient a non-exclusive, worldwide,
royalty-free patent license under Licensed Patents to make, use, sell,
offer to sell, import and otherwise transfer the Contribution of such
Contributor, if any, in source code and object code form. This patent
license shall apply to the combination of the Contribution and the
Program if, at the time the Contribution is added by the Contributor,
such addition of the Contribution causes such combination to be covered
by the Licensed Patents. The patent license shall not apply to any other
combinations which include the Contribution. No hardware per se is
licensed hereunder.
c) Recipient understands that although each Contributor
grants the licenses to its Contributions set forth herein, no assurances
are provided by any Contributor that the Program does not infringe the
patent or other intellectual property rights of any other entity. Each
Contributor disclaims any liability to Recipient for claims brought by
any other entity based on infringement of intellectual property rights
or otherwise. As a condition to exercising the rights and licenses
granted hereunder, each Recipient hereby assumes sole responsibility to
secure any other intellectual property rights needed, if any. For
example, if a third party patent license is required to allow Recipient
to distribute the Program, it is Recipient's responsibility to acquire
that license before distributing the Program.
d) Each Contributor represents that to its knowledge it
has sufficient copyright rights in its Contribution, if any, to grant
the copyright license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code
form under its own license agreement, provided that:
a) it complies with the terms and conditions of this
Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors
all warranties and conditions, express and implied, including warranties
or conditions of title and non-infringement, and implied warranties or
conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors
all liability for damages, including direct, indirect, special,
incidental and consequential damages, such as lost profits;
iii) states that any provisions which differ from this
Agreement are offered by that Contributor alone and not by any other
party; and
iv) states that source code for the Program is available
from such Contributor, and informs licensees how to obtain it in a
reasonable manner on or through a medium customarily used for software
exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each
copy of the Program.
Contributors may not remove or alter any copyright notices contained
within the Program.
Each Contributor must identify itself as the originator of its
Contribution, if any, in a manner that reasonably allows subsequent
Recipients to identify the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain
responsibilities with respect to end users, business partners and the
like. While this license is intended to facilitate the commercial use of
the Program, the Contributor who includes the Program in a commercial
product offering should do so in a manner which does not create
potential liability for other Contributors. Therefore, if a Contributor
includes the Program in a commercial product offering, such Contributor
("Commercial Contributor") hereby agrees to defend and
indemnify every other Contributor ("Indemnified Contributor")
against any losses, damages and costs (collectively "Losses")
arising from claims, lawsuits and other legal actions brought by a third
party against the Indemnified Contributor to the extent caused by the
acts or omissions of such Commercial Contributor in connection with its
distribution of the Program in a commercial product offering. The
obligations in this section do not apply to any claims or Losses
relating to any actual or alleged intellectual property infringement. In
order to qualify, an Indemnified Contributor must: a) promptly notify
the Commercial Contributor in writing of such claim, and b) allow the
Commercial Contributor to control, and cooperate with the Commercial
Contributor in, the defense and any related settlement negotiations. The
Indemnified Contributor may participate in any such claim at its own
expense.
For example, a Contributor might include the Program in a commercial
product offering, Product X. That Contributor is then a Commercial
Contributor. If that Commercial Contributor then makes performance
claims, or offers warranties related to Product X, those performance
claims and warranties are such Commercial Contributor's responsibility
alone. Under this section, the Commercial Contributor would have to
defend claims against the other Contributors related to those
performance claims and warranties, and if a court requires any other
Contributor to pay any damages as a result, the Commercial Contributor
must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
responsible for determining the appropriateness of using and
distributing the Program and assumes all risks associated with its
exercise of rights under this Agreement , including but not limited to
the risks and costs of program errors, compliance with applicable laws,
damage to or loss of data, programs or equipment, and unavailability or
interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this Agreement, and without further action
by the parties hereto, such provision shall be reformed to the minimum
extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity
(including a cross-claim or counterclaim in a lawsuit) alleging that the
Program itself (excluding combinations of the Program with other
software or hardware) infringes such Recipient's patent(s), then such
Recipient's rights granted under Section 2(b) shall terminate as of the
date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it
fails to comply with any of the material terms or conditions of this
Agreement and does not cure such failure in a reasonable period of time
after becoming aware of such noncompliance. If all Recipient's rights
under this Agreement terminate, Recipient agrees to cease use and
distribution of the Program as soon as reasonably practicable. However,
Recipient's obligations under this Agreement and any licenses granted by
Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this
Agreement, but in order to avoid inconsistency the Agreement is
copyrighted and may only be modified in the following manner. The
Agreement Steward reserves the right to publish new versions (including
revisions) of this Agreement from time to time. No one other than the
Agreement Steward has the right to modify this Agreement. The Eclipse
Foundation is the initial Agreement Steward. The Eclipse Foundation may
assign the responsibility to serve as the Agreement Steward to a
suitable separate entity. Each new version of the Agreement will be
given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version
of the Agreement is published, Contributor may elect to distribute the
Program (including its Contributions) under the new version. Except as
expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
rights or licenses to the intellectual property of any Contributor under
this Agreement, whether expressly, by implication, estoppel or
otherwise. All rights in the Program not expressly granted under this
Agreement are reserved.
This Agreement is governed by the laws of the State of New York and
the intellectual property laws of the United States of America. No party
to this Agreement will bring a legal action under this Agreement more
than one year after the cause of action arose. Each party waives its
rights to a jury trial in any resulting litigation.
clojure-java.jdbc-ad7f7f6/pom.xml 0000664 0000000 0000000 00000004217 11765226254 0017036 0 ustar 00root root 0000000 0000000
4.0.0
java.jdbc
0.2.2
${project.artifactId}
org.clojure
pom.contrib
0.0.25
Stephen C. Gilardi
Sean Corfield
scm:git:git@github.com:clojure/java.jdbc.git
scm:git:git@github.com:clojure/java.jdbc.git
git@github.com:clojure/java.jdbc.git
mysql
mysql-connector-java
5.1.6
test
org.apache.derby
derby
10.8.1.2
test
hsqldb
hsqldb
1.8.0.10
test
postgresql
postgresql
8.4-702.jdbc4
test
org.xerial
sqlite-jdbc
3.7.2
test
net.sourceforge.jtds
jtds
1.2.4
test
clojure-java.jdbc-ad7f7f6/src/ 0000775 0000000 0000000 00000000000 11765226254 0016304 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/main/ 0000775 0000000 0000000 00000000000 11765226254 0017230 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/main/clojure/ 0000775 0000000 0000000 00000000000 11765226254 0020673 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/main/clojure/clojure/ 0000775 0000000 0000000 00000000000 11765226254 0022336 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/main/clojure/clojure/java/ 0000775 0000000 0000000 00000000000 11765226254 0023257 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/main/clojure/clojure/java/jdbc.clj 0000664 0000000 0000000 00000073233 11765226254 0024663 0 ustar 00root root 0000000 0000000 ;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse Public
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;; be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other,
;; from this software.
;;
;; jdbc.clj
;;
;; A Clojure interface to sql databases via jdbc
;;
;; scgilardi (gmail)
;; Created 2 April 2008
;;
;; seancorfield (gmail)
;; Migrated from clojure.contrib.sql 17 April 2011
(ns
^{
:author "Stephen C. Gilardi, Sean Corfield",
:doc "A Clojure interface to SQL databases via JDBC
clojure.java.jdbc provides a simple abstraction for CRUD (create, read,
update, delete) operations on a SQL database, along with basic transaction
support. Basic DDL operations are also supported (create table, drop table,
access to table metadata).
Maps are used to represent records, making it easy to store and retrieve
data. Results can be processed using any standard sequence operations.
For most operations, Java's PreparedStatement is used so your SQL and
parameters can be represented as simple vectors where the first element
is the SQL string, with ? for each parameter, and the remaining elements
are the parameter values to be substituted. In general, operations return
the number of rows affected, except for a single record insert where any
generated keys are returned (as a map)." }
clojure.java.jdbc
(:import [java.net URI]
[java.sql BatchUpdateException DriverManager PreparedStatement ResultSet SQLException Statement]
[java.util Hashtable Map Properties]
[javax.naming InitialContext Name]
[javax.sql DataSource])
(:refer-clojure :exclude [resultset-seq])
(:require [clojure.string :as str]))
(def ^{:private true :dynamic true
:doc "The default entity naming strategy is to do nothing."}
*as-str*
identity)
(def ^{:private true :dynamic true
:doc "The default keyword naming strategy is to lowercase the entity."}
*as-key*
str/lower-case)
(defn as-str
"Given a naming strategy and a keyword, return the keyword as a
string per that naming strategy. Given (a naming strategy and)
a string, return it as-is."
[f x]
(if (instance? clojure.lang.Named x)
(f (name x))
(str x)))
(defn as-key
"Given a naming strategy and a string, return the string as a
keyword per that naming strategy. Given (a naming strategy and)
a keyword, return it as-is."
[f x]
(if (instance? clojure.lang.Named x)
x
(keyword (f (str x)))))
(defn as-identifier
"Given a keyword, convert it to a string using the current naming
strategy.
Given a string, return it as-is."
([x] (as-identifier x *as-str*))
([x f-entity] (as-str f-entity x)))
(defn as-keyword
"Given an entity name (string), convert it to a keyword using the
current naming strategy.
Given a keyword, return it as-is."
([x] (as-keyword x *as-key*))
([x f-keyword] (as-key f-keyword x)))
(defn- ^Properties as-properties
"Convert any seq of pairs to a java.utils.Properties instance.
Uses as-str to convert both keys and values into strings."
[m]
(let [p (Properties.)]
(doseq [[k v] m]
(.setProperty p (as-str identity k) (as-str identity v)))
p))
(def ^{:private true :dynamic true} *db* {:connection nil :level 0})
(def ^{:private true :doc "Map of classnames to subprotocols"} classnames
{"postgresql" "org.postgresql.Driver"
"mysql" "com.mysql.jdbc.Driver"
"sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver"
"jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver"
"derby" "org.apache.derby.jdbc.EmbeddedDriver"
"hsqldb" "org.hsqldb.jdbcDriver"
"sqlite" "org.sqlite.JDBC"})
(def ^{:private true :doc "Map of schemes to subprotocols"} subprotocols
{"postgres" "postgresql"})
(defn find-connection
"Returns the current database connection (or nil if there is none)"
^java.sql.Connection []
(:connection *db*))
(defn connection
"Returns the current database connection (or throws if there is none)"
^java.sql.Connection []
(or (find-connection)
(throw (Exception. "no current database connection"))))
(defn- parse-properties-uri [^URI uri]
(let [host (.getHost uri)
port (if (pos? (.getPort uri)) (.getPort uri))
path (.getPath uri)
scheme (.getScheme uri)]
(merge
{:subname (if port
(str "//" host ":" port path)
(str "//" host path))
:subprotocol (subprotocols scheme scheme)}
(if-let [user-info (.getUserInfo uri)]
{:user (first (str/split user-info #":"))
:password (second (str/split user-info #":"))}))))
(defn- strip-jdbc [^String spec]
(if (.startsWith spec "jdbc:")
(.substring spec 5)
spec))
(defn- get-connection
"Creates a connection to a database. db-spec is a map containing values
for one of the following parameter sets:
Factory:
:factory (required) a function of one argument, a map of params
(others) (optional) passed to the factory function in a map
DriverManager:
:subprotocol (required) a String, the jdbc subprotocol
:subname (required) a String, the jdbc subname
:classname (optional) a String, the jdbc driver class name
(others) (optional) passed to the driver as properties.
DataSource:
:datasource (required) a javax.sql.DataSource
:username (optional) a String
:password (optional) a String, required if :username is supplied
JNDI:
:name (required) a String or javax.naming.Name
:environment (optional) a java.util.Map
URI:
Parsed JDBC connection string - see below
String:
subprotocol://user:password@host:post/subname
An optional prefix of jdbc: is allowed."
[{:keys [factory
classname subprotocol subname
datasource username password
name environment]
:as db-spec}]
(cond
(instance? URI db-spec)
(get-connection (parse-properties-uri db-spec))
(string? db-spec)
(get-connection (URI. (strip-jdbc db-spec)))
factory
(factory (dissoc db-spec :factory))
(and subprotocol subname)
(let [url (format "jdbc:%s:%s" subprotocol subname)
etc (dissoc db-spec :classname :subprotocol :subname)
classname (or classname (classnames subprotocol))]
(clojure.lang.RT/loadClassForName classname)
(DriverManager/getConnection url (as-properties etc)))
(and datasource username password)
(.getConnection ^DataSource datasource ^String username ^String password)
datasource
(.getConnection ^DataSource datasource)
name
(let [env (and environment (Hashtable. ^Map environment))
context (InitialContext. env)
^DataSource datasource (.lookup context ^String name)]
(.getConnection datasource))
:else
(let [^String msg (format "db-spec %s is missing a required parameter" db-spec)]
(throw (IllegalArgumentException. msg)))))
(defn- make-name-unique
"Given a collection of column names and a new column name,
return the new column name made unique, if necessary, by
appending _N where N is some unique integer suffix."
[cols col-name n]
(let [suffixed-name (if (= n 1) col-name (str col-name "_" n))]
(if (apply distinct? suffixed-name cols)
suffixed-name
(recur cols col-name (inc n)))))
(defn- make-cols-unique
"Given a collection of column names, rename duplicates so
that the result is a collection of unique column names."
[cols]
(if (or (empty? cols) (apply distinct? cols))
cols
(reduce (fn [unique-cols col-name] (conj unique-cols (make-name-unique unique-cols col-name 1))) [] cols)))
(defn resultset-seq
"Creates and returns a lazy sequence of maps corresponding to
the rows in the java.sql.ResultSet rs. Based on clojure.core/resultset-seq
but it respects the current naming strategy. Duplicate column names are
made unique by appending _N before applying the naming strategy (where
N is a unique integer)."
[^ResultSet rs]
(let [rsmeta (.getMetaData rs)
idxs (range 1 (inc (.getColumnCount rsmeta)))
keys (->> idxs
(map (fn [^Integer i] (.getColumnLabel rsmeta i)))
make-cols-unique
(map (comp keyword *as-key*)))
row-values (fn [] (map (fn [^Integer i] (.getObject rs i)) idxs))
;; This used to use create-struct (on keys) and then struct to populate each row.
;; That had the side effect of preserving the order of columns in each row. As
;; part of JDBC-15, this was changed because structmaps are deprecated. We don't
;; want to switch to records so we're using regular maps instead. We no longer
;; guarantee column order in rows but using into {} should preserve order for up
;; to 16 columns (because it will use a PersistentArrayMap). If someone is relying
;; on the order-preserving behavior of structmaps, we can reconsider...
rows (fn thisfn []
(when (.next rs)
(cons (zipmap keys (row-values)) (lazy-seq (thisfn)))))]
(rows)))
(defn as-quoted-str
"Given a quoting pattern - either a single character or a vector pair of
characters - and a string, return the quoted string:
(as-quoted-str X foo) will return XfooX
(as-quoted-str [A B] foo) will return AfooB"
[q x]
(if (vector? q)
(str (first q) x (last q))
(str q x q)))
(defn as-named-identifier
"Given a naming strategy and a keyword, return the keyword as a string using the
entity naming strategy.
Given a naming strategy and a string, return the string as-is.
The naming strategy should either be a function (the entity naming strategy) or
a map containing :entity and/or :keyword keys which provide the entity naming
strategy and/or keyword naming strategy respectively."
[naming-strategy x]
(as-identifier x (if (map? naming-strategy) (or (:entity naming-strategy) identity) naming-strategy)))
(defn as-named-keyword
"Given a naming strategy and a string, return the string as a keyword using the
keyword naming strategy.
Given a naming strategy and a keyword, return the keyword as-is.
The naming strategy should either be a function (the entity naming strategy) or
a map containing :entity and/or :keyword keys which provide the entity naming
strategy and/or keyword naming strategy respectively.
Note that providing a single function will cause the default keyword naming
strategy to be used!"
[naming-strategy x]
(as-keyword x (if (and (map? naming-strategy) (:keyword naming-strategy)) (:keyword naming-strategy) str/lower-case)))
(defn as-quoted-identifier
"Given a quote pattern - either a single character or a pair of characters in
a vector - and a keyword, return the keyword as a string using a simple
quoting naming strategy.
Given a qote pattern and a string, return the string as-is.
(as-quoted-identifier X :name) will return XnameX as a string.
(as-quoted-identifier [A B] :name) will return AnameB as a string."
[q x]
(as-identifier x (partial as-quoted-str q)))
(defmacro with-naming-strategy
"Evaluates body in the context of a naming strategy.
The naming strategy is either a function - the entity naming strategy - or
a map containing :entity and/or :keyword keys which provide the entity naming
strategy and/or the keyword naming strategy respectively. The default entity
naming strategy is identity; the default keyword naming strategy is lower-case."
[naming-strategy & body ]
`(binding [*as-str* (if (map? ~naming-strategy) (or (:entity ~naming-strategy) identity) ~naming-strategy)
*as-key* (if (map? ~naming-strategy) (or (:keyword ~naming-strategy) str/lower-case))] ~@body))
(defmacro with-quoted-identifiers
"Evaluates body in the context of a simple quoting naming strategy."
[q & body ]
`(binding [*as-str* (partial as-quoted-str ~q)] ~@body))
(defn with-connection*
"Evaluates func in the context of a new connection to a database then
closes the connection."
[db-spec func]
(with-open [^java.sql.Connection con (get-connection db-spec)]
(binding [*db* (assoc *db* :connection con :level 0 :rollback (atom false))]
(func))))
(defmacro with-connection
"Evaluates body in the context of a new connection to a database then
closes the connection. db-spec is a map containing values for one of the
following parameter sets:
Factory:
:factory (required) a function of one argument, a map of params
(others) (optional) passed to the factory function in a map
DriverManager:
:subprotocol (required) a String, the jdbc subprotocol
:subname (required) a String, the jdbc subname
:classname (optional) a String, the jdbc driver class name
(others) (optional) passed to the driver as properties.
DataSource:
:datasource (required) a javax.sql.DataSource
:username (optional) a String
:password (optional) a String, required if :username is supplied
JNDI:
:name (required) a String or javax.naming.Name
:environment (optional) a java.util.Map"
[db-spec & body]
`(with-connection* ~db-spec (fn [] ~@body)))
(defn- rollback
"Accessor for the rollback flag on the current connection"
([]
(deref (:rollback *db*)))
([val]
(swap! (:rollback *db*) (fn [_] val))))
(defn transaction*
"Evaluates func as a transaction on the open database connection. Any
nested transactions are absorbed into the outermost transaction. By
default, all database updates are committed together as a group after
evaluating the outermost body, or rolled back on any uncaught
exception. If rollback is set within scope of the outermost transaction,
the entire transaction will be rolled back rather than committed when
complete."
[func]
(binding [*db* (update-in *db* [:level] inc)]
;; This ugliness makes it easier to catch SQLException objects
;; rather than something wrapped in a RuntimeException which
;; can really obscure your code when working with JDBC from
;; Clojure... :(
(letfn [(throw-non-rte [^Throwable ex]
(cond (instance? java.sql.SQLException ex) (throw ex)
(and (instance? RuntimeException ex) (.getCause ex)) (throw-non-rte (.getCause ex))
:else (throw ex)))]
(if (= (:level *db*) 1)
(let [^java.sql.Connection con (connection)
auto-commit (.getAutoCommit con)]
(io!
(.setAutoCommit con false)
(try
(let [result (func)]
(if (rollback)
(.rollback con)
(.commit con))
result)
(catch Exception e
(.rollback con)
(throw-non-rte e))
(finally
(rollback false)
(.setAutoCommit con auto-commit)))))
(try
(func)
(catch Exception e
(throw-non-rte e)))))))
(defmacro transaction
"Evaluates body as a transaction on the open database connection. Any
nested transactions are absorbed into the outermost transaction. By
default, all database updates are committed together as a group after
evaluating the outermost body, or rolled back on any uncaught
exception. If set-rollback-only is called within scope of the outermost
transaction, the entire transaction will be rolled back rather than
committed when complete."
[& body]
`(transaction* (fn [] ~@body)))
(defn set-rollback-only
"Marks the outermost transaction such that it will rollback rather than
commit when complete"
[]
(rollback true))
(defn is-rollback-only
"Returns true if the outermost transaction will rollback rather than
commit when complete"
[]
(rollback))
(defn- execute-batch
"Executes a batch of SQL commands and returns a sequence of update counts.
(-2) indicates a single operation operating on an unknown number of rows.
Specifically, Oracle returns that and we must call getUpdateCount() to get
the actual number of rows affected. In general, operations return an array
of update counts, so this may not be a general solution for Oracle..."
[stmt]
(let [result (.executeBatch stmt)]
(if (and (= 1 (count result)) (= -2 (first result)))
(list (.getUpdateCount stmt))
(seq result))))
(defn do-commands
"Executes SQL commands on the open database connection."
[& commands]
(with-open [^Statement stmt (let [^java.sql.Connection con (connection)] (.createStatement con))]
(doseq [^String cmd commands]
(.addBatch stmt cmd))
(transaction
(execute-batch stmt))))
(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 prepare-statement
"Create a prepared statement from a connection, a SQL string and an
optional list of parameters:
:return-keys true | false - default false
:result-type :forward-only | :scroll-insensitive | :scroll-sensitive
:concurrency :read-only | :updatable
:fetch-size n
:max-rows n"
[^java.sql.Connection con ^String sql & {:keys [return-keys result-type concurrency cursors fetch-size max-rows]}]
(let [^PreparedStatement stmt (cond
return-keys (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
(.prepareStatement con sql
(result-type result-set-type)
(concurrency result-set-concurrency)
(cursors result-set-holdability))
(.prepareStatement con sql
(result-type result-set-type)
(concurrency result-set-concurrency)))
:else (.prepareStatement con sql))]
(when fetch-size (.setFetchSize stmt fetch-size))
(when max-rows (.setMaxRows stmt max-rows))
stmt))
(defn- set-parameters
"Add the parameters to the given statement."
[^PreparedStatement stmt params]
(dorun
(map-indexed
(fn [ix value]
(.setObject stmt (inc ix) value))
params)))
(defn do-prepared
"Executes an (optionally parameterized) SQL prepared statement on the
open database connection. Each param-group is a seq of values for all of
the parameters.
Return a seq of update counts (one count for each param-group)."
[sql & param-groups]
(with-open [^PreparedStatement stmt (prepare-statement (connection) sql)]
(if (empty? param-groups)
(transaction* (fn [] (vector (.executeUpdate stmt))))
(do
(doseq [param-group param-groups]
(set-parameters stmt param-group)
(.addBatch stmt))
(transaction* (fn [] (execute-batch stmt)))))))
(defn create-table-ddl
"Given a table name and column specs with an optional table-spec
return the DDL string for creating a table based on that."
[name & specs]
(let [split-specs (partition-by #(= :table-spec %) specs)
col-specs (first split-specs)
table-spec (first (second (rest split-specs)))
table-spec-str (or (and table-spec (str " " table-spec)) "")
specs-to-string (fn [specs]
(apply str
(map as-identifier
(apply concat
(interpose [", "]
(map (partial interpose " ") specs))))))]
(format "CREATE TABLE %s (%s)%s"
(as-identifier name)
(specs-to-string col-specs)
table-spec-str)))
(defn create-table
"Creates a table on the open database connection given a table name and
specs. Each spec is either a column spec: a vector containing a column
name and optionally a type and other constraints, or a table-level
constraint: a vector containing words that express the constraint. An
optional suffix to the CREATE TABLE DDL describing table attributes may
by provided as :table-spec {table-attributes-string}. All words used to
describe the table may be supplied as strings or keywords."
[name & specs]
(do-commands (apply create-table-ddl name specs)))
(defn drop-table
"Drops a table on the open database connection given its name, a string
or keyword"
[name]
(do-commands
(format "DROP TABLE %s" (as-identifier name))))
(defn do-prepared-return-keys
"Executes an (optionally parameterized) SQL prepared statement on the
open database connection. The param-group is a seq of values for all of
the parameters.
Return the generated keys for the (single) update/insert."
[sql param-group]
(with-open [^PreparedStatement stmt (prepare-statement (connection) sql :return-keys true)]
(set-parameters stmt param-group)
(transaction* (fn [] (let [counts (.executeUpdate stmt)]
(try
(let [rs (.getGeneratedKeys stmt)
result (first (resultset-seq rs))]
;; sqlite (and maybe others?) requires
;; record set to be closed
(.close rs)
result)
(catch Exception _
;; assume generated keys is unsupported and return counts instead:
counts)))))))
(defn insert-values
"Inserts rows into a table with values for specified columns only.
column-names is a vector of strings or keywords identifying columns. Each
value-group is a vector containing a values for each column in
order. When inserting complete rows (all columns), consider using
insert-rows instead.
If a single set of values is inserted, returns a map of the generated keys."
[table column-names & value-groups]
(let [column-strs (map as-identifier column-names)
n (count (first value-groups))
return-keys (= 1 (count value-groups))
prepared-statement (if return-keys do-prepared-return-keys do-prepared)
template (apply str (interpose "," (repeat n "?")))
columns (if (seq column-names)
(format "(%s)" (apply str (interpose "," column-strs)))
"")]
(apply prepared-statement
(format "INSERT INTO %s %s VALUES (%s)"
(as-identifier table) columns template)
value-groups)))
(defn insert-rows
"Inserts complete rows into a table. Each row is a vector of values for
each of the table's columns in order.
If a single row is inserted, returns a map of the generated keys."
[table & rows]
(apply insert-values table nil rows))
(defn insert-records
"Inserts records into a table. records are maps from strings or keywords
(identifying columns) to values. Inserts the records one at a time.
Returns a sequence of maps containing the generated keys for each record."
[table & records]
(let [ins-v (fn [record] (insert-values table (keys record) (vals record)))]
(doall (map ins-v records))))
(defn insert-record
"Inserts a single record into a table. A record is a map from strings or
keywords (identifying columns) to values.
Returns a map of the generated keys."
[table record]
(let [keys (insert-records table record)]
(first keys)))
(defn delete-rows
"Deletes rows from a table. where-params is a vector containing a string
providing the (optionally parameterized) selection criteria followed by
values for any parameters."
[table where-params]
(let [[where & params] where-params]
(do-prepared
(format "DELETE FROM %s WHERE %s"
(as-identifier table) where)
params)))
(defn update-values
"Updates values on selected rows in a table. where-params is a vector
containing a string providing the (optionally parameterized) selection
criteria followed by values for any parameters. record is a map from
strings or keywords (identifying columns) to updated values."
[table where-params record]
(let [[where & params] where-params
column-strs (map as-identifier (keys record))
columns (apply str (concat (interpose "=?, " column-strs) "=?"))]
(do-prepared
(format "UPDATE %s SET %s WHERE %s"
(as-identifier table) columns where)
(concat (vals record) params))))
(defn update-or-insert-values
"Updates values on selected rows in a table, or inserts a new row when no
existing row matches the selection criteria. where-params is a vector
containing a string providing the (optionally parameterized) selection
criteria followed by values for any parameters. record is a map from
strings or keywords (identifying columns) to updated values."
[table where-params record]
(transaction
(let [result (update-values table where-params record)]
(if (zero? (first result))
(insert-values table (keys record) (vals record))
result))))
(defn with-query-results*
"Executes a query, then evaluates func passing in a seq of the results as
an argument. The first argument is a vector containing either:
[sql & params] - a SQL query, followed by any parameters it needs
[stmt & params] - a PreparedStatement, followed by any parameters it needs
(the PreparedStatement already contains the SQL query)
[options sql & params] - options and a SQL query for creating a
PreparedStatement, follwed by any parameters it needs
See prepare-statement for supported options."
[sql-params func]
(when-not (vector? sql-params)
(let [^Class sql-params-class (class sql-params)
^String msg (format "\"%s\" expected %s %s, found %s %s"
"sql-params"
"vector"
"[sql param*]"
(.getName sql-params-class)
(pr-str sql-params))]
(throw (IllegalArgumentException. msg))))
(let [special (first sql-params)
sql-is-first (string? special)
options-are-first (map? special)
sql (cond sql-is-first special
options-are-first (second sql-params))
params (vec (cond sql-is-first (rest sql-params)
options-are-first (rest (rest sql-params))
:else (rest sql-params)))
prepare-args (when (map? special) (flatten (seq special)))]
(with-open [^PreparedStatement stmt (if (instance? PreparedStatement special) special (apply prepare-statement (connection) sql prepare-args))]
(set-parameters stmt params)
(with-open [rset (.executeQuery stmt)]
(func (resultset-seq rset))))))
(defmacro with-query-results
"Executes a query, then evaluates body with results bound to a seq of the
results. sql-params is a vector containing either:
[sql & params] - a SQL query, followed by any parameters it needs
[stmt & params] - a PreparedStatement, followed by any parameters it needs
(the PreparedStatement already contains the SQL query)
[options sql & params] - options and a SQL query for creating a
PreparedStatement, follwed by any parameters it needs
See prepare-statement for supported options."
[results sql-params & body]
`(with-query-results* ~sql-params (fn [~results] ~@body)))
(defn print-sql-exception
"Prints the contents of an SQLException to *out*"
[^SQLException exception]
(let [^Class exception-class (class exception)]
(println
(format (str "%s:" \newline
" Message: %s" \newline
" SQLState: %s" \newline
" Error Code: %d")
(.getSimpleName exception-class)
(.getMessage exception)
(.getSQLState exception)
(.getErrorCode exception)))))
(defn print-sql-exception-chain
"Prints a chain of SQLExceptions to *out*"
[^SQLException exception]
(loop [e exception]
(when e
(print-sql-exception e)
(recur (.getNextException e)))))
(def ^{:private true} special-counts
{Statement/EXECUTE_FAILED "EXECUTE_FAILED"
Statement/SUCCESS_NO_INFO "SUCCESS_NO_INFO"})
(defn print-update-counts
"Prints the update counts from a BatchUpdateException to *out*"
[^BatchUpdateException exception]
(println "Update counts:")
(dorun
(map-indexed
(fn [index count]
(println (format " Statement %d: %s"
index
(get special-counts count count))))
(.getUpdateCounts exception))))
clojure-java.jdbc-ad7f7f6/src/test/ 0000775 0000000 0000000 00000000000 11765226254 0017263 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/test/clojure/ 0000775 0000000 0000000 00000000000 11765226254 0020726 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/ 0000775 0000000 0000000 00000000000 11765226254 0022371 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/java/ 0000775 0000000 0000000 00000000000 11765226254 0023312 5 ustar 00root root 0000000 0000000 clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/java/test_jdbc.clj 0000664 0000000 0000000 00000034056 11765226254 0025755 0 ustar 00root root 0000000 0000000 ;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse Public
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;; be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other,
;; from this software.
;;
;; test_jdbc.clj
;;
;; This namespace contains tests that exercise the JDBC portion of java.jdbc
;; so these tests expect databases to be available. Embedded databases can
;; be tested without external infrastructure (Apache Derby, HSQLDB). Other
;; databases will be available for testing in different environments. The
;; available databases for testing can be configured below.
;;
;; scgilardi (gmail)
;; Created 13 September 2008
;;
;; seancorfield (gmail)
;; Migrated from clojure.contrib.test-sql 17 April 2011
(ns clojure.java.test-jdbc
(:use clojure.test)
(:require [clojure.java.jdbc :as sql]))
;; Set test-databases according to whether you have the local database available:
;; Possible values so far: [:mysql :postgres :derby :hsqldb :mysql-str :postgres-str]
;; Apache Derby and HSQLDB can run without an external setup.
(def test-databases
(if-let [dbs (System/getenv "TEST_DBS")]
(map keyword (.split dbs ","))
;; enable more by default once the build server is equipped?
[:derby :hsqldb :sqlite]))
;; MS SQL Server requires more specialized configuration:
(def mssql-host
(if-let [host (System/getenv "TEST_MSSQL_HOST")] host "127.0.0.1\\SQLEXPRESS"))
(def mssql-port
(if-let [port (System/getenv "TEST_MSSQL_PORT")] port "1433"))
(def mssql-user
(if-let [user (System/getenv "TEST_MSSQL_USER")] user "sa"))
(def mssql-pass
(if-let [pass (System/getenv "TEST_MSSQL_PASS")] pass ""))
(def mssql-dbname
(if-let [name (System/getenv "TEST_MSSQL_NAME")] name "clojure_test"))
(def jtds-host
(if-let [host (System/getenv "TEST_JTDS_HOST")] host mssql-host))
(def jtds-port
(if-let [port (System/getenv "TEST_JTDS_PORT")] port mssql-port))
(def jtds-user
(if-let [user (System/getenv "TEST_JTDS_USER")] user mssql-user))
(def jtds-pass
(if-let [pass (System/getenv "TEST_JTDS_PASS")] pass mssql-pass))
(def jtds-dbname
(if-let [name (System/getenv "TEST_JTDS_NAME")] name mssql-dbname))
;; database connections used for testing:
(def mysql-db {:subprotocol "mysql"
:subname "//127.0.0.1:3306/clojure_test"
:user "clojure_test"
:password "clojure_test"})
(def derby-db {:subprotocol "derby"
:subname "clojure_test_derby"
:create true})
(def hsqldb-db {:subprotocol "hsqldb"
:subname "clojure_test_hsqldb"})
(def sqlite-db {:subprotocol "sqlite"
:subname "clojure_test_sqlite"})
(def postgres-db {:subprotocol "postgresql"
:subname "clojure_test"
:user "clojure_test"
:password "clojure_test"})
(def mssql-db {:subprotocol "sqlserver"
:subname (str "//" mssql-host ":" mssql-port ";DATABASENAME=" mssql-dbname)
:user mssql-user
:password mssql-pass})
(def jtds-db {:subprotocol "jtds:sqlserver"
:subname (str "//" jtds-host ":" jtds-port "/" jtds-dbname)
:user jtds-user
:password jtds-pass})
;; To test against the stringified DB connection settings:
(def mysql-str-db
"mysql://clojure_test:clojure_test@localhost:3306/clojure_test")
(def mysql-jdbc-str-db
"jdbc:mysql://clojure_test:clojure_test@localhost:3306/clojure_test")
(def postgres-str-db
"postgres://clojure_test:clojure_test@localhost/clojure_test")
(defn- test-specs
"Return a sequence of db-spec maps that should be used for tests"
[]
(for [db test-databases]
@(ns-resolve 'clojure.java.test-jdbc (symbol (str (name db) "-db")))))
(defn- clean-up
"Attempt to drop any test tables before we start a test."
[t]
(doseq [db (test-specs)]
(sql/with-connection db
(doseq [table [:fruit :fruit2 :veggies :veggies2]]
(try
(sql/drop-table table)
(catch Exception _
;; ignore
)))))
(t))
(use-fixtures
:each clean-up)
;; We start with all tables dropped and each test has to create the tables
;; necessary for it to do its job, and populate it as needed...
(defn- create-test-table
"Create a standard test table. Must be inside with-connection.
For MySQL, ensure table uses an engine that supports transactions!"
[table db]
(let [p (:subprotocol db)]
(sql/create-table
table
[:id :int (if (= "mysql" p) "PRIMARY KEY AUTO_INCREMENT" "DEFAULT 0")]
[:name "VARCHAR(32)" (if (= "mysql" p) "" "PRIMARY KEY")]
[:appearance "VARCHAR(32)"]
[:cost :int]
[:grade :real]
:table-spec (if (or (= "mysql" p) (and (string? db) (re-find #"mysql:" db)))
"ENGINE=InnoDB" ""))))
(deftest test-create-table
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))))
(deftest test-drop-table
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit2 db)
(sql/drop-table :fruit2)
(is (thrown? java.sql.SQLException (sql/with-query-results res ["SELECT * FROM fruit2"] (count res)))))))
(deftest test-do-commands
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit2 db)
(sql/do-commands "DROP TABLE fruit2")
(is (thrown? java.sql.SQLException (sql/with-query-results res ["SELECT * FROM fruit2"] (count res)))))))
(deftest test-do-prepared1
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit2 db)
(sql/do-prepared "INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )")
(is (= 1 (sql/with-query-results res ["SELECT * FROM fruit2"] (count res)))))))
(deftest test-do-prepared2
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit2 db)
(sql/do-prepared "DROP TABLE fruit2")
(is (thrown? java.sql.SQLException (sql/with-query-results res ["SELECT * FROM fruit2"] (count res)))))))
(deftest test-do-prepared3
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit2 db)
(sql/do-prepared "INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( ?, ?, ?, ? )" ["test" "test" 1 1.0])
(is (= 1 (sql/with-query-results res ["SELECT * FROM fruit2"] (count res)))))))
(deftest test-do-prepared4
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit2 db)
(sql/do-prepared "INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( ?, ?, ?, ? )" ["test" "test" 1 1.0] ["two" "two" 2 2.0])
(is (= 2 (sql/with-query-results res ["SELECT * FROM fruit2"] (count res)))))))
(deftest test-insert-rows
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(let [r (sql/insert-rows
:fruit
[1 "Apple" "red" 59 87]
[2 "Banana" "yellow" 29 92.2]
[3 "Peach" "fuzzy" 139 90.0]
[4 "Orange" "juicy" 89 88.6])]
(is (= '(1 1 1 1) r)))
(is (= 4 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(is (= "Apple" (sql/with-query-results res ["SELECT * FROM fruit WHERE appearance = ?" "red"] (:name (first res)))))
(is (= "juicy" (sql/with-query-results res ["SELECT * FROM fruit WHERE name = ?" "Orange"] (:appearance (first res))))))))
(deftest test-insert-values
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(let [r (sql/insert-values
:fruit
[:name :cost]
["Mango" 722]
["Feijoa" 441])]
(is (= '(1 1) r)))
(is (= 2 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(is (= "Mango" (sql/with-query-results res ["SELECT * FROM fruit WHERE cost = ?" 722] (:name (first res))))))))
(deftest test-insert-records
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(let [r (sql/insert-records
:fruit
{:name "Pomegranate" :appearance "fresh" :cost 585}
{:name "Kiwifruit" :grade 93})]
(condp = (:subprotocol db)
nil nil ; for the string connection args
"postgresql" (is (= 2 (count r)))
"mysql" (is (= '({:generated_key 1} {:generated_key 2}) r))
"sqlserver" (is (= '({:generated_keys nil} {:generated_keys nil}) r))
"jtds:sqlserver" (is (= '({:id nil} {:id nil}) r))
"hsqldb" (is (= '(1 1) r))
"sqlite" (is (= (list {(keyword "last_insert_rowid()") 1}
{(keyword "last_insert_rowid()") 2}) r))
"derby" (is (= '({:1 nil} {:1 nil}) r))))
(is (= 2 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(is (= "Pomegranate" (sql/with-query-results res ["SELECT * FROM fruit WHERE cost = ?" 585] (:name (first res))))))))
(deftest test-update-values
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(let [r (sql/insert-rows
:fruit
[1 "Apple" "red" 59 87]
[2 "Banana" "yellow" 29 92.2]
[3 "Peach" "fuzzy" 139 90.0]
[4 "Orange" "juicy" 89 88.6])]
(is (= '(1 1 1 1) r)))
(sql/update-values
:fruit
["name=?" "Banana"]
{:appearance "bruised" :cost 14})
(is (= 4 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(is (= "Apple" (sql/with-query-results res ["SELECT * FROM fruit WHERE appearance = ?" "red"] (:name (first res)))))
(is (= "Banana" (sql/with-query-results res ["SELECT * FROM fruit WHERE appearance = ?" "bruised"] (:name (first res)))))
(is (= 14 (sql/with-query-results res ["SELECT * FROM fruit WHERE name = ?" "Banana"] (:cost (first res))))))))
(deftest test-update-or-insert-values
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(sql/update-or-insert-values
:fruit
["name=?" "Pomegranate"]
{:name "Pomegranate" :appearance "fresh" :cost 585})
(is (= 1 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(is (= 585 (sql/with-query-results res ["SELECT * FROM fruit WHERE appearance = ?" "fresh"] (:cost (first res)))))
(sql/update-or-insert-values
:fruit
["name=?" "Pomegranate"]
{:name "Pomegranate" :appearance "ripe" :cost 565})
(is (= 1 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(is (= 565 (sql/with-query-results res ["SELECT * FROM fruit WHERE appearance = ?" "ripe"] (:cost (first res)))))
(sql/update-or-insert-values
:fruit
["name=?" "Apple"]
{:name "Apple" :appearance "green" :cost 74})
(is (= 2 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))))
(deftest test-partial-exception
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(try
(sql/transaction
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"]
["Pear" "bruised"])
(is (= 2 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))
(throw (Exception. "deliberate exception")))
(catch Exception _
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))))))
(deftest test-sql-exception
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(try
(sql/transaction
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"]
["Pear" "bruised"]
["Apple" "strange" "whoops"])
;; sqlite does not throw exception for too many items
(throw (java.sql.SQLException.)))
(catch java.sql.SQLException _
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))))
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))))
(deftest test-rollback
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(try
(sql/transaction
(is (not (sql/is-rollback-only)))
(sql/set-rollback-only)
(is (sql/is-rollback-only))
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"]
["Pear" "bruised"]
["Apple" "strange" "whoops"])
(is (= 3 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))
(catch java.sql.SQLException _
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))))
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))))
(deftest test-transactions-with-possible-generated-keys-result-set
(doseq [db (test-specs)]
(sql/with-connection db
(create-test-table :fruit db)
(try
(sql/transaction
(sql/set-rollback-only)
(sql/insert-values
:fruit
[:name :appearance]
["Grape" "yummy"])
(is (= 1 (sql/with-query-results res ["SELECT * FROM fruit"] (count res))))))
(is (= 0 (sql/with-query-results res ["SELECT * FROM fruit"] (count res)))))))
(deftest test-metadata
(doseq [db (test-specs)]
(when-not (and (map? db) (.endsWith ^String (:subprotocol db) "sqlserver"))
(let [metadata (sql/with-connection
db
(into []
(sql/resultset-seq
(-> (sql/connection)
(.getMetaData)
(.getTables nil nil nil (into-array ["TABLE" "VIEW"]))))))]
(is (= [] metadata))))))
clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/java/test_utilities.clj 0000664 0000000 0000000 00000010521 11765226254 0027055 0 ustar 00root root 0000000 0000000 ;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse Public
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;; be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other,
;; from this software.
;;
;; test_utilities.clj
;;
;; This namespace contains tests of all the utility functions within java.jdbc
;; and does not rely on any databases.
;;
;; scgilardi (gmail)
;; Created 13 September 2008
;;
;; seancorfield (gmail)
;; Migrated from clojure.contrib.test-sql 17 April 2011
(ns clojure.java.test-utilities
(:use clojure.test)
(:require [clojure.java.jdbc :as sql]))
;; basic tests for keyword / entity conversion
(deftest test-as-identifier
(is (= "kw" (sql/as-identifier "kw")))
(is (= "kw" (sql/as-identifier :kw)))
(is (= "KW" (sql/as-identifier "KW")))
(is (= "KW" (sql/as-identifier :KW))))
(deftest test-as-keyword
(is (= :kw (sql/as-keyword "kw")))
(is (= :kw (sql/as-keyword :kw)))
(is (= :kw (sql/as-keyword "KW")))
(is (= :KW (sql/as-keyword :KW))))
(deftest test-quoted
(is (= "kw" (sql/as-quoted-identifier [ \[ \] ] "kw")))
(is (= "[kw]" (sql/as-quoted-identifier [ \[ \] ] :kw)))
(is (= "KW" (sql/as-quoted-identifier \` "KW")))
(is (= "`KW`" (sql/as-quoted-identifier \` :KW))))
(def quote-dash { :entity (partial sql/as-quoted-str \`) :keyword #(.replace % "_" "-") })
(deftest test-named
(is (= "kw" (sql/as-named-identifier quote-dash "kw")))
(is (= "`kw`" (sql/as-named-identifier quote-dash :kw)))
(is (= :K-W (sql/as-named-keyword quote-dash "K_W")))
(is (= :K_W (sql/as-named-keyword quote-dash :K_W))))
(deftest test-with-quote
(sql/with-quoted-identifiers [ \[ \] ]
(is (= "kw" (sql/as-identifier "kw")))
(is (= "[kw]" (sql/as-identifier :kw)))
(is (= "KW" (sql/as-identifier "KW")))
(is (= "[KW]" (sql/as-identifier :KW)))))
(deftest test-with-naming
(sql/with-naming-strategy quote-dash
(is (= "kw" (sql/as-identifier "kw")))
(is (= "`kw`" (sql/as-identifier :kw)))
(is (= :K-W (sql/as-keyword "K_W")))
(is (= :K_W) (sql/as-keyword :K_W))))
(deftest test-print-update-counts
(let [bu-ex (java.sql.BatchUpdateException. (int-array [1 2 3]))]
(let [e (is (thrown? java.sql.BatchUpdateException (throw bu-ex)))
counts-str (with-out-str (sql/print-update-counts e))]
(is (re-find #"^Update counts" counts-str))
(is (re-find #"Statement 0: 1" counts-str))
(is (re-find #"Statement 2: 3" counts-str)))))
(deftest test-print-exception-chain
(let [base-ex (java.sql.SQLException. "Base Message" "Base State")
test-ex (java.sql.BatchUpdateException. "Test Message" "Test State" (int-array [1 2 3]))]
(.setNextException test-ex base-ex)
(let [e (is (thrown? java.sql.BatchUpdateException (throw test-ex)))
except-str (with-out-str (sql/print-sql-exception-chain e))
pattern (fn [s] (java.util.regex.Pattern/compile s java.util.regex.Pattern/DOTALL))]
(is (re-find (pattern "^BatchUpdateException:.*SQLException:") except-str))
(is (re-find (pattern "Message: Test Message.*Message: Base Message") except-str))
(is (re-find (pattern "SQLState: Test State.*SQLState: Base State") except-str)))))
(deftest test-make-name-unique
(let [make-name-unique @#'sql/make-name-unique]
(is (= "a" (make-name-unique '() "a" 1)))
(is (= "a_2" (make-name-unique '("a") "a" 1)))
(is (= "a_3" (make-name-unique '("a" "b" "a_2") "a" 1)))))
(deftest test-make-cols-unique
(let [make-cols-unique @#'sql/make-cols-unique]
(is (= '() (make-cols-unique '())))
(is (= '("a") (make-cols-unique '("a"))))
(is (= '("a" "a_2") (make-cols-unique '("a" "a"))))
(is (= '("a" "b" "a_2" "a_3") (make-cols-unique '("a" "b" "a" "a"))))
(is (= '("a" "b" "a_2" "b_2" "a_3" "b_3") (make-cols-unique '("a" "b" "a" "b" "a" "b"))))))
;; DDL tests
(deftest test-create-table-ddl
(is (= "CREATE TABLE table (col1 int, col2 int)"
(sql/create-table-ddl :table ["col1 int"] [:col2 :int])))
(is (= "CREATE TABLE table (col1 int, col2 int) ENGINE=MyISAM"
(sql/create-table-ddl :table [:col1 "int"] ["col2" :int] :table-spec "ENGINE=MyISAM"))))