pax_global_header00006660000000000000000000000064117652262540014524gustar00rootroot0000000000000052 comment=ffb284804d0fb56f86b2ea4a7c23d884702226c7 clojure-java.jdbc-ad7f7f6/000077500000000000000000000000001176522625400155155ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/.gitignore000066400000000000000000000002301176522625400175000ustar00rootroot00000000000000*.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.txt000066400000000000000000000112011176522625400173210ustar00rootroot00000000000000Changes 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.md000066400000000000000000000151441176522625400170010ustar00rootroot00000000000000clojure.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/000077500000000000000000000000001176522625400162625ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/doc/clojure/000077500000000000000000000000001176522625400177255ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/doc/clojure/java/000077500000000000000000000000001176522625400206465ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/000077500000000000000000000000001176522625400215505ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/doc/clojure/java/jdbc/ConnectionPooling.md000066400000000000000000000034111176522625400255200ustar00rootroot00000000000000# 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.md000066400000000000000000000102741176522625400242720ustar00rootroot00000000000000# 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.md000066400000000000000000000024411176522625400235040ustar00rootroot00000000000000# 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.md000066400000000000000000000162621176522625400235460ustar00rootroot00000000000000# 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.html000066400000000000000000000305361176522625400171720ustar00rootroot00000000000000 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.xml000066400000000000000000000042171176522625400170360ustar00rootroot00000000000000 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/000077500000000000000000000000001176522625400163045ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/main/000077500000000000000000000000001176522625400172305ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/main/clojure/000077500000000000000000000000001176522625400206735ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/main/clojure/clojure/000077500000000000000000000000001176522625400223365ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/main/clojure/clojure/java/000077500000000000000000000000001176522625400232575ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/main/clojure/clojure/java/jdbc.clj000066400000000000000000000732331176522625400246630ustar00rootroot00000000000000;; 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/000077500000000000000000000000001176522625400172635ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/test/clojure/000077500000000000000000000000001176522625400207265ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/000077500000000000000000000000001176522625400223715ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/java/000077500000000000000000000000001176522625400233125ustar00rootroot00000000000000clojure-java.jdbc-ad7f7f6/src/test/clojure/clojure/java/test_jdbc.clj000066400000000000000000000340561176522625400257550ustar00rootroot00000000000000;; 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.clj000066400000000000000000000105211176522625400270550ustar00rootroot00000000000000;; 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"))))