├── .github ├── workflows │ ├── test.yml │ ├── snapshot.yml │ ├── doc-build.yml │ └── release.yml └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── CONTRIBUTING.md ├── run-tests.sh ├── deps.edn ├── pom.xml ├── src ├── test │ └── clojure │ │ └── clojure │ │ └── java │ │ ├── jdbc │ │ └── utilities_test.clj │ │ └── jdbc_test.clj ├── main │ └── clojure │ │ └── clojure │ │ └── java │ │ └── jdbc │ │ ├── datafy.clj │ │ └── spec.clj └── perf │ └── clojure │ └── clojure │ └── java │ └── perf_jdbc.clj ├── LICENSE ├── epl.html ├── CHANGES.md └── README.md /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | call-test: 7 | uses: clojure/build.ci/.github/workflows/test.yml@master 8 | -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Snapshot on demand 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | call-snapshot: 7 | uses: clojure/build.ci/.github/workflows/snapshot.yml@master 8 | secrets: inherit 9 | -------------------------------------------------------------------------------- /.github/workflows/doc-build.yml: -------------------------------------------------------------------------------- 1 | name: Build API Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | call-doc-build-workflow: 8 | uses: clojure/build.ci/.github/workflows/doc-build.yml@master 9 | with: 10 | project: clojure/java.jdbc 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .calva/repl.calva-repl 2 | .classpath 3 | .clj-kondo/.cache 4 | .cpcache 5 | .lsp/.cache 6 | .portal 7 | .project 8 | .rebl 9 | .settings 10 | *.jar 11 | /.lein-failures 12 | /.lein-repl-history 13 | /.nrepl-port 14 | /build.boot 15 | /clojure_test_* 16 | bin 17 | classes 18 | derby.log 19 | settings.xml 20 | target 21 | test-all.sh 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This is a [Clojure contrib] project. 2 | 3 | Under the Clojure contrib [guidelines], this project cannot accept 4 | pull requests. All patches must be submitted via [JIRA]. 5 | 6 | See [Contributing] on the Clojure website for 7 | more information on how to contribute. 8 | 9 | [Clojure contrib]: https://clojure.org/community/contrib_libs 10 | [Contributing]: https://clojure.org/community/contributing 11 | [JIRA]: http://clojure.atlassian.net/browse/JDBC 12 | [guidelines]: https://clojure.org/community/contrib_howto 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release on demand 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseVersion: 7 | description: "Version to release" 8 | required: true 9 | snapshotVersion: 10 | description: "Snapshot version after release" 11 | required: true 12 | 13 | jobs: 14 | call-release: 15 | uses: clojure/build.ci/.github/workflows/release.yml@master 16 | with: 17 | releaseVersion: ${{ github.event.inputs.releaseVersion }} 18 | snapshotVersion: ${{ github.event.inputs.snapshotVersion }} 19 | secrets: inherit -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | Hi! Thanks for your interest in contributing to this project. 2 | 3 | Clojure contrib projects do not use GitHub issues or pull requests, and 4 | require a signed Contributor Agreement. If you would like to contribute, 5 | please read more about the CA and sign that first (this can be done online). 6 | 7 | Then go to this project's issue tracker in JIRA to create tickets, update 8 | tickets, or submit patches. For help in creating tickets and patches, 9 | please see: 10 | 11 | - Signing the CA: https://clojure.org/community/contributing 12 | - Creating Tickets: https://clojure.org/community/creating_tickets 13 | - Developing Patches: https://clojure.org/community/developing_patches 14 | - Contributing FAQ: https://clojure.org/community/contributing 15 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Run this shell script with a list of databases you want to test against: 4 | # 5 | # ./run-tests.sh mysql postgres 6 | # 7 | # The default list of derby h2 hsqldb sqlite are always tested against. 8 | # 9 | # Additionally you can specify: mssql jtds postgres pgsql mysql 10 | # 11 | # Alternatives to mysql (a db-spec) are mysql-str and mysql-jdbc-str which use 12 | # connection strings instead. You may only specify one of these! 13 | # 14 | # Note: for mysql, postgres, and pgsql, the tests assume a database schema 15 | # called clojure_test that is accesible by a user clojure_test with the 16 | # password # clojure_test (currently hardcoded in the tests, sorry!). 17 | # This will eventually change... 18 | # 19 | # For postgres or pgsql, you can set the following environment variables 20 | # to override the defaults of 127.0.0.1 and 5432: 21 | # 22 | # TEST_POSTGRES_HOST TEST_POSTGRES_PORT 23 | # 24 | # Currently you may only specify one of postgres or pgsql! 25 | # 26 | # For mssql, you can set the following environment variables to override the 27 | # defaults of 127.0.0.1\\SQLEXPRESS, 1433, clojure_test, sa, (empty string): 28 | # 29 | # TEST_MSSQL_HOST TEST_MSSQL_PORT TEST_MSSQL_NAME TEST_MSSQL_USER TEST_MSSQL_PASS 30 | # 31 | # For jtds, you can set the following environment variables (defaults per above): 32 | # 33 | # TEST_JTDS_HOST TEST_JTDS_PORT TEST_JTDS_NAME TEST_JTDS_USER TEST_JTDS_PASS 34 | # 35 | # For jtds you can just specify the IP address or hostname, you do not need 36 | # the \\SQLEXPRESS part. 37 | # 38 | # Note: if you specify both mssql and jtds, make sure they're pointing at 39 | # different database names or the tests will fail! 40 | # 41 | # Default set of databases to test: 42 | dbs="derby h2 hsqldb sqlite" 43 | 44 | # Start with clean databases each time to avoid slowdown 45 | rm -rf clojure_test_* 46 | 47 | versions="1.9 1.10 1.11 1.12" 48 | for v in $versions 49 | do 50 | TEST_DBS="$dbs $*" clj -M:test:runner:$v 51 | if test $? -ne 0 52 | then 53 | exit $? 54 | fi 55 | done 56 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | ;; You can run clojure.java.jdbc tests with: clj -A:test:runner 2 | ;; You can also specify an alias to select which version of Clojure to test 3 | ;; against: :1.9 :1.10 :1.11 :1.12 4 | 5 | {:paths ["src/main/clojure"] 6 | :aliases {:test 7 | {:extra-paths ["src/test/clojure"] 8 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 9 | org.apache.derby/derby {:mvn/version "10.14.2.0"} 10 | org.hsqldb/hsqldb$jdk8 {:mvn/version "2.7.2"} 11 | com.h2database/h2 {:mvn/version "1.4.197"} 12 | net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} 13 | ;; Note: Tests fail with 6.0.2+ driver 14 | mysql/mysql-connector-java {:mvn/version "5.1.41"} 15 | org.postgresql/postgresql {:mvn/version "42.7.3"} 16 | com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.9"} 17 | org.xerial/sqlite-jdbc {:mvn/version "3.45.2.0"} 18 | ;; Note: Assumes Java 8; there's a .jre11 version as well 19 | com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.6.1.jre8"}}} 20 | :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} 21 | :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} 22 | :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} 23 | :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}} 24 | :perf {:extra-paths ["src/perf/clojure"] 25 | :extra-deps {criterium/criterium {:mvn/version "0.4.6"}} 26 | :jvm-opts ["-server" 27 | "-Xmx4096m" 28 | "-Dclojure.compiler.direct-linking=true"]} 29 | :runner 30 | {:extra-deps {io.github.cognitect-labs/test-runner 31 | {:git/tag "v0.5.1" :git/sha "dfb30dd"}} 32 | :main-opts ["-m" "cognitect.test-runner" 33 | "-d" "src/test/clojure"]}}} 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | java.jdbc 5 | 0.7.13-SNAPSHOT 6 | java.jdbc 7 | JDBC utilities for Clojure. 8 | 9 | 10 | org.clojure 11 | pom.contrib 12 | 1.3.0 13 | 14 | 15 | 16 | 17 | Stephen C. Gilardi 18 | 19 | 20 | Sean Corfield 21 | 22 | 23 | 24 | 25 | scm:git:git@github.com:clojure/java.jdbc.git 26 | scm:git:git@github.com:clojure/java.jdbc.git 27 | git@github.com:clojure/java.jdbc.git 28 | HEAD 29 | 30 | 31 | 32 | 1.9.0 33 | 34 | 35 | 36 | 37 | 38 | 41 | com.theoryinpractise 42 | clojure-maven-plugin 43 | 1.7.1 44 | true 45 | 46 | ${clojure.warnOnReflection} 47 | true 48 | 49 | 50 | 51 | clojure-compile 52 | none 53 | 54 | 55 | clojure-test 56 | test 57 | 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | mysql 69 | mysql-connector-java 70 | 5.1.41 71 | test 72 | 73 | 74 | org.apache.derby 75 | derby 76 | 10.14.2.0 77 | test 78 | 79 | 80 | org.hsqldb 81 | hsqldb 82 | 2.7.2 83 | jdk8 84 | test 85 | 86 | 87 | com.h2database 88 | h2 89 | 1.4.197 90 | test 91 | 92 | 93 | org.postgresql 94 | postgresql 95 | 42.7.3 96 | test 97 | 98 | 99 | org.xerial 100 | sqlite-jdbc 101 | 3.45.2.0 102 | test 103 | 104 | 105 | net.sourceforge.jtds 106 | jtds 107 | 1.3.1 108 | test 109 | 110 | 111 | org.clojure 112 | test.check 113 | 1.1.1 114 | test 115 | 116 | 117 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/java/jdbc/utilities_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2008-2017 Sean Corfield, Stephen C. Gilardi. 2 | ;; All rights reserved. The use and 3 | ;; distribution terms for this software are covered by the Eclipse Public 4 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can 5 | ;; be found in the file epl-v10.html at the root of this distribution. By 6 | ;; using this software in any fashion, you are agreeing to be bound by the 7 | ;; terms of this license. You must not remove this notice, or any other, 8 | ;; from this software. 9 | ;; 10 | ;; test_utilities.clj 11 | ;; 12 | ;; This namespace contains tests of all the utility functions within java.jdbc 13 | ;; and does not rely on any databases. 14 | ;; 15 | ;; scgilardi (gmail) 16 | ;; Created 13 September 2008 17 | ;; 18 | ;; seancorfield (gmail) 19 | ;; Migrated from clojure.contrib.test-sql 17 April 2011 20 | 21 | (ns clojure.java.jdbc.utilities-test 22 | (:use clojure.test) 23 | (:require [clojure.java.jdbc :as sql])) 24 | 25 | (deftest test-print-update-counts 26 | (let [bu-ex (java.sql.BatchUpdateException. (int-array [1 2 3]))] 27 | (let [e (is (thrown? java.sql.BatchUpdateException (throw bu-ex))) 28 | counts-str (with-out-str (sql/print-update-counts e))] 29 | (is (re-find #"^Update counts" counts-str)) 30 | (is (re-find #"Statement 0: 1" counts-str)) 31 | (is (re-find #"Statement 2: 3" counts-str))))) 32 | 33 | (deftest test-print-exception-chain 34 | (let [base-ex (java.sql.SQLException. "Base Message" "Base State") 35 | test-ex (java.sql.BatchUpdateException. "Test Message" "Test State" (int-array [1 2 3]))] 36 | (.setNextException test-ex base-ex) 37 | (let [e (is (thrown? java.sql.BatchUpdateException (throw test-ex))) 38 | except-str (with-out-str (sql/print-sql-exception-chain e)) 39 | pattern (fn [s] (java.util.regex.Pattern/compile s java.util.regex.Pattern/DOTALL))] 40 | (is (re-find (pattern "^BatchUpdateException:.*SQLException:") except-str)) 41 | (is (re-find (pattern "Message: Test Message.*Message: Base Message") except-str)) 42 | (is (re-find (pattern "SQLState: Test State.*SQLState: Base State") except-str))))) 43 | 44 | (deftest test-make-cols-unique 45 | (let [make-cols-unique @#'sql/make-cols-unique] 46 | (is (= [] 47 | (into [] make-cols-unique []))) 48 | (is (= ["a"] 49 | (into [] make-cols-unique ["a"]))) 50 | (is (= ["a" "a_2"] 51 | (into [] make-cols-unique ["a" "a"]))) 52 | (is (= ["a" "b" "a_2" "a_3"] 53 | (into [] make-cols-unique ["a" "b" "a" "a"]))) 54 | (is (= ["a" "b" "a_2" "b_2" "a_3" "b_3"] 55 | (into [] make-cols-unique ["a" "b" "a" "b" "a" "b"]))))) 56 | 57 | ;; DDL tests 58 | 59 | (deftest test-create-table-ddl 60 | (is (= "CREATE TABLE THING (COL1 INT, COL2 int)" 61 | (sql/create-table-ddl :thing [["col1 int"] [:col2 :int]] 62 | {:entities clojure.string/upper-case}))) 63 | (is (= "CREATE TABLE THING (COL1 int, COL2 int) ENGINE=MyISAM" 64 | (sql/create-table-ddl :thing [[:col1 "int"] ["col2" :int]] 65 | {:table-spec "ENGINE=MyISAM" 66 | :entities clojure.string/upper-case})))) 67 | 68 | ;; since we have clojure.spec instrumentation enabled for Clojure 1.9.0 69 | ;; we need to account for the fact that we'll get different exceptions 70 | ;; for Clojure < 1.9.0 since the spec will trigger first and obscure 71 | ;; our own argument checking on 1.9.0+ 72 | 73 | (defn argument-exception? 74 | "Given a thunk, try to execute it and return true if it throws 75 | either an IllegalArgumentException or a Clojure exception from 76 | clojure.spec." 77 | [thunk] 78 | (try 79 | (thunk) 80 | false 81 | (catch IllegalArgumentException _ true) 82 | (catch clojure.lang.ExceptionInfo e 83 | (re-find #"did not conform to spec" (.getMessage e))))) 84 | 85 | (deftest test-invalid-create-table-ddl 86 | (is (argument-exception? (fn [] (sql/create-table-ddl :thing [[]])))) 87 | (is (argument-exception? (fn [] (sql/create-table-ddl :thing [[:col1 "int"] []])))) 88 | (is (argument-exception? (fn [] (sql/create-table-ddl :thing [:col1 "int"]))))) 89 | 90 | (deftest test-quoted 91 | (is (= "`x`" ((sql/quoted \`) "x"))) 92 | (is (= "[x]" ((sql/quoted [\[ \]]) "x"))) 93 | (is (= "`x`" ((sql/quoted :mysql) "x"))) 94 | (is (= "\"x\"" ((sql/quoted :ansi) "x"))) 95 | (is (= "\"x\"" ((sql/quoted :oracle) "x"))) 96 | (is (= "[x]" ((sql/quoted :sqlserver) "x")))) 97 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/java/jdbc/datafy.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2018 Sean Corfield. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by 3 | ;; the Eclipse Public License 1.0 4 | ;; (http://opensource.org/licenses/eclipse-1.0.php) which can be 5 | ;; found in the file epl-v10.html at the root of this distribution. 6 | ;; By using this software in any fashion, you are agreeing to be 7 | ;; bound by the terms of this license. You must not remove this 8 | ;; notice, or any other, from this software. 9 | ;; 10 | ;; datafy.clj 11 | ;; 12 | ;; Exploring datafy and nav for JDBC databases 13 | 14 | (ns 15 | ^{:author "Sean Corfield", 16 | :doc "Variants of 'query' functions from clojure.java.jdbc that support 17 | the new clojure.datafy functionality in Clojure 1.10. 18 | 19 | The whole schema/column lookup piece is very likely to change! 20 | 21 | Currently, the :schema option for a 'query' function is a mapping 22 | from column name to a tuple of table name, key column, and optionally 23 | the cardinality (:one -- the default -- or :many). The cardinality 24 | determines whether navigation should produce a single row (hash map) 25 | or a result set. 26 | 27 | One of the problems is that the general case -- query -- doesn't 28 | have any concept of an associated table name (and may of course 29 | join across multiple tables), so there's no good way to take the 30 | table name into account when mapping a column to another table. 31 | 32 | For find-by-keys and get-by-id, you do have the starting table 33 | name so you could map [table1 column1] to [table2 column2] and have 34 | table-specific mappings. 35 | 36 | The obvious, logical thing would be to use SQL metadata to figure 37 | out actual foreign key constraints but not everyone uses them, for 38 | a variety of reasons. For folks who do use them, they can build 39 | their schema structure from the database, and pass the relevant 40 | part of it to the functions below (via :schema in options)."} 41 | clojure.java.jdbc.datafy 42 | (:require [clojure.core.protocols :as p] 43 | [clojure.java.jdbc :as jdbc])) 44 | 45 | (declare datafy-result-set) 46 | (declare datafy-row) 47 | 48 | (defn- default-schema 49 | "The default schema lookup rule for column names. 50 | 51 | If a column name ends with _id or id, it is assumed to be a foreign key 52 | into the table identified by the first part of the column name." 53 | [col] 54 | (let [[_ table] (re-find #"^(.*?)_?id$" (name col))] 55 | (when table 56 | [(keyword table) :id]))) 57 | 58 | (defn- schema-opt 59 | "Returns the schema 'option'. 60 | 61 | As with other clojure.java.jdbc options, it can be provided in the db-spec 62 | hash map or in the options for a particular function call. 63 | 64 | A schema can a simple map from column names to pairs of table name and 65 | the key column to be used in that table. A schema can also be a function 66 | that is called with column names and should return nil if the column is 67 | not to be treated as a foreign key, or a pair of table name and the key 68 | column within that table." 69 | [db-spec opts] 70 | (:schema (merge {:schema default-schema} 71 | (when (map? db-spec) db-spec) 72 | opts))) 73 | 74 | (defn- navize-row [db-spec opts row] 75 | "Given a db-spec, a map of options, and a row -- a hash map -- return the 76 | row with metadata that provides navigation via foreign keys." 77 | (let [schema (schema-opt db-spec opts)] 78 | (with-meta row 79 | {`p/nav (fn [coll k v] 80 | (let [[table fk cardinality] (schema k)] 81 | (if fk 82 | (try 83 | (if (= :many cardinality) 84 | (datafy-result-set db-spec opts 85 | (jdbc/find-by-keys db-spec table {fk v})) 86 | (datafy-row db-spec opts 87 | (jdbc/get-by-id db-spec table v fk))) 88 | (catch Exception _ 89 | ;; assume an exception means we just cannot 90 | ;; navigate anywhere, so return just the value 91 | v)) 92 | v)))}))) 93 | 94 | (defn- datafy-row [db-spec opts row] 95 | "Given a db-spec, a map of options, and a row -- a hash map -- return the 96 | row with metadata that provides datafication (which in turn provides). 97 | navigation)." 98 | (with-meta row {`p/datafy (partial navize-row db-spec opts)})) 99 | 100 | (defn- datafy-result-set [db-spec opts rs] 101 | "Given a db-spec, a map of options, and a result set -- a sequence of hash 102 | maps that represent rows -- return a sequence of datafiable rows." 103 | (mapv (partial datafy-row db-spec opts) rs)) 104 | 105 | (defn get-by-id 106 | "Given a database connection, a table name, a primary key value, an 107 | optional primary key column name, and an optional options map, return 108 | a single matching row, or nil. 109 | The primary key column name defaults to :id." 110 | ([db table pk-value] (get-by-id db table pk-value :id {})) 111 | ([db table pk-value pk-name-or-opts] 112 | (if (map? pk-name-or-opts) 113 | (get-by-id db table pk-value :id pk-name-or-opts) 114 | (get-by-id db table pk-value pk-name-or-opts {}))) 115 | ([db table pk-value pk-name opts] 116 | (datafy-row db opts 117 | (jdbc/get-by-id db table pk-value pk-name opts)))) 118 | 119 | (defn find-by-keys 120 | "Given a database connection, a table name, a map of column name/value 121 | pairs, and an optional options map, return any matching rows. 122 | 123 | An :order-by option may be supplied to sort the rows, e.g., 124 | 125 | {:order-by [{:name :asc} {:age :desc} {:income :asc}]} 126 | ;; equivalent to: 127 | {:order-by [:name {:age :desc} :income]} 128 | 129 | The :order-by value is a sequence of column names (to sort in ascending 130 | order) and/or maps from column names to directions (:asc or :desc). The 131 | directions may be strings or keywords and are not case-sensitive. They 132 | are mapped to ASC or DESC in the generated SQL. 133 | 134 | Note: if a ordering map has more than one key, the order of the columns 135 | in the generated SQL ORDER BY clause is unspecified (so such maps should 136 | only contain one key/value pair)." 137 | ([db table columns] (find-by-keys db table columns {})) 138 | ([db table columns opts] 139 | (datafy-result-set db opts 140 | (jdbc/find-by-keys db table columns opts)))) 141 | 142 | (defn query 143 | "Given a database connection and a vector containing SQL and optional parameters, 144 | perform a simple database query. The options specify how to construct the result 145 | set (and are also passed to prepare-statement as needed): 146 | :as-arrays? - return the results as a set of arrays, default false. 147 | :identifiers - applied to each column name in the result set, default lower-case 148 | :keywordize? - defaults to true, can be false to opt-out of converting 149 | identifiers to keywords 150 | :qualifier - optionally provides the namespace qualifier for identifiers 151 | :result-set-fn - applied to the entire result set, default doall / vec 152 | if :as-arrays? true, :result-set-fn will default to vec 153 | if :as-arrays? false, :result-set-fn will default to doall 154 | :row-fn - applied to each row as the result set is constructed, default identity 155 | The second argument is a vector containing a SQL string or PreparedStatement, followed 156 | by any parameters it needs. 157 | See also prepare-statement for additional options." 158 | ([db sql-params] (query db sql-params {})) 159 | ([db sql-params opts] 160 | (datafy-result-set db opts 161 | (jdbc/query db sql-params opts)))) 162 | -------------------------------------------------------------------------------- /src/perf/clojure/clojure/java/perf_jdbc.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2017 Tommmi Reiman, Sean Corfield. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by 3 | ;; the Eclipse Public License 1.0 4 | ;; (http://opensource.org/licenses/eclipse-1.0.php) which can be 5 | ;; found in the file epl-v10.html at the root of this distribution. 6 | ;; By using this software in any fashion, you are agreeing to be 7 | ;; bound by the terms of this license. You must not remove this 8 | ;; notice, or any other, from this software. 9 | 10 | (ns clojure.java.perf-jdbc 11 | "Performance tests for parts of clojure.java.jdbc. 12 | 13 | Here's how to run these tests: 14 | 15 | $ clj -A:test:perf 16 | Clojure 1.9.0 17 | user=> (require '[clojure.java.perf-jdbc :as p]) 18 | nil 19 | user=> (p/calibrate) 20 | ... 21 | nil 22 | user=> (p/test-dummy) 23 | ... 24 | nil 25 | user=> (p/test-h2) 26 | ... 27 | nil 28 | user=> 29 | 30 | These test compare the raw performance (against an in-memory H2 database) 31 | for hand-crafted Java JDBC calls and various `query` and `reducible-query` 32 | calls." 33 | (:require [criterium.core :as cc] 34 | [clojure.java.jdbc :as sql]) 35 | (:import (java.sql Connection PreparedStatement ResultSet Statement ResultSetMetaData))) 36 | 37 | (defn calibrate [] 38 | ;; 840ms 39 | (cc/quick-bench (reduce + (take 10e6 (range))))) 40 | 41 | (def db 42 | "Note: loading this namespace creates a connection to the H2 database!" 43 | {:connection (sql/get-connection "jdbc:h2:mem:test_mem")}) 44 | 45 | (defn create-table! [db] 46 | (sql/db-do-commands 47 | db (sql/create-table-ddl 48 | :fruit 49 | [[:id :int "DEFAULT 0"] 50 | [:name "VARCHAR(32)" "PRIMARY KEY"] 51 | [:appearance "VARCHAR(32)"] 52 | [:cost :int] 53 | [:grade :real]] 54 | {:table-spec ""}))) 55 | 56 | (defn- drop-table! [db] 57 | (doseq [table [:fruit :fruit2 :veggies :veggies2]] 58 | (try 59 | (sql/db-do-commands db (sql/drop-table-ddl table)) 60 | (catch java.sql.SQLException _)))) 61 | 62 | (defn add-stuff! [db] 63 | (sql/insert-multi! db 64 | :fruit 65 | nil 66 | [[1 "Apple" "red" 59 87] 67 | [2 "Banana" "yellow" 29 92.2] 68 | [3 "Peach" "fuzzy" 139 90.0] 69 | [4 "Orange" "juicy" 89 88.6]])) 70 | 71 | (def dummy-con 72 | (reify 73 | Connection 74 | (createStatement [_] 75 | (reify 76 | Statement 77 | (addBatch [_ _]))) 78 | (prepareStatement [_ _] 79 | (reify 80 | PreparedStatement 81 | (setObject [_ _ _]) 82 | (setString [_ _ _]) 83 | (close [_]) 84 | (executeQuery [_] 85 | (reify 86 | ResultSet 87 | (getMetaData [_] 88 | (reify 89 | ResultSetMetaData 90 | (getColumnCount [_] 1) 91 | (getColumnLabel [_ _] "name"))) 92 | (next [_] true) 93 | (close [_]) 94 | (^Object getObject [_ ^int s] 95 | "Apple") 96 | (^Object getObject [_ ^String s] 97 | "Apple") 98 | (^String getString [_ ^String s] 99 | "Apple"))))))) 100 | 101 | (defn select [db] 102 | (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"] 103 | {:row-fn :name :result-set-fn first})) 104 | 105 | (defn select-p [db ps] 106 | (sql/query db [ps "red"] 107 | {:row-fn :name :result-set-fn first})) 108 | 109 | (defn select* [^Connection con] 110 | (let [ps (doto (.prepareStatement con "SELECT * FROM fruit WHERE appearance = ?") 111 | (.setObject 1 "red")) 112 | rs (.executeQuery ps) 113 | _ (.next rs) 114 | value (.getObject rs "name")] 115 | (.close ps) 116 | value)) 117 | 118 | (defn test-dummy [] 119 | (do 120 | (let [db {:connection dummy-con}] 121 | (assert (= "Apple" (select db))) 122 | ;(time (dotimes [_ 100000] (select db))) 123 | 124 | ; 3.029268 ms (3030 ns) 125 | (cc/quick-bench (dotimes [_ 1000] (select db)))) 126 | 127 | (let [con dummy-con] 128 | (assert (= "Apple" (select* con))) 129 | ;(time (dotimes [_ 100000] (select* con))) 130 | 131 | ; 716.661522 ns (0.7ns) -> 4300x faster 132 | (cc/quick-bench (dotimes [_ 1000] (select* con)))))) 133 | 134 | (defn test-h2 [] 135 | (do 136 | (drop-table! db) 137 | (create-table! db) 138 | (add-stuff! db) 139 | 140 | (println "Basic select...") 141 | (let [db db] 142 | (assert (= "Apple" (select db))) 143 | (cc/quick-bench (select db))) 144 | 145 | (println "Basic select first rs...") 146 | (let [db db] 147 | (cc/quick-bench (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"] 148 | {:result-set-fn first :qualifier "fruit"}))) 149 | 150 | (println "Select with prepared statement...") 151 | (let [con (:connection db)] 152 | (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] 153 | (assert (= "Apple" (select-p db ps))) 154 | (cc/quick-bench (select-p db ps)))) 155 | 156 | (println "Reducible query...") 157 | (let [db db 158 | rq (sql/reducible-query db ["SELECT * FROM fruit WHERE appearance = ?" "red"])] 159 | (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) 160 | nil rq))) 161 | (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) 162 | nil rq))) 163 | 164 | (println "Reducible query with prepared statement...") 165 | (let [con (:connection db)] 166 | (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] 167 | (let [rq (sql/reducible-query db [ps "red"])] 168 | (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) 169 | nil rq))) 170 | (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) 171 | nil rq))))) 172 | 173 | (println "Reducible query with prepared statement and simple identifiers...") 174 | (let [con (:connection db)] 175 | (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] 176 | (let [rq (sql/reducible-query db [ps "red"] 177 | {:keywordize? false :identifiers identity})] 178 | (assert (= "Apple" (reduce (fn [_ row] (reduced (get row "NAME"))) 179 | nil rq))) 180 | (cc/quick-bench (reduce (fn [_ row] (reduced (get row "NAME"))) 181 | nil rq))))) 182 | 183 | (println "Reducible query with prepared statement and raw result set...") 184 | (let [con (:connection db)] 185 | (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] 186 | (let [rq (sql/reducible-query db [ps "red"] {:raw? true})] 187 | (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) 188 | nil rq))) 189 | (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) 190 | nil rq))))) 191 | 192 | (println "Reducible query with raw result set...") 193 | (let [db db 194 | rq (sql/reducible-query 195 | db 196 | ["SELECT * FROM fruit WHERE appearance = ?" "red"] 197 | {:raw? true})] 198 | (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) 199 | nil rq))) 200 | (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) 201 | nil rq))) 202 | 203 | (println "Repeated reducible query with raw result set...") 204 | (let [db db] 205 | (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) 206 | nil (sql/reducible-query 207 | db 208 | ["SELECT * FROM fruit WHERE appearance = ?" "red"] 209 | {:raw? true})))) 210 | (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) 211 | nil (sql/reducible-query 212 | db 213 | ["SELECT * FROM fruit WHERE appearance = ?" "red"] 214 | {:raw? true})))) 215 | 216 | (println "Raw Java...") 217 | (let [con (:connection db)] 218 | (assert (= "Apple" (select* con))) 219 | (cc/quick-bench (select* con))))) 220 | 221 | (comment 222 | (calibrate) 223 | (test-dummy) 224 | (test-h2) 225 | ;; The following are just some things I was double-checking while adding 226 | ;; to Tommi's original tests -- Sean. 227 | ;; Shows the row is a reify instance, but you can select by key: 228 | (reduce (fn [_ row] (println row) (reduced (:name row))) 229 | nil (sql/reducible-query 230 | db 231 | ["SELECT * FROM fruit WHERE appearance = ?" "red"] 232 | {:raw? true})) 233 | ;; Shows you can construct a map result using select-keys: 234 | (reduce (fn [_ row] (reduced (select-keys row [:cost]))) 235 | nil (sql/reducible-query 236 | db 237 | ["SELECT * FROM fruit WHERE appearance = ?" "red"] 238 | {:raw? true})) 239 | ;; Shows you can reconstruct an entire result set (with very little overhead): 240 | (transduce (map #(select-keys % [:id :name :cost :appearance :grade])) 241 | conj [] (sql/reducible-query 242 | db 243 | "SELECT * FROM fruit" 244 | {:raw? true}))) 245 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | 205 | 206 | -------------------------------------------------------------------------------- /epl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/java/jdbc/spec.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2016-2019 Sean Corfield. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by 3 | ;; the Eclipse Public License 1.0 4 | ;; (http://opensource.org/licenses/eclipse-1.0.php) which can be 5 | ;; found in the file epl-v10.html at the root of this distribution. 6 | ;; By using this software in any fashion, you are agreeing to be 7 | ;; bound by the terms of this license. You must not remove this 8 | ;; notice, or any other, from this software. 9 | ;; 10 | ;; jdbc/spec.clj 11 | ;; 12 | ;; Optional specifications for clojure.java.jdbc 13 | 14 | (ns ^{:author "Sean Corfield" 15 | :doc "Optional specifications for use with Clojure 1.9 or later."} 16 | clojure.java.jdbc.spec 17 | (:require [clojure.spec.alpha :as s] 18 | [clojure.java.jdbc :as sql])) 19 | 20 | (set! *warn-on-reflection* true) 21 | 22 | ;; basic java.sql types -- cannot be generated! 23 | 24 | (s/def ::connection #(instance? java.sql.Connection %)) 25 | (s/def ::datasource #(instance? javax.sql.DataSource %)) 26 | (s/def ::prepared-statement #(instance? java.sql.PreparedStatement %)) 27 | (s/def ::result-set #(instance? java.sql.ResultSet %)) 28 | (s/def ::result-set-metadata #(instance? java.sql.ResultSetMetaData %)) 29 | (s/def ::uri #(instance? java.net.URI %)) 30 | 31 | ;; database specification (connection description) 32 | 33 | (s/def ::subprotocol-base #{"derby" "h2" "h2:mem" "hsqldb" "jtds:sqlserver" "mysql" 34 | "oracle:oci" "oracle:thin" "pgsql" "postgresql" 35 | "redshift" "sqlite" "sqlserver"}) 36 | (s/def ::subprotocol-alias #{"hsql" "jtds" "mssql" "oracle" "postgres"}) 37 | ;; technically :subprotocol can be any string... 38 | (s/def ::subprotocol string?) 39 | ;; ...but :dbtype must be a recognizable database type 40 | (s/def ::dbtype (s/or :alias ::subprotocol-alias 41 | :name ::subprotocol-base)) 42 | (s/def ::dbname string?) 43 | ;; usually IP address or domain name but could be more general string 44 | ;; (e.g., it could be username:password@domain.name) 45 | (s/def ::host string?) 46 | ;; usually a numeric port number, but could be an arbitrary string if 47 | ;; the specified database accepts URIs constructed that way 48 | (s/def ::port (s/or :port pos-int? 49 | :s string?)) 50 | (s/def ::subname string?) 51 | ;; will be a valid Java classname (including package) 52 | (s/def ::classname string?) 53 | (s/def ::factory (s/fspec :args (s/cat :db-spec ::db-spec) 54 | :ret ::connection)) 55 | (s/def ::user string?) 56 | (s/def ::username ::user) ; an alias 57 | (s/def ::password string?) 58 | (s/def ::name string?) 59 | (s/def ::environment (s/nilable map?)) 60 | ;; raw connection-uri 61 | (s/def ::connection-uri string?) 62 | 63 | (s/def ::db-spec-connection (s/keys :req-un [::connection])) 64 | (s/def ::db-spec-friendly (s/keys :req-un [::dbtype ::dbname] 65 | :opt-un [::host ::port ::user ::password 66 | ::classname])) 67 | (s/def ::db-spec-raw (s/keys :req-un [::connection-uri] 68 | :opt-un [::user ::password])) 69 | (s/def ::db-spec-driver-manager (s/keys :req-un [::subprotocol ::subname] 70 | :opt-un [::classname ::user ::password])) 71 | (s/def ::db-spec-factory (s/keys :req-un [::factory])) 72 | (s/def ::db-spec-data-source (s/keys :req-un [::datasource] 73 | :opt-un [::username ::user ::password])) 74 | (s/def ::db-spec-jndi (s/keys :req-un [::name] 75 | :opt-un [::environment])) 76 | (s/def ::db-spec-string string?) 77 | (s/def ::db-spec-uri ::uri) 78 | (s/def ::db-spec (s/or :connection ::db-spec-connection 79 | :friendly ::db-spec-friendly 80 | :raw ::db-spec-raw 81 | :driver-mgr ::db-spec-driver-manager 82 | :factory ::db-spec-factory 83 | :datasource ::db-spec-data-source 84 | :jndi ::db-spec-jndi 85 | :uri-str ::db-spec-string 86 | :uri-obj ::db-spec-uri)) 87 | 88 | ;; naming 89 | 90 | (s/def ::entity string?) 91 | 92 | (s/def ::identifier (s/or :kw keyword? :s string?)) 93 | 94 | ;; SQL and parameters 95 | 96 | (s/def ::sql-stmt (s/or :sql string? :stmt ::prepared-statement)) 97 | 98 | (s/def ::sql-value any?) ;; for now 99 | 100 | (s/def ::sql-params (s/or :sql ::sql-stmt 101 | :sql-params (s/cat :sql ::sql-stmt :params (s/* ::sql-value)))) 102 | 103 | (s/def ::where-clause (s/cat :where string? :params (s/* ::sql-value))) 104 | 105 | ;; results 106 | 107 | (s/def ::execute-result (s/* integer?)) 108 | 109 | ;; specific options that can be passed 110 | ;; a few of them are nilable, where the functions either pass a possibly nil 111 | ;; version of the option to a called function, but most are not nilable because 112 | ;; the corresponding options must either be omitted or given valid values 113 | 114 | (s/def ::as-arrays? (s/or :as-is #{:cols-as-is} :truthy (s/nilable boolean?))) 115 | (s/def ::auto-commit? boolean?) 116 | (s/def ::concurrency (set (keys @#'sql/result-set-concurrency))) 117 | (s/def ::cursors (set (keys @#'sql/result-set-holdability))) 118 | (s/def ::fetch-size nat-int?) 119 | ;; note the asymmetry here: the identifiers function converts a SQL entity to 120 | ;; an identifier (a symbol or a string), whereas the entities function converts 121 | ;; a string (not an identifier) to a SQL entity; SQL entities are always strings 122 | ;; but whilst java.jdbc lets you produce a keyword from identifiers, it does not 123 | ;; assume that entities can accept keywords! 124 | (s/def ::identifiers (s/fspec :args (s/cat :s ::entity) 125 | :ret ::identifier)) 126 | (s/def ::isolation (set (keys @#'sql/isolation-levels))) 127 | (s/def ::entities (s/fspec :args (s/cat :s string?) 128 | :ret ::entity)) 129 | (s/def ::keywordize? boolean?) 130 | (s/def ::max-size nat-int?) 131 | (s/def ::multi? boolean?) 132 | ;; strictly speaking we accept any keyword or string whose upper case name 133 | ;; is either ASC or DESC so this spec is overly restrictive; the :id-dir 134 | ;; can actually be an empty map although that is not very useful 135 | (s/def ::direction #{:asc :desc "asc" "desc" "ASC" "DESC"}) 136 | (s/def ::column-direction (s/or :id ::identifier 137 | :id-dir (s/map-of ::identifier ::direction))) 138 | (s/def ::order-by (s/coll-of ::column-direction)) 139 | (s/def ::qualifier (s/nilable string?)) 140 | ;; cannot generate a result set so we can't specify this yet 141 | #_(s/def ::read-columns (s/fspec :args (s/cat :rs ::result-set 142 | :rsmeta ::result-set-metadata 143 | :idxs (s/coll-of pos-int?)) 144 | :ret (s/coll-of any?))) 145 | (s/def ::read-columns fn?) 146 | (s/def ::read-only? boolean?) 147 | ;; there's not much we can say about result-set-fn -- it accepts a collection of 148 | ;; transformed rows (from row-fn), and it produces whatever it wants 149 | (s/def ::result-set-fn (s/fspec :args (s/cat :rs (s/coll-of any?)) 150 | :ret any?)) 151 | (s/def ::result-type (set (keys @#'sql/result-set-type))) 152 | ;; there's not much we can say about row-fn -- it accepts a row from a ResultSet 153 | ;; which is a map of keywords to SQL values, and it produces whatever it wants 154 | (s/def ::row-fn (s/fspec :args (s/cat :row (s/map-of keyword? ::sql-value)) 155 | :ret any?)) 156 | (s/def ::return-keys (s/or :columns (s/coll-of ::entity :kind vector?) 157 | :boolean boolean?)) 158 | (s/def ::table-spec string?) 159 | (s/def ::timeout nat-int?) 160 | (s/def ::transaction? boolean?) 161 | (s/def ::explain? (s/or :b boolean? :s string?)) 162 | (s/def ::explain-fn fn?) 163 | (s/def ::conditional? (s/or :b boolean? :s string? :f fn?)) 164 | 165 | ;; various types of options 166 | 167 | (s/def ::exec-sql-options (s/keys :req-un [] :opt-un [::entities ::transaction?])) 168 | 169 | (s/def ::execute-options (s/keys :req-un [] :opt-un [::transaction? ::multi? 170 | ::return-keys])) 171 | 172 | (s/def ::find-by-keys-options (s/keys :req-un [] 173 | :opt-un [::entities ::order-by 174 | ::result-set-fn ::row-fn 175 | ::identifiers ::qualifier 176 | ::keywordize? 177 | ::as-arrays? ::read-columns])) 178 | 179 | (s/def ::connection-options (s/keys :req-un [] 180 | :opt-un [::auto-commit? ::read-only?])) 181 | 182 | (s/def ::prepare-options (s/merge (s/keys :req-un [] 183 | :opt-un [::return-keys ::result-type 184 | ::concurrency ::cursors ::fetch-size 185 | ::max-rows ::timeout]) 186 | ::connection-options)) 187 | 188 | (s/def ::transaction-options (s/keys :req-un [] 189 | :opt-un [::isolation ::read-only?])) 190 | 191 | (s/def ::query-options (s/merge (s/keys :req-un [] 192 | :opt-un [::result-set-fn ::row-fn 193 | ::identifiers ::qualifier 194 | ::keywordize? 195 | ::as-arrays? ::read-columns]) 196 | ::prepare-options)) 197 | 198 | (s/def ::reducible-query-options (s/merge (s/keys :req-un [] 199 | :opt-un [::identifiers 200 | ::keywordize? 201 | ::qualifier 202 | ::read-columns]) 203 | ::prepare-options)) 204 | 205 | ;; the function API 206 | 207 | (s/def ::naming-strategy (s/fspec :args (s/cat :x ::identifier) 208 | :ret ::identifier)) 209 | 210 | (s/fdef sql/as-sql-name 211 | :args (s/cat :f ::naming-strategy :x ::identifier) 212 | :ret ::identifier) 213 | 214 | (s/def ::delimiter (s/or :s string? :c char?)) 215 | (s/fdef sql/quoted 216 | :args (s/cat :q (s/or :pair (s/coll-of ::delimiter 217 | :kind vector? :count 2) 218 | :delimiter ::delimiter 219 | :dialect #{:ansi :mysql :sqlserver :oracle})) 220 | :ret ::naming-strategy) 221 | 222 | (s/fdef sql/get-connection 223 | :args (s/cat :db-spec ::db-spec 224 | :opts (s/? ::connection-options)) 225 | :ret ::connection) 226 | 227 | (s/fdef sql/result-set-seq 228 | :args (s/cat :rs ::result-set 229 | :opts (s/? ::query-options)) 230 | :ret any?) 231 | 232 | (s/fdef sql/prepare-statement 233 | :args (s/cat :con ::connection 234 | :sql string? 235 | :opts (s/? ::prepare-options)) 236 | :ret ::prepared-statement) 237 | 238 | ;; print-sql-exception, print-sql-exception-chain, print-update-counts 239 | 240 | (s/fdef sql/db-find-connection 241 | :args (s/cat :db-spec ::db-spec) 242 | :ret (s/nilable ::connection)) 243 | 244 | (s/fdef sql/db-connection 245 | :args (s/cat :db-spec ::db-spec) 246 | :ret ::connection) 247 | 248 | ;; transaction functions 249 | 250 | (s/fdef sql/db-set-rollback-only! 251 | :args (s/cat :db ::db-spec)) 252 | 253 | (s/fdef sql/db-unset-rollback-only! 254 | :args (s/cat :db ::db-spec)) 255 | 256 | (s/fdef sql/db-is-rollback-only 257 | :args (s/cat :db ::db-spec) 258 | :ret boolean?) 259 | 260 | (s/fdef sql/get-isolation-level 261 | :args (s/cat :db ::db-spec) 262 | :ret (s/nilable (s/or :isolation ::isolation 263 | :unknown #{:unknown}))) 264 | 265 | (s/fdef sql/db-transaction* 266 | :args (s/cat :db ::db-spec 267 | :func ifn? 268 | :opts (s/? ::transaction-options)) 269 | :ret any?) 270 | 271 | (s/def ::transaction-binding (s/spec (s/cat :t-con simple-symbol? 272 | :db-spec any? 273 | :opts (s/? any?)))) 274 | 275 | (s/fdef sql/with-db-transaction 276 | :args (s/cat :binding ::transaction-binding 277 | :body (s/* any?))) 278 | 279 | (s/def ::connection-binding (s/spec (s/cat :con-db simple-symbol? 280 | :db-spec any? 281 | :opts (s/? any?)))) 282 | 283 | (s/fdef sql/with-db-connection 284 | :args (s/cat :binding ::connection-binding 285 | :body (s/* any?))) 286 | 287 | (s/fdef sql/with-db-metadata 288 | :args (s/cat :binding ::connection-binding 289 | :body (s/* any?))) 290 | 291 | (s/fdef sql/metadata-result 292 | :args (s/cat :rs-or-value any? 293 | :opts (s/? ::query-options)) 294 | :ret any?) 295 | 296 | (s/fdef sql/metadata-query 297 | :args (s/cat :meta-query any? 298 | :opt-args (s/? any?))) 299 | 300 | (s/fdef sql/db-do-commands 301 | :args (s/cat :db ::db-spec 302 | :transaction? (s/? boolean?) 303 | :sql-commands (s/or :command string? 304 | :commands (s/coll-of string?))) 305 | :ret any?) 306 | 307 | (s/fdef sql/db-do-prepared-return-keys 308 | :args (s/cat :db ::db-spec 309 | :transaction? (s/? boolean?) 310 | :sql-params ::sql-params 311 | :opts (s/? (s/merge ::execute-options ::query-options))) 312 | :ret any?) 313 | 314 | (s/fdef sql/db-do-prepared 315 | :args (s/cat :db ::db-spec 316 | :transaction? (s/? boolean?) 317 | :sql-params ::sql-params 318 | ;; TODO: this set of options needs reviewing: 319 | :opts (s/? (s/merge ::execute-options ::query-options))) 320 | :ret any?) 321 | 322 | (s/fdef sql/db-query-with-resultset 323 | :args (s/cat :db ::db-spec 324 | :sql-params ::sql-params 325 | :func ifn? 326 | :opts (s/? ::query-options)) 327 | :ret any?) 328 | 329 | (s/fdef sql/query 330 | :args (s/cat :db ::db-spec 331 | :sql-params ::sql-params 332 | :opts (s/? (s/merge ::query-options 333 | (s/keys :req-un [] 334 | :opt-un [::explain? 335 | ::explain-fn])))) 336 | :ret any?) 337 | 338 | (s/fdef sql/reducible-result-set 339 | :args (s/cat :rs ::result-set 340 | :opts (s/? ::reducible-query-options)) 341 | :ret any?) 342 | 343 | (s/fdef sql/reducible-query 344 | :args (s/cat :db ::db-spec 345 | :sql-params ::sql-params 346 | :opts (s/? ::reducible-query-options)) 347 | :ret any?) 348 | 349 | (s/fdef sql/find-by-keys 350 | :args (s/cat :db ::db-spec 351 | :table ::identifier 352 | :columns (s/map-of ::identifier ::sql-value) 353 | :opts (s/? ::find-by-keys-options)) 354 | :ret any?) 355 | 356 | (s/fdef sql/get-by-id 357 | :args (s/cat :db ::db-spec 358 | :table ::identifier 359 | :pk-value ::sql-value 360 | :opt-args (s/cat :pk-name (s/? ::identifier) 361 | :opts (s/? ::find-by-keys-options))) 362 | :ret any?) 363 | 364 | (s/fdef sql/execute! 365 | :args (s/cat :db ::db-spec 366 | :sql-params ::sql-params 367 | :opts (s/? ::execute-options)) 368 | :ret (s/or :rows ::execute-result 369 | :keys (s/coll-of map?))) 370 | 371 | (s/fdef sql/delete! 372 | :args (s/cat :db ::db-spec 373 | :table ::identifier 374 | :where-clause (s/spec ::where-clause) 375 | :opts (s/? ::exec-sql-options)) 376 | :ret ::execute-result) 377 | 378 | (s/fdef sql/insert! 379 | :args (s/or :row (s/cat :db ::db-spec 380 | :table ::identifier 381 | :row (s/map-of ::identifier any?) 382 | :opts (s/? (s/merge ::execute-options ::query-options))) 383 | :cvs (s/cat :db ::db-spec 384 | :table ::identifier 385 | :cols (s/nilable (s/coll-of ::identifier)) 386 | :vals (s/coll-of any?) 387 | :opts (s/? (s/merge ::execute-options ::query-options)))) 388 | :ret any?) 389 | 390 | (s/fdef sql/insert-multi! 391 | :args (s/or :rows (s/cat :db ::db-spec 392 | :table ::identifier 393 | :rows (s/coll-of (s/map-of ::identifier any?)) 394 | :opts (s/? (s/merge ::execute-options ::query-options))) 395 | :cvs (s/cat :db ::db-spec 396 | :table ::identifier 397 | :cols (s/nilable (s/coll-of ::identifier)) 398 | :vals (s/coll-of (s/coll-of any?)) 399 | :opts (s/? (s/merge ::execute-options ::query-options)))) 400 | :ret any?) 401 | 402 | (s/fdef sql/update! 403 | :args (s/cat :db ::db-spec 404 | :table ::identifier 405 | :set-map (s/map-of ::identifier ::sql-value) 406 | :where-clause (s/spec ::where-clause) 407 | :opts (s/? ::exec-sql-options)) 408 | :ret ::execute-result) 409 | 410 | (s/def ::column-spec (s/cat :col ::identifier :spec (s/* (s/or :kw keyword? 411 | :str string? 412 | :num number?)))) 413 | 414 | (s/fdef sql/create-table-ddl 415 | :args (s/cat :table ::identifier 416 | :specs (s/coll-of ::column-spec) 417 | :opts (s/? (s/keys :req-un [] 418 | :opt-un [::entities 419 | ::conditional? 420 | ::table-spec]))) 421 | 422 | :ret string?) 423 | 424 | (s/fdef sql/drop-table-ddl 425 | :args (s/cat :table ::identifier 426 | :opts (s/? (s/keys :req-un [] 427 | :opt-un [::entities 428 | ::conditional?]))) 429 | :ret string?) 430 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changes not yet released 2 | * Update most testing dependencies (and update a couple of tests to match). 3 | * Drop support for Clojure 1.7 & 1.8. Test against 1.9, 1.10, 1.11, and 1.12. 4 | 5 | Changes in 0.7.12 6 | 7 | * Make the protocols `ISQLValue`, `ISQLParameter`, and `IResultSetReadColumn` extensible via metadata. 8 | 9 | Changes in 0.7.11 10 | 11 | * Address edge case in transaction rollback failure [JDBC-179](https://clojure.atlassian.net/browse/JDBC-179). 12 | 13 | Changes in 0.7.10 14 | 15 | * Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. 16 | * Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). 17 | * Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://clojure.atlassian.net/browse/JDBC-177). 18 | 19 | Changes in 0.7.9 20 | 21 | * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://clojure.atlassian.net/browse/JDBC-176). 22 | * Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://clojure.atlassian.net/browse/JDBC-175). 23 | * Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://clojure.atlassian.net/browse/JDBC-174). 24 | * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://clojure.atlassian.net/browse/JDBC-173). 25 | 26 | Changes in 0.7.8 27 | 28 | * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://clojure.atlassian.net/browse/JDBC-172). 29 | * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://clojure.atlassian.net/browse/JDBC-171). 30 | 31 | Changes in 0.7.7 32 | 33 | * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://clojure.atlassian.net/browse/JDBC-169). 34 | * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). 35 | 36 | Changes in 0.7.6 37 | 38 | * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://clojure.atlassian.net/browse/JDBC-166). 39 | * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). 40 | * Add missing spec for `db-spec` being a `java.net.URI` object. 41 | * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). 42 | * Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://clojure.atlassian.net/browse/JDBC-165). 43 | * Update tests so they work properly with string `db-spec` test databases. 44 | * Ensure no reflection warnings are present. 45 | * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". 46 | 47 | Changes in 0.7.5 48 | 49 | * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://clojure.atlassian.net/browse/JDBC-163). 50 | 51 | Changes in 0.7.4 52 | 53 | * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://clojure.atlassian.net/browse/JDBC-160). 54 | * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. 55 | * Performance improvements, primarily in `query` and `reducible-query`. 56 | * Experimental `:raw?` result set handling in `reducible-query`. 57 | * `modify-connection` is more robust in the face of `null` connections and bad option values. 58 | 59 | Changes in 0.7.3 60 | 61 | * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://clojure.atlassian.net/browse/JDBC-159). 62 | * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://clojure.atlassian.net/browse/JDBC-158). 63 | 64 | Changes in 0.7.2 65 | 66 | * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://clojure.atlassian.net/browse/JDBC-156). 67 | * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. 68 | * Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. 69 | 70 | Changes in 0.7.1 71 | 72 | * Connection strings with empty values were not parsed correctly [JDBC-155](https://clojure.atlassian.net/browse/JDBC-155). 73 | 74 | Changes in 0.7.0 75 | 76 | * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). 77 | * Add better support for Oracle connections (default port to `1521`, support `:dbtype "oracle"` -- as `"oracle:thin"` -- and `:dbtype "oracle:oci"`, with `@` instead of `//` before host). 78 | 79 | Changes in 0.7.0-beta5 80 | 81 | * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://clojure.atlassian.net/browse/JDBC-153). 82 | * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. 83 | 84 | Changes in 0.7.0-beta4 85 | 86 | * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. 87 | * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. 88 | 89 | Changes in 0.7.0-beta3 90 | 91 | * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://clojure.atlassian.net/browse/JDBC-152). 92 | 93 | Changes in 0.7.0-beta2 94 | 95 | * Support for Clojure 1.6.0 and earlier has been dropped -- breaking change. 96 | * Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! 97 | * All public functions now have specs in the optional `clojure.java.jdbc.spec` namespace (requires `clojure.spec.alpha`). 98 | * `reducible-query` and `reducible-result-set` use `IReduce` and correctly support the no-`init` arity of `reduce` by using the first row of the `ResultSet`, if present, as the (missing) `init` value, and only calling `f` with no arguments if the `ResultSet` is empty. The `init` arity of `reduce` only ever calls `f` with two arguments. 99 | 100 | Changes in 0.7.0-beta1 101 | 102 | * Support for Clojure 1.4.0 has been dropped -- breaking change. 103 | * Optional spec support now uses `clojure.spec.alpha`. 104 | * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://clojure.atlassian.net/browse/JDBC-99). 105 | 106 | Changes in 0.7.0-alpha3 107 | 108 | * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](https://clojure.atlassian.net/browse/JDBC-151). 109 | * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. 110 | 111 | Changes in 0.7.0-alpha2 112 | 113 | * `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](https://clojure.atlassian.net/browse/JDBC-150). 114 | * `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](https://clojure.atlassian.net/browse/JDBC-149). 115 | * Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](https://clojure.atlassian.net/browse/JDBC-148). 116 | * Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](https://clojure.atlassian.net/browse/JDBC-145). 117 | 118 | Changes in 0.7.0-alpha1 -- potentially breaking changes 119 | 120 | * The signatures of `as-sql-name` and `quoted` have changed slightly: the former no longer has the curried (single argument) version, and the latter no longer has the two argument version. This change came out of a discussion on Slack which indicated curried functions are non-idiomatic. If you relied on the curried version of `as-sql-name`, you will not need to use `partial`. If you relied on the two argument version of `quoted`, you will need to add an extra `( )` for the one argument call. I'd be fairly surprised if anyone is using `as-sql-name` at all since it is really an implementation detail. I'd also be surprised if anyone was using the two argument version of `quoted` since the natural usage is `:entities (quoted [\[ \]])` to create a naming strategy (that provides SQL entity quoting). 121 | * Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](https://clojure.atlassian.net/browse/JDBC-147). 122 | * All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](https://clojure.atlassian.net/browse/JDBC-144). 123 | * Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](https://clojure.atlassian.net/browse/JDBC-141). 124 | * Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](https://clojure.atlassian.net/browse/JDBC-137). 125 | * Expanded optional `clojure.spec` coverage to almost the whole library API. 126 | 127 | Changes in 0.6.2-alpha3 128 | 129 | * Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](https://clojure.atlassian.net/browse/JDBC-140). 130 | * Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](https://clojure.atlassian.net/browse/JDBC-139). 131 | * Fixed postgres / postgresql alias support [JDBC-138](https://clojure.atlassian.net/browse/JDBC-138). 132 | This also adds aliases for mssql (sqlserver), jtds (jtds:sqlserver), oracle (oracle:thin), and hsql (hsqldb). 133 | 134 | Changes in 0.6.2-alpha2 135 | 136 | * Updates to `clojure.spec` support to work properly with Clojure 1.9.0 Alpha 10. 137 | 138 | Changes in 0.6.2-alpha1 139 | 140 | * Experimental support for `clojure.spec` via the new `clojure.java.jdbc.spec` namespace. Requires Clojure 1.9.0 Alpha 8 (or later). 141 | * All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](https://clojure.atlassian.net/browse/JDBC-136). 142 | * `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](https://clojure.atlassian.net/browse/JDBC-135). 143 | * `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](https://clojure.atlassian.net/browse/JDBC-134). 144 | * In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](https://clojure.atlassian.net/browse/JDBC-133). 145 | 146 | Changes in 0.6.1 147 | 148 | * `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](https://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. 149 | * PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](https://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](https://clojure.atlassian.net/browse/JDBC-129). 150 | 151 | Changes in 0.6.0 152 | 153 | * `find-by-keys` now correctly handles `nil` values [JDBC-126](https://clojure.atlassian.net/browse/JDBC-126). 154 | * `find-by-keys` calls `seq` on `:order-by` to treat `[]` as no `ORDER BY` clause. 155 | 156 | Changes in 0.6.0-rc2 157 | 158 | * `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). 159 | - Passing the `prepare-statement` options map as the first element of the `[sql & params]` vector is no longer supported and will throw an `IllegalArgumentException`. It was always very poorly documented and almost never used, as far as I can tell. 160 | * `db-query-with-resultset` no longer requires the `sql-params` argument to be a vector: a sequence is acceptable. This is in line with other functions that accept a sequence. 161 | * `db-query-with-resultset` now accepts a bare SQL string or `PreparedStatement` as the `sql-params` argument, when there are no parameters needed. This is in line with other functions that accept SQL or a `PreparedStatement`. 162 | * `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). 163 | * `find-by-keys` now accepts an `:order-by` option that specifies a sequence of orderings; an ordering is either a column (to sort ascending) or a map from column name to direct (`:asc` or `:desc`). 164 | 165 | Changes in 0.6.0-rc1 166 | 167 | * Adds `get-by-id` and `find-by-keys` convenience functions (these were easy to add after the API changes in 0.6.0 and we rely very heavily on them at World Singles so putting them in the core for everyone seemed reasonable). 168 | * REMINDER: ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). 169 | - See alpha2 / alpha1 below for more details. 170 | 171 | Changes in 0.6.0-alpha2 172 | 173 | * ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). 174 | - This removes deprecated functionality from db-do-commands and `db-do-prepared*` which should have been removed in Alpha 1. 175 | * Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](https://clojure.atlassian.net/browse/JDBC-124). 176 | * Fix typo in `insert-multi!` argument validation exception [JDBC-123](https://clojure.atlassian.net/browse/JDBC-123). 177 | 178 | Changes in 0.6.0-alpha1 179 | 180 | * (ALMOST) ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). 181 | - See changes described in versions 0.5.5 through 0.5.8 for what was deprecated 182 | - Use version 0.5.8 as a bridge to identify any deprecated API calls on which your code relies! 183 | - `db-transaction` (deprecated in version 0.3.0) has been removed 184 | - The `java.jdbc.deprecated` namespace has been removed 185 | 186 | Changes in 0.5.8 187 | 188 | * `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. 189 | * `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. 190 | 191 | Changes in 0.5.7 192 | 193 | * `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](https://clojure.atlassian.net/browse/JDBC-121). 194 | 195 | Changes in 0.5.6 196 | 197 | * `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](https://clojure.atlassian.net/browse/JDBC-120). If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. 198 | * `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](https://clojure.atlassian.net/browse/JDBC-119). If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. 199 | * NOTE: all deprecated functionality will go away in version 0.6.0! 200 | 201 | Changes in 0.5.5 202 | 203 | * Allow options map in all calls that previously took optional keyword arguments [JDBC-117](https://clojure.atlassian.net/browse/JDBC-117). The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. 204 | 205 | Changes in 0.5.0 206 | 207 | * Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](https://clojure.atlassian.net/browse/JDBC-115). 208 | * Remove exception wrapping [JDBC-114](https://clojure.atlassian.net/browse/JDBC-114). 209 | * Drop Clojure 1.3 compatibility. 210 | 211 | Changes in 0.4.2 212 | 213 | * Remove redundant type hints [JDBC-113](https://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. 214 | * Avoid reflection on `.prepareStatement` [JDBC-112](https://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. 215 | * Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](https://clojure.atlassian.net/browse/JDBC-107). 216 | * `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](https://clojure.atlassian.net/browse/JDBC-104). 217 | * Officially support H2 (and test against it) to support [JDBC-91](https://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. 218 | 219 | Changes in 0.4.0 / 0.4.1 220 | 221 | * `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](https://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. 222 | * Nested transaction checks isolation level is the same [JDBC-110](https://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. 223 | * Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](https://clojure.atlassian.net/browse/JDBC-109). 224 | * Drop Clojure 1.2 compatibility. 225 | 226 | Changes in 0.3.7 227 | 228 | * Bump all driver versions in `project.clj` and re-test. 229 | * Remove duplicate `count` calls in `insert-sql` [JDBC-108](https://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. 230 | * Remove driver versions from README and link to Maven Central [JDBC-106](https://clojure.atlassian.net/browse/JDBC-106). 231 | * Fix links in CHANGES and README [JDBC-103](https://clojure.atlassian.net/browse/JDBC-103) - John Walker. 232 | 233 | Changes in 0.3.6 234 | 235 | * Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](https://clojure.atlassian.net/browse/JDBC-102). 236 | * Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](https://clojure.atlassian.net/browse/JDBC-101). 237 | * Add `:timeout` argument to `prepare-statement` [JDBC-100](https://clojure.atlassian.net/browse/JDBC-100). 238 | 239 | Changes in 0.3.5 240 | 241 | * Reflection warnings on executeUpdate addressed. 242 | * HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](https://clojure.atlassian.net/browse/JDBC-94). 243 | * Add support for readonly transactions via :read-only? [JDBC-93](https://clojure.atlassian.net/browse/JDBC-93). 244 | 245 | Changes in 0.3.4 246 | 247 | * execute! can now accept a PreparedStatement [JDBC-96](https://clojure.atlassian.net/browse/JDBC-96). 248 | * Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](https://clojure.atlassian.net/browse/JDBC-92). 249 | * Support oracle:oci and oracle:thin subprotocols [JDBC-90](https://clojure.atlassian.net/browse/JDBC-90). 250 | 251 | Changes in 0.3.3 252 | 253 | * Prevent exception/crash when query called with bare SQL string [JDBC-89](https://clojure.atlassian.net/browse/JDBC-89). 254 | * Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](https://clojure.atlassian.net/browse/JDBC-87). 255 | * Support key/value configuration from URI (Phil Hagelberg). 256 | 257 | Changes in 0.3.2 258 | 259 | * Add nil protocol implementation to ISQLParameter. 260 | 261 | Changes in 0.3.1 (broken) 262 | 263 | * Improve docstrings and add :arglists for better auto-generated documentation. 264 | * Make insert-sql private - technically a breaking change but it should never have been public: sorry folks! 265 | * Provide better protocol for setting parameters in prepared statements [JDBC-86](https://clojure.atlassian.net/browse/JDBC-86). 266 | * Fix parens in two deprecated tests [JDBC-85](https://clojure.atlassian.net/browse/JDBC-85). 267 | * Made create-table-ddl less aggressive about applying as-sql-name so only first name in a column spec is affected. 268 | 269 | Changes in 0.3.0 270 | 271 | * Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](https://clojure.atlassian.net/browse/JDBC-84). 272 | * Rename recently introduced test to ensure unique names [JDBC-83](https://clojure.atlassian.net/browse/JDBC-83). 273 | * Rename unused arguments in protocol implementation to support Android [JDBC-82](https://clojure.atlassian.net/browse/JDBC-82). 274 | * Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](https://clojure.atlassian.net/browse/JDBC-65). 275 | 276 | Changes in 0.3.0-rc1 277 | 278 | * Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](https://clojure.atlassian.net/browse/JDBC-81). 279 | * Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](https://clojure.atlassian.net/browse/JDBC-80). 280 | * Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](https://clojure.atlassian.net/browse/JDBC-79). 281 | * Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](https://clojure.atlassian.net/browse/JDBC-77). 282 | * Add support for :isolation in with-db-transaction [JDBC-75](https://clojure.atlassian.net/browse/JDBC-75). 283 | * Add :user as an alias for :username for DataSource connections [JDBC-74](https://clojure.atlassian.net/browse/JDBC-74). 284 | 285 | Changes in 0.3.0-beta2 286 | 287 | * The DSL namespaces introduced in 0.3.0-alpha1 have been retired - see [java-jdbc/dsl](https://github.com/seancorfield/jsql) for a migration path if you wish to continue using the DSL (although it is recommended you switch to another, more expressive DSL). 288 | * The older API (0.2.3) which was deprecated in earlier 0.3.0 builds has moved to `clojure.java.jdbc.deprecated` to help streamline the API for 0.3.0 and clean up the documentation. 289 | 290 | Changes in 0.3.0-beta1 291 | 292 | * query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](https://clojure.atlassian.net/browse/JDBC-72). 293 | * "h2" is recognized as a protocol shorthand for org.h2.Driver 294 | * Tests no longer use :1 literal [JDBC-71](https://clojure.atlassian.net/browse/JDBC-71). 295 | * Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](https://clojure.atlassian.net/browse/JDBC-69). 296 | * New db-query-with-resultset function replaces private `db-with-query-results*` and processes a raw ResultSet object [JDBC-63](https://clojure.atlassian.net/browse/JDBC-63). 297 | * Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40). 298 | 299 | Changes in 0.3.0-alpha5 300 | 301 | * DDL now supports entities naming strategy [JDBC-53](https://clojure.atlassian.net/browse/JDBC-53). 302 | * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). 303 | * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) 304 | * Added Leiningen support for easier development/testing (Maven is still the primary build tool). 305 | * Added create-index / drop-index DDL [JDBC-62](https://clojure.atlassian.net/browse/JDBC-62) - moquist 306 | * Make transaction? boolean optional in various `db-do-*` functions 307 | * Create clojure.java.jdbc.ddl namespace 308 | * Add create-table, drop-table, create-index and drop-index 309 | * Deprecate create-table, create-table-ddl and drop-table in main namespace 310 | * Update README to clarify PostgreSQL instructions. 311 | * Fix test suite for PostgreSQL [JDBC-59](https://clojure.atlassian.net/browser/JDBC-59) 312 | * Improve hooks for Oracle data type handling [JDBC-57](https://clojure.atlassian.net/browser/JDBC-57) 313 | * Fix reflection warnings [JDBC-55](https://clojure.atlassian.net/browser/JDBC-55) 314 | 315 | * DDL now supports entities naming strategy [JDBC-53](https://clojure.atlassian.net/browse/JDBC-53). 316 | * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). 317 | * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) 318 | * Added Leiningen support for easier development/testing (Maven is still the primary build tool). 319 | * Added create-index / drop-index DDL [JDBC-62](https://clojure.atlassian.net/browse/JDBC-62) - moquist 320 | * Make transaction? boolean optional in various `db-do-*` functions 321 | * It will ultimately change to a function argument I think when [JDBC-37](https://clojure.atlassian.net/browser/JDBC-37) is dealt with 322 | * Create clojure.java.jdbc.ddl namespace 323 | * Add create-table and drop-table 324 | * Deprecate create-table, create-table-ddl and drop-table in main namespace 325 | * More DDL is coming soon 326 | * Update README to clarify PostgreSQL instructions. 327 | * Fix test suite for PostgreSQL [JDBC-59](https://clojure.atlassian.net/browser/JDBC-59) 328 | * Improve hooks for Oracle data type handling [JDBC-57](https://clojure.atlassian.net/browser/JDBC-57) 329 | * Fix reflection warnings [JDBC-55](https://clojure.atlassian.net/browser/JDBC-55) 330 | 331 | Changes in 0.3.0-alpha4 332 | 333 | * Fix connection leaks [JDBC-54](https://clojure.atlassian.net/browser/JDBC-54) 334 | * Allow order-by to accept empty sequence (and return empty string) 335 | 336 | Changes in 0.3.0-alpha3 337 | 338 | * Fix macro / import interaction by fully qualifying Connection type. 339 | 340 | Changes in 0.3.0-alpha2 341 | 342 | * Address [JDBC-51](https://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection 343 | * Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](https://clojure.atlassian.net/browse/JDBC-46) 344 | * Add :multi? to execute! so it can be used for repeated operations [JDBC-52](https://clojure.atlassian.net/browse/JDBC-52) 345 | * Reverted specialized handling of NULL values (reopens [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40)) 346 | * Rename :as-arrays to :as-arrays? since it is boolean 347 | * Add curried version of clojure.java.jdbc.sql/as-quoted-str 348 | * Officially deprecate resultset-seq 349 | 350 | Changes in 0.3.0-alpha1 351 | 352 | Major overhaul of the API and deprecation of most of the old API! 353 | 354 | * Add insert!, query, update!, delete! and execute! high-level API 355 | [JDBC-20](https://clojure.atlassian.net/browse/JDBC-20) 356 | * Add optional SQL-generating DSL in clojure.java.jdbc.sql (implied by JDBC-20) 357 | * Add db- prefixed versions of low-level API 358 | * Add db-transaction macro: 359 | 360 | ``` 361 | (db-transaction [t-con db-spec] 362 | (query t-con (select * :user (where {:id 42})))) 363 | ``` 364 | 365 | * Add result-set-seq as replacement for resultset-seq (which will be deprecated) 366 | * Transaction now correctly rollback on non-Exception Throwables 367 | [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) 368 | * Rewrite old API functions in terms of new API, and deprecate old API 369 | [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) 370 | * Add :as-arrays to query / result-set-seq 371 | [JDBC-41](https://clojure.atlassian.net/browse/JDBC-41) 372 | * Better handling of NULL values [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40) 373 | and [JDBC-18](https://clojure.atlassian.net/browse/JDBC-18) 374 | Note: JDBC-40 has been reverted in 0.3.0-alpha2 because it introduced regressions for PostgreSQL 375 | * db-do-commands allows you to execute SQL without a transaction wrapping it 376 | [JDBC-38](https://clojure.atlassian.net/browse/JDBC-38) 377 | * Remove reflection warning from execute-batch 378 | * Add notes to README about 3rd party database driver dependencies 379 | * Add optional :identifiers argument to resultset-seq so you can explicitly pass in the naming strategy 380 | 381 | Changes in 0.2.3: 382 | 383 | * as-str now treats a.b as two identifiers separated by . so quoting produces [a].[b] instead of [a.b] 384 | * Add :connection-uri option [JDBC-34](https://clojure.atlassian.net/browse/JDBC-34) 385 | 386 | Changes in 0.2.2: 387 | 388 | * Handle Oracle unknown row count affected [JDBC-33](https://clojure.atlassian.net/browse/JDBC-33) 389 | * Handle jdbc: prefix in string db-specs [JDBC-32](https://clojure.atlassian.net/browse/JDBC-32) 390 | * Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](https://clojure.atlassian.net/browse/JDBC-31) 391 | 392 | Changes in 0.2.1: 393 | 394 | * Result set performance enhancement (Juergen Hoetzel) [JDBC-29](https://clojure.atlassian.net/browse/JDBC-29) 395 | * Make do-prepared-return-keys (for Korma team) [JDBC-30](https://clojure.atlassian.net/browse/JDBC-30) 396 | 397 | Changes in 0.2.0: 398 | 399 | * Merge internal namespace into main jdbc namespace and update symbol visibility / naming. 400 | 401 | Changes in 0.1.4: 402 | 403 | * Unwrap RTE for nested transaction exception (we already unwrapped top-level transaction RTEs). 404 | * Remove reflection warning unwrapping RunTimeException (Alan Malloy) 405 | 406 | Changes in 0.1.3: 407 | 408 | * Fix JDBC-26 (fully) by adding transaction/generated keys support for SQLite3 (based on patch from Nelson Morris) 409 | 410 | Changes in 0.1.2: 411 | 412 | * Fix JDBC-23 by handling prepared statement params correctly (Ghadi Shayban) 413 | * Fix JDBC-26 by adding support for SQLite3 (based on patch from Nelson Morris) 414 | * Fix JDBC-27 by replacing replicate with repeat (Jonas Enlund) 415 | * Ensure MS SQL Server passes tests with both Microsoft and jTDS drivers 416 | * Build server now tests derby, hsqldb and sqlite by default 417 | * Update README per Stuart Sierra's outline for contrib projects 418 | 419 | Changes in 0.1.1: 420 | 421 | * Fix JDBC-21 by adding support for db-spec as URI (Phil Hagelberg). 422 | * Fix JDBC-22 by deducing driver class name from subprotocol (Phil Hagelberg). 423 | * Add Postgres dependency so tests can be automcated (Phil Hagelberg). 424 | * Add ability to specify test databases via TEST_DBS environment variable (Phil Hagelberg). 425 | 426 | Changes in 0.1.0: 427 | 428 | * Fix JDBC-15 by removing dependence on deprecated structmap. 429 | 430 | Changes in 0.0.7: 431 | 432 | * Fix JDBC-9 by renaming duplicate columns instead of throwing an exception. 433 | - thanx to Peter Siewert! 434 | * Fix JDBC-16 by ensuring do-prepared works with no param-groups provided. 435 | * Fix JDBC-17 by adding type hints to remove more reflection warnings. 436 | - thanx to Stuart Sierra! 437 | Documentation: 438 | * Address JDBC-4 by documenting how to do connection pooling. 439 | 440 | Changes in 0.0.6: 441 | 442 | * Move former tests to test-utilities namespace - these do not touch a database 443 | * Convert old "test" examples into real tests against real databases 444 | - tested locally against MySQL, Apache Derby, HSQLDB 445 | - build system should run against Apache Derby, HSQLSB 446 | - will add additional databases later 447 | * Fix JDBC-12 by removing batch when doing a single update 448 | * Remove wrapping of exceptions in transactions to make it easier to work with SQLExceptions 449 | 450 | Changes in 0.0.5: 451 | 452 | * Add prepare-statement function to ease creation of PreparedStatement with common options: 453 | - see docstring for details 454 | * with-query-results now allows the SQL/params vector to be: 455 | - a PreparedStatement object, followed by any parameters the SQL needs 456 | - a SQL query string, followed by any parameters it needs 457 | - options (for prepareStatement), a SQL query string, followed by any parameters it needs 458 | * Add support for databases that cannot return generated keys (e.g., HSQLDB) 459 | - insert operations silently return the insert counts instead of generated keys 460 | - it is the user's responsibility to handle this if you're using such a database! 461 | 462 | Changes in 0.0.4: 463 | 464 | * Fix JDBC-2 by allowing :table-spec {string} at the end of create-table arguments: 465 | (sql/create-table :foo [:col1 "int"] ["col2" :int] :table-spec "ENGINE=MyISAM") 466 | * Fix JDBC-8 by removing all reflection warnings 467 | * Fix JDBC-11 by no longer committing the transaction when an Error occurs 468 | * Clean up as-... functions to reduce use of (binding) 469 | * Refactor `do-prepared*`, separating out return keys logic and parameter setting logic 470 | - in preparation for exposing more hooks in PreparedStatement creation / manipulation 471 | 472 | Changes in 0.0.3: 473 | 474 | * Fix JDBC-10 by using .executeUpdate when generating keys (MS SQL Server, PostgreSQL compatibility issue) 475 | 476 | Changes in 0.0.2: 477 | 478 | * Fix JDBC-7 Clojure 1.2 compatibility (thanx to Aaron Bedra!) 479 | 480 | Changes in 0.0.1 (compared to clojure.contrib.sql): 481 | 482 | * Exposed print-... functions for exception printing; no longer writes exceptions to `*out*` 483 | * Add clojure.java.jdbc/resultset-seq (to replace clojure.core/resultset-seq which should be deprecated) 484 | * Add support for naming and quoting strategies - see https://clojure.github.io/java.jdbc/doc/clojure/java/jdbc/NameMapping.html 485 | - The formatting is a bit borked, Tom F knows about this and is working on an enhancement to auto-doc to improve it 486 | * Add ability to return generated keys from single insert operations, add insert-record function 487 | * Clojure 1.3 compatibility 488 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clojure.java.jdbc 2 | ======================================== 3 | 4 | A low-level Clojure wrapper for JDBC-based access to databases. This project is "Inactive". It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). 5 | 6 | For higher level DSLs and migration libraries that are compatible, see the [documentation](https://clojure-doc.org/articles/ecosystem/java_jdbc/home). 7 | 8 | Formerly known as `clojure.contrib.sql`. 9 | 10 | This library is mature and stable. It is widely used and its use is described in many books and tutorials. It will only get critical bug fixes (e.g., security). Based on my experience using and maintaining this library, I've created a faster, more modern JDBC wrapper called [next.jdbc](https://github.com/seancorfield/next-jdbc). I consider it to be the "next generation" of `clojure.java.jdbc` but it exposes a different API -- a better API, I think. 11 | 12 | Documentation 13 | ======================================== 14 | * [API Reference](https://clojure.github.io/java.jdbc/) (Autogenerated) 15 | * [Overview](https://clojure-doc.org/articles/ecosystem/java_jdbc/home) 16 | * [Manipulating Data with SQL](https://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql) 17 | * [How to Reuse Database Connections](https://clojure-doc.org/articles/ecosystem/java_jdbc/reusing_connections) 18 | * [Using DDL and Metadata](https://clojure-doc.org/articles/ecosystem/java_jdbc/using_ddl) 19 | 20 | Support 21 | ======================================== 22 | * [Mailing List](https://groups.google.com/forum/#!forum/clojure-java-jdbc) 23 | * #sql on [Clojurians Slack](http://clojurians.net/) 24 | 25 | Releases and Dependency Information 26 | ======================================== 27 | 28 | Latest stable release: 0.7.12 -- requires Clojure 1.7 or later! 29 | 30 | * [All Released Versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) 31 | * [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~) 32 | 33 | [CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information: 34 | ```clojure 35 | org.clojure/java.jdbc {:mvn/version "0.7.12"} 36 | ``` 37 | [Leiningen](https://github.com/technomancy/leiningen) dependency information: 38 | ```clojure 39 | [org.clojure/java.jdbc "0.7.12"] 40 | ``` 41 | [Maven](https://maven.apache.org/) dependency information: 42 | ```xml 43 | 44 | org.clojure 45 | java.jdbc 46 | 0.7.12 47 | 48 | ``` 49 | _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ 50 | 51 | You will also need to add dependencies for the JDBC driver you intend to use. Here are links (to Maven Central) for each of the common database drivers that clojure.java.jdbc is known to be used with: 52 | 53 | * [Apache Derby](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.apache.derby%22%20AND%20a%3A%22derby%22) 54 | * [H2](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.h2database%22%20AND%20a%3A%22h2%22) 55 | * [HSQLDB](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22hsqldb%22%20AND%20a%3A%22hsqldb%22) 56 | * [Microsoft SQL Server jTDS](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22net.sourceforge.jtds%22%20AND%20a%3A%22jtds%22) 57 | * [Microsoft SQL Server -- Official MS Version](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.microsoft.sqlserver%22%20AND%20a%3A%22mssql-jdbc%22) 58 | * [MySQL](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22mysql%22%20AND%20a%3A%22mysql-connector-java%22) 59 | * [PostgreSQL](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.postgresql%22%20AND%20a%3A%22postgresql%22) 60 | * [SQLite](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.xerial%22%20AND%20a%3A%22sqlite-jdbc%22) 61 | 62 | Note: different versions of various database drivers have different Java/JVM version requirements. In particular, recent versions of Apache Derby require at least Java 8 and recent versions of H2 require at least Java 7. Clojure's Continuous Integration system uses older versions so tests can be run on Java 6 (see `pom.xml`); local testing is done with more recent versions on Java 8. 63 | 64 | Example Usage 65 | ======================================== 66 | ```clojure 67 | (require '[clojure.java.jdbc :as j]) 68 | 69 | ;; there are many ways to write a db-spec but the easiest way is to 70 | ;; use :dbtype and then provide the :dbname and any of :user, :password, 71 | ;; :host, :port, and other options as needed: 72 | (def mysql-db {:dbtype "mysql" 73 | :dbname "clojure_test" 74 | :user "clojure_test" 75 | :password "clojure_test"}) 76 | 77 | (def pg-db {:dbtype "postgresql" 78 | :dbname "mypgdatabase" 79 | :host "mydb.server.com" 80 | :user "myuser" 81 | :password "secret" 82 | :ssl true 83 | :sslfactory "org.postgresql.ssl.NonValidatingFactory"}) 84 | 85 | ;; if the dbtype is not known to clojure.java.jdbc, or you want to override the 86 | ;; default choice of JDBC driver class name, you can provide :classname and the 87 | ;; name of the class to use: 88 | 89 | (def redshift42 {:dbtype "redshift" 90 | :dbname "myredstore" 91 | :classname "com.amazon.redshift.jdbc42.Driver" 92 | ...}) 93 | 94 | ;; you can also specify a full connection string if you'd prefer: 95 | (def pg-uri 96 | {:connection-uri (str "postgresql://myuser:secret@mydb.server.com:5432/mypgdatabase" 97 | "?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory")}) 98 | 99 | (j/insert-multi! mysql-db :fruit 100 | [{:name "Apple" :appearance "rosy" :cost 24} 101 | {:name "Orange" :appearance "round" :cost 49}]) 102 | ;; ({:generated_key 1} {:generated_key 2}) 103 | 104 | (j/query mysql-db 105 | ["select * from fruit where appearance = ?" "rosy"] 106 | {:row-fn :cost}) 107 | ;; (24) 108 | ``` 109 | For more detail see the [API reference](https://clojure.github.io/java.jdbc/) or [documentation](https://clojure-doc.org/articles/ecosystem/java_jdbc/home). 110 | 111 | Developer Information 112 | ======================================== 113 | 114 | * [GitHub project](https://github.com/clojure/java.jdbc) 115 | * [Bug Tracker](https://clojure.atlassian.net/browse/JDBC) 116 | * [Continuous Integration](https://github.com/clojure/java.jdbc/actions/workflows/test.yml) 117 | 118 | * Testing: 119 | * Currently by default tests run only against Derby and HSQLDB, the in-process databases. 120 | 121 | * To test against PostgreSQL, first create the user and database: 122 | 123 | $ sudo -u postgres createuser clojure_test -P clojure_test 124 | $ sudo -u postgres createdb clojure_test -O clojure_test 125 | 126 | * Or similarly with MySQL: 127 | 128 | $ mysql -u root 129 | mysql> create database clojure_test; 130 | mysql> grant all on clojure_test.* to clojure_test identified by "clojure_test"; 131 | 132 | * Then run the tests with the TEST_DBS environment variable: 133 | 134 | $ TEST_DBS="mysql postgres" mvn test 135 | 136 | * Also see the `run-tests.sh` shell script which uses the `clj` CLI and `deps.edn` for multi-version testing! 137 | 138 | Change Log 139 | ==================== 140 | 141 | * Release 0.7.12 on 2021-02-01 142 | * Make the protocols `ISQLValue`, `ISQLParameter`, and `IResultSetReadColumn` extensible via metadata. 143 | 144 | * Release 0.7.11 on 2019-12-24 145 | * Address edge case in transaction rollback failure [JDBC-179](https://clojure.atlassian.net/browse/JDBC-179). 146 | 147 | * Release 0.7.10 on 2019-08-24 148 | * Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. 149 | * Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). 150 | * Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://clojure.atlassian.net/browse/JDBC-177). 151 | 152 | * Release 0.7.9 on 2019-02-21 153 | * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://clojure.atlassian.net/browse/JDBC-176). 154 | * Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://clojure.atlassian.net/browse/JDBC-175). 155 | * Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://clojure.atlassian.net/browse/JDBC-174). 156 | * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://clojure.atlassian.net/browse/JDBC-173). 157 | 158 | * Release 0.7.8 on 2018-08-13 159 | * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://clojure.atlassian.net/browse/JDBC-172). 160 | * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://clojure.atlassian.net/browse/JDBC-171). 161 | 162 | * Release 0.7.7 on 2018-06-23 163 | * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://clojure.atlassian.net/browse/JDBC-169). 164 | * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). 165 | 166 | * Release 0.7.6 on 2018-04-24 167 | * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://clojure.atlassian.net/browse/JDBC-166). 168 | * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). 169 | * Add missing spec for `db-spec` being a `java.net.URI` object. 170 | * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). 171 | * Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://clojure.atlassian.net/browse/JDBC-165). 172 | * Update tests so they work properly with string `db-spec` test databases. 173 | * Ensure no reflection warnings are present. 174 | * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". 175 | 176 | * Release 0.7.5 on 2017-12-29 177 | * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://clojure.atlassian.net/browse/JDBC-163). 178 | 179 | * Release 0.7.4 on 2017-12-14 180 | * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://clojure.atlassian.net/browse/JDBC-160). 181 | * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. 182 | * Performance improvements, primarily in `query` and `reducible-query`. 183 | * Experimental `:raw?` result set handling in `reducible-query`. 184 | * `modify-connection` is more robust in the face of `null` connections and bad option values. 185 | 186 | * Release 0.7.3 on 2017-10-05 187 | * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://clojure.atlassian.net/browse/JDBC-159). 188 | * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://clojure.atlassian.net/browse/JDBC-158). 189 | 190 | * Release 0.7.2 on 2017-10-02 191 | * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://clojure.atlassian.net/browse/JDBC-156). 192 | * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. 193 | * Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. 194 | 195 | * Release 0.7.1 on 2017-08-30 196 | * Connection strings with empty values were not parsed correctly [JDBC-155](https://clojure.atlassian.net/browse/JDBC-155). 197 | 198 | * Release 0.7.0 on 2017-07-16 199 | * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). 200 | * Add better support for Oracle connections (default port to `1521`, support `:dbtype "oracle"` -- as `"oracle:thin"` -- and `:dbtype "oracle:oci"`, with `@` instead of `//` before host). 201 | 202 | * Release 0.7.0-beta5 on 2017-07-05 203 | * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://clojure.atlassian.net/browse/JDBC-153). 204 | * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. 205 | 206 | * Release 0.7.0-beta4 on 2017-07-04 207 | * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. 208 | * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. 209 | 210 | * Release 0.7.0-beta3 on 2017-07-04 211 | * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://clojure.atlassian.net/browse/JDBC-152). 212 | 213 | * Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) 214 | * Support for Clojure 1.5 and 1.6 has been dropped -- breaking change. 215 | * Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! 216 | * All public functions now have specs in the optional `clojure.java.jdbc.spec` namespace (requires `clojure.spec.alpha`). 217 | * `reducible-query` and `reducible-result-set` use `IReduce` and correctly support the no-`init` arity of `reduce` by using the first row of the `ResultSet`, if present, as the (missing) `init` value, and only calling `f` with no arguments if the `ResultSet` is empty. The `init` arity of `reduce` only ever calls `f` with two arguments. 218 | 219 | * Release 0.7.0-beta1 on 2017-06-29 220 | * Support for Clojure 1.4.0 has been dropped -- breaking change. 221 | * Optional spec support now uses `clojure.spec.alpha`. 222 | * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://clojure.atlassian.net/browse/JDBC-99). 223 | 224 | * Release 0.7.0-alpha3 on 2017-03-23 225 | * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](https://clojure.atlassian.net/browse/JDBC-151). 226 | * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. 227 | 228 | * Release 0.7.0-alpha2 on 2017-03-01 229 | * `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](https://clojure.atlassian.net/browse/JDBC-150). 230 | * `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](https://clojure.atlassian.net/browse/JDBC-149). 231 | * Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](https://clojure.atlassian.net/browse/JDBC-148). 232 | * Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](https://clojure.atlassian.net/browse/JDBC-145). 233 | 234 | * Release 0.7.0-alpha1 on 2016-11-12 -- potentially breaking changes 235 | * The signatures of `as-sql-name` and `quoted` have changed slightly: the former no longer has the curried (single argument) version, and the latter no longer has the two argument version. This change came out of a discussion on Slack which indicated curried functions are non-idiomatic. If you relied on the curried version of `as-sql-name`, you will not need to use `partial`. If you relied on the two argument version of `quoted`, you will need to add an extra `( )` for the one argument call. I'd be fairly surprised if anyone is using `as-sql-name` at all since it is really an implementation detail. I'd also be surprised if anyone was using the two argument version of `quoted` since the natural usage is `:entities (quoted [\[ \]])` to create a naming strategy (that provides SQL entity quoting). 236 | * Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](https://clojure.atlassian.net/browse/JDBC-147). 237 | * All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](https://clojure.atlassian.net/browse/JDBC-144). 238 | * Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](https://clojure.atlassian.net/browse/JDBC-141). 239 | * Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](https://clojure.atlassian.net/browse/JDBC-137). 240 | * Expanded optional `clojure.spec` coverage to almost the whole library API. 241 | 242 | * Release 0.6.2-alpha3 on 2016-08-25 243 | * Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](https://clojure.atlassian.net/browse/JDBC-140). 244 | * Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](https://clojure.atlassian.net/browse/JDBC-139). 245 | * Fixed postgres / postgresql alias support [JDBC-138](https://clojure.atlassian.net/browse/JDBC-138). 246 | This also adds aliases for mssql (sqlserver), jtds (jtds:sqlserver), oracle (oracle:thin), and hsql (hsqldb). 247 | 248 | * Release 0.6.2-alpha2 on 2016-07-21 249 | * Update `clojure.spec` support to work with Clojure 1.9.0 Alpha 10. 250 | 251 | * Release 0.6.2-alpha1 on 2016-07-05 252 | * Experimental support for `clojure.spec` via the new `clojure.java.jdbc.spec` namespace. Requires Clojure 1.9.0 Alpha 8 (or later). 253 | * All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](https://clojure.atlassian.net/browse/JDBC-136). 254 | * `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](https://clojure.atlassian.net/browse/JDBC-135). 255 | * `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](https://clojure.atlassian.net/browse/JDBC-134). 256 | * In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](https://clojure.atlassian.net/browse/JDBC-133). 257 | 258 | * Release 0.6.1 on 2016-05-12 -- **IMPORTANT BUG FIX!** 259 | * `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](https://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. 260 | * PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](https://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](https://clojure.atlassian.net/browse/JDBC-129). 261 | 262 | 263 | * Release 0.6.0 on 2016-05-11 -- **BREAKING RELEASE! DEPRECATED FUNCTIONALITY REMOVED!** 264 | * `find-by-keys` now correctly handles `nil` values [JDBC-126](https://clojure.atlassian.net/browse/JDBC-126). 0.6.0 / 2016-05-11. 265 | * `find-by-keys` calls `seq` on `:order-by` to treat `[]` as no `ORDER BY` clause. 0.6.0 / 2016-05-11. 266 | * `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. 267 | - Passing the `prepare-statement` options map as the first element of the `[sql & params]` vector is no longer supported and will throw an `IllegalArgumentException`. It was always very poorly documented and almost never used, as far as I can tell. 268 | * `db-query-with-resultset` no longer requires the `sql-params` argument to be a vector: a sequence is acceptable. This is in line with other functions that accept a sequence. 0.6.0-rc2 / 2016-05-07. 269 | * `db-query-with-resultset` now accepts a bare SQL string or `PreparedStatement` as the `sql-params` argument, when there are no parameters needed. This is in line with other functions that accept SQL or a `PreparedStatement`. 0.6.0-rc2 / 2016-05-07. 270 | * `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. 271 | * Adds `get-by-id` and `find-by-keys` convenience functions (these were easy to add after the API changes in 0.6.0 and we rely very heavily on them at World Singles so putting them in the core for everyone seemed reasonable). 0.6.0-rc1 / 2016-05-04. 272 | - `find-by-keys` accepts an `:order-by` option that expects a sequence of orderings; an ordering is a column name (keyword) or a map from column name (keyword) to direction (`:asc` or `:desc`). 0.6.0-rc2 / 2016-05-07. 273 | * Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](https://clojure.atlassian.net/browse/JDBC-124). 0.6.0-alpha2 / 2016-04-18. 274 | * Fix typo in `insert-multi!` argument validation exception [JDBC-123](https://clojure.atlassian.net/browse/JDBC-123). 0.6.0-alpha2 / 2016-04-18. 275 | * ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). 0.6.0-alpha1 / 2016-04-13 276 | - See changes described in versions 0.5.5 through 0.5.8 for what was deprecated 277 | - Use version 0.5.8 as a bridge to identify any deprecated API calls on which your code relies! 278 | - `db-transaction` (deprecated in version 0.3.0) has been removed 279 | - The `java.jdbc.deprecated` namespace has been removed 280 | 281 | * Release 0.5.8 on 2016-04-12 282 | * `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. 283 | * `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. 284 | 285 | * Release 0.5.7 on 2016-04-10 286 | * `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](https://clojure.atlassian.net/browse/JDBC-121). 287 | 288 | * Release 0.5.6 on 2016-04-10 289 | * `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](https://clojure.atlassian.net/browse/JDBC-120). 290 | - If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. 291 | * `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](https://clojure.atlassian.net/browse/JDBC-119). 292 | - If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. 293 | * NOTE: all deprecated functionality will go away in version 0.6.0! 294 | 295 | * Release 0.5.5 on 2016-04-09 296 | * Allow options map in all calls that previously took optional keyword arguments [JDBC-117](https://clojure.atlassian.net/browse/JDBC-117). 297 | - The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. 298 | 299 | * Release 0.5.0 on 2016-03-27 300 | * Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](https://clojure.atlassian.net/browse/JDBC-115). 301 | * Remove exception wrapping [JDBC-114](https://clojure.atlassian.net/browse/JDBC-114). 302 | * Drop Clojure 1.3 compatibility. 303 | 304 | * Release 0.4.2 on 2015-09-15 305 | * Remove redundant type hints [JDBC-113](https://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. 306 | * Avoid reflection on `.prepareStatement` [JDBC-112](https://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. 307 | * Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](https://clojure.atlassian.net/browse/JDBC-107). 308 | * `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](https://clojure.atlassian.net/browse/JDBC-104). 309 | * Officially support H2 (and test against it) to support [JDBC-91](https://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. 310 | 311 | * Release 0.4.0 / 0.4.1 on 2015-07-26 312 | * `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](https://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. 313 | * Nested transaction checks isolation level is the same [JDBC-110](https://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. 314 | * Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](https://clojure.atlassian.net/browse/JDBC-109). 315 | * Drop Clojure 1.2 compatibility. 316 | 317 | * Release 0.3.7 on 2015-05-18 318 | * Bump all driver versions in `project.clj` and re-test. 319 | * Remove duplicate `count` calls in `insert-sql` [JDBC-108](https://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. 320 | * Remove driver versions from README and link to Maven Central [JDBC-106](https://clojure.atlassian.net/browse/JDBC-106). 321 | * Fix links in CHANGES and README [JDBC-103](https://clojure.atlassian.net/browse/JDBC-103) - John Walker. 322 | 323 | * Release 0.3.6 on 2014-10-28 324 | * Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](https://clojure.atlassian.net/browse/JDBC-102). 325 | * Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](https://clojure.atlassian.net/browse/JDBC-101). 326 | * Add `:timeout` argument to `prepare-statement` [JDBC-100](https://clojure.atlassian.net/browse/JDBC-100). 327 | 328 | * Release 0.3.5 on 2014-08-01 329 | * Reflection warnings on executeUpdate addressed. 330 | * HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](https://clojure.atlassian.net/browse/JDBC-94). 331 | * Add support for readonly transactions via :read-only? [JDBC-93](https://clojure.atlassian.net/browse/JDBC-93). 332 | 333 | * Release 0.3.4 on 2014-06-30 334 | * execute! can now accept a PreparedStatement [JDBC-96](https://clojure.atlassian.net/browse/JDBC-96). 335 | * Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](https://clojure.atlassian.net/browse/JDBC-92). 336 | * Support oracle:oci and oracle:thin subprotocols [JDBC-90](https://clojure.atlassian.net/browse/JDBC-90). 337 | 338 | * Release 0.3.3 on 2014-01-30 339 | * Prevent exception/crash when query called with bare SQL string [JDBC-89](https://clojure.atlassian.net/browse/JDBC-89). 340 | * Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](https://clojure.atlassian.net/browse/JDBC-87). 341 | * Support key/value configuration from URI (Phil Hagelberg). 342 | 343 | * Release 0.3.2 on 2013-12-30 344 | * Add nil protocol implementation to ISQLParameter 345 | 346 | * Release 0.3.1 on 2013-12-29 (broken; use 0.3.2 instead) 347 | * Improve docstrings and add :arglists for better auto-generated documentation. 348 | * Make insert-sql private - technically a breaking change but it should never have been public: sorry folks! 349 | * Provide better protocol for setting parameters in prepared statements [JDBC-86](https://clojure.atlassian.net/browse/JDBC-86). 350 | * Fix parens in two deprecated tests [JDBC-85](https://clojure.atlassian.net/browse/JDBC-85). 351 | * Made create-table-ddl less aggressive about applying as-sql-name so only first name in a column spec is affected. 352 | 353 | * Release 0.3.0 on 2013-12-16 354 | * Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](https://clojure.atlassian.net/browse/JDBC-84). 355 | * Rename recently introduced test to ensure unique names [JDBC-83](https://clojure.atlassian.net/browse/JDBC-83). 356 | * Rename unused arguments in protocol implementation to support Android [JDBC-82](https://clojure.atlassian.net/browse/JDBC-82). 357 | * Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](https://clojure.atlassian.net/browse/JDBC-65). 358 | 359 | * Release 0.3.0-rc1 on 2013-12-12 360 | * Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](https://clojure.atlassian.net/browse/JDBC-81). 361 | * Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](https://clojure.atlassian.net/browse/JDBC-80). 362 | * Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](https://clojure.atlassian.net/browse/JDBC-79). 363 | * Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](https://clojure.atlassian.net/browse/JDBC-77). 364 | * Add support for :isolation in with-db-transaction [JDBC-75](https://clojure.atlassian.net/browse/JDBC-75). 365 | * Add :user as an alias for :username for DataSource connections [JDBC-74](https://clojure.atlassian.net/browse/JDBC-74). 366 | 367 | * Release 0.3.0-beta2 on 2013-11-24 368 | * **BREAKING CHANGES!** 369 | * The DSL namespaces introduced in 0.3.0-alpha1 have been retired - see [java-jdbc/dsl](https://github.com/seancorfield/jsql) for a migration path if you wish to continue using the DSL (although it is recommended you switch to another, more expressive DSL). 370 | * The older API (0.2.3) which was deprecated in earlier 0.3.0 builds has moved to `clojure.java.jdbc.deprecated` to help streamline the API for 0.3.0 and clean up the documentation. 371 | 372 | * Release 0.3.0-beta1 on 2013-11-03 373 | * query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](https://clojure.atlassian.net/browse/JDBC-72). 374 | * "h2" is recognized as a protocol shorthand for org.h2.Driver 375 | * Tests no longer use :1 literal [JDBC-71](https://clojure.atlassian.net/browse/JDBC-71). 376 | * Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](https://clojure.atlassian.net/browse/JDBC-69). 377 | * New db-query-with-resultset function replaces private `db-with-query-results*` and processes a raw ResultSet object [JDBC-63](https://clojure.atlassian.net/browse/JDBC-63). 378 | * Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40). 379 | 380 | * Release 0.3.0-alpha5 on 2013-09-15 381 | * DDL now supports entities naming strategy [JDBC-53](https://clojure.atlassian.net/browse/JDBC-53). 382 | * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](https://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). 383 | * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](https://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) 384 | * Added Leiningen support for easier development/testing (Maven is still the primary build tool). 385 | * Added create-index / drop-index DDL [JDBC-62](https://clojure.atlassian.net/browse/JDBC-62) - moquist 386 | * Make transaction? boolean optional in various `db-do-*` functions 387 | * Create clojure.java.jdbc.ddl namespace 388 | * Add create-table, drop-table, create-index and drop-index 389 | * Deprecate create-table, create-table-ddl and drop-table in main namespace 390 | * Update README to clarify PostgreSQL instructions. 391 | * Fix test suite for PostgreSQL [JDBC-59](https://clojure.atlassian.net/browser/JDBC-59) 392 | * Improve hooks for Oracle data type handling [JDBC-57](https://clojure.atlassian.net/browser/JDBC-57) 393 | * Fix reflection warnings [JDBC-55](https://clojure.atlassian.net/browser/JDBC-55) 394 | 395 | * Release 0.3.0-alpha4 on 2013-05-11 396 | * Fix connection leaks [JDBC-54](https://clojure.atlassian.net/browser/JDBC-54) 397 | * Allow order-by to accept empty sequence (and return empty string) 398 | 399 | * Release 0.3.0-alpha3 on 2013-05-04 400 | * Fix macro / import interaction by fully qualifying Connection type. 401 | 402 | * Release 0.3.0-alpha2 on 2013-05-03 403 | * Address [JDBC-51](https://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection 404 | * Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](https://clojure.atlassian.net/browse/JDBC-46) 405 | * Add :multi? to execute! so it can be used for repeated operations [JDBC-52](https://clojure.atlassian.net/browse/JDBC-52) 406 | * Reverted specialized handling of NULL values (reopens [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40)) 407 | * Rename :as-arrays to :as-arrays? since it is boolean 408 | * Add curried version of clojure.java.jdbc.sql/as-quoted-str 409 | * Officially deprecate resultset-seq 410 | 411 | * Release 0.3.0-alpha1 on 2013-04-07 412 | * MAJOR API OVERHAUL! 413 | * Most of the old 0.2.x API has been deprecated and a new, more idiomatic API introduced, along with a minimal DSL to generate basic SQL 414 | * Specifics: 415 | * Add insert!, query, update!, delete! and execute! high-level API [JDBC-20](https://clojure.atlassian.net/browse/JDBC-20) 416 | * Add optional SQL-generating DSL in clojure.java.jdbc.sql (implied by JDBC-20) 417 | * Add db- prefixed versions of low-level API 418 | * Add db-transaction macro 419 | * Add result-set-seq as replacement for resultset-seq (which will be deprecated) 420 | * Transaction now correctly rollback on non-Exception Throwables [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) 421 | * Rewrite old API functions in terms of new API, and deprecate old API [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) 422 | * Add :as-arrays to query / result-set-seq [JDBC-41](https://clojure.atlassian.net/browse/JDBC-41) 423 | * Better handling of NULL values [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40) and [JDBC-18](https://clojure.atlassian.net/browse/JDBC-18) 424 | Note: JDBC-40 is being reverted in 0.3.0-alpha2 because it introduces regressions in PostgreSQL 425 | * db-do-commands allows you to execute SQL without a transaction wrapping it [JDBC-38](https://clojure.atlassian.net/browse/JDBC-38) 426 | * Remove reflection warning from execute-batch 427 | * Add notes to README about 3rd party database driver dependencies 428 | * Add optional :identifiers argument to resultset-seq so you can explicitly pass in the naming strategy 429 | 430 | * Release 0.2.3 on 2012-06-18 431 | * as-str now treats a.b as two identifiers separated by . so quoting produces [a].[b] instead of [a.b] 432 | * Add :connection-uri option [JDBC-34](https://clojure.atlassian.net/browse/JDBC-34) 433 | 434 | * Release 0.2.2 on 2012-06-10 435 | * Handle Oracle unknown row count affected [JDBC-33](https://clojure.atlassian.net/browse/JDBC-33) 436 | * Handle jdbc: prefix in string db-specs [JDBC-32](https://clojure.atlassian.net/browse/JDBC-32) 437 | * Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](https://clojure.atlassian.net/browse/JDBC-31) 438 | 439 | * Release 0.2.1 on 2012-05-10 440 | * Result set performance enhancement (Juergen Hoetzel) [JDBC-29](https://clojure.atlassian.net/browse/JDBC-29) 441 | * Make do-prepared-return-keys (for Korma team) [JDBC-30](https://clojure.atlassian.net/browse/JDBC-30) 442 | 443 | * Release 0.2.0 on 2012-04-23 444 | * Merge internal namespace into main jdbc namespace [JDBC-19](https://clojure.atlassian.net/browse/JDBC-19) 445 | 446 | * Release 0.1.4 on 2012-04-15 447 | * Unwrap RTE for nested transaction exceptions (we already 448 | unwrapped top-level transaction RTEs). 449 | * Remove reflection warning unwrapping RunTimeException (Alan Malloy) 450 | 451 | * Release 0.1.3 on 2012-02-29 452 | * Fix generated keys inside transactions for SQLite3 [JDBC-26](https://clojure.atlassian.net/browse/JDBC-26) 453 | 454 | * Release 0.1.2 on 2012-02-29 455 | * Handle prepared statement params correctly [JDBC-23](https://clojure.atlassian.net/browse/JDBC-23) 456 | * Add support for SQLite3 [JDBC-26](https://clojure.atlassian.net/browse/JDBC-26) 457 | * Replace replicate (deprecated) with repeat [JDBC-27](https://clojure.atlassian.net/browse/JDBC-27) 458 | * Ensure MS SQL Server passes tests with both Microsoft and jTDS drivers 459 | * Build server now tests derby, hsqldb and sqlite by default 460 | * Update README per Stuart Sierra's outline for contrib projects 461 | 462 | * Release 0.1.1 on 2011-11-02 463 | * Accept string or URI in connection definition [JDBC-21](https://clojure.atlassian.net/browse/JDBC-21) 464 | * Allow driver, port and subprotocol to be deduced [JDBC-22](https://clojure.atlassian.net/browse/JDBC-22) 465 | 466 | * Release 0.1.0 on 2011-10-16 467 | * Remove dependence on deprecated structmap [JDBC-15](https://clojure.atlassian.net/browse/JDBC-15) 468 | 469 | * Release 0.0.7 on 2011-10-11 470 | * Rename duplicate columns [JDBC-9](https://clojure.atlassian.net/browse/JDBC-9) 471 | * Ensure do-preared traps invalid SQL [JDBC-16](https://clojure.atlassian.net/browse/JDBC-16) 472 | 473 | * Release 0.0.6 on 2011-08-04 474 | * Improve exception handling (unwrap RTE) 475 | * Don't use batch for update (causes exceptions on Apache Derby) [JDBC-12](https://clojure.atlassian.net/browse/JDBC-12) 476 | * Add test suite 477 | 478 | * Release 0.0.5 on 2011-07-18 479 | * Expose prepare-statement API 480 | * Allow with-query-results to accept a PreparedStatement or options for creating one, instead of SQL query string and parameters 481 | * Support databases that cannot return generated keys 482 | 483 | * Release 0.0.4 on 2011-07-17 484 | * Allow :table-spec {string} in create-table [JDBC-4](https://clojure.atlassian.net/browse/JDBC-4) 485 | * Remove reflection warnings [JDBC-8](https://clojure.atlassian.net/browse/JDBC-8) 486 | * Ensure transactions are not committed when Error occurs [JDBC-11](https://clojure.atlassian.net/browse/JDBC-11) 487 | 488 | * Release 0.0.3 on 2011-07-01 489 | * Key generation compatibility with MS SQL Server, PostgreSQL [JDBC-10](https://clojure.atlassian.net/browse/JDBC-10) 490 | 491 | * Release 0.0.2 on 2011-06-07 492 | * Clojure 1.2 compatibility [JDBC-7](https://clojure.atlassian.net/browse/JDBC-7) 493 | 494 | * Release 0.0.1 on 2011-05-07 495 | * Initial release 496 | 497 | * Changes from clojure.contrib.sql: 498 | * Expose print-... functions; no longer write exceptions to `\*out\*` 499 | * Define resultset-seq to replace clojure.core/resultset-seq 500 | * Add naming / quoting strategies (see [name mapping documentation](https://clojure.github.io/java.jdbc/doc/clojure/java/jdbc/NameMapping.html) 501 | * Return generated keys from insert operations, where possible 502 | * Add insert-record function 503 | * Clojure 1.3 compatibility 504 | 505 | Copyright and License 506 | ======================================== 507 | 508 | Copyright (c) Sean Corfield, Stephen Gilardi, 2011-2023. All rights reserved. The use and 509 | distribution terms for this software are covered by the Eclipse Public 510 | License 1.0 (https://opensource.org/license/epl-1-0/) which can 511 | be found in the file epl-v10.html at the root of this distribution. 512 | By using this software in any fashion, you are agreeing to be bound by 513 | the terms of this license. You must not remove this notice, or any 514 | other, from this software. 515 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/java/jdbc_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stephen C. Gilardi, Sean Corfield. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which 4 | ;; can be found in the file epl-v10.html at the root of this distribution. By 5 | ;; using this software in any fashion, you are agreeing to be bound by the 6 | ;; terms of this license. You must not remove this notice, or any other, 7 | ;; from this software. 8 | ;; 9 | ;; test_jdbc.clj 10 | ;; 11 | ;; This namespace contains tests that exercise the JDBC portion of java.jdbc 12 | ;; so these tests expect databases to be available. Embedded databases can 13 | ;; be tested without external infrastructure (Apache Derby, HSQLDB). Other 14 | ;; databases will be available for testing in different environments. The 15 | ;; available databases for testing can be configured below. 16 | ;; 17 | ;; scgilardi (gmail) 18 | ;; Created 13 September 2008 19 | ;; 20 | ;; seancorfield (gmail) 21 | ;; Migrated from clojure.contrib.test-sql 17 April 2011 22 | 23 | (ns clojure.java.jdbc-test 24 | (:require [clojure.test :refer [deftest is use-fixtures]] 25 | [clojure.java.jdbc :as sql] 26 | [clojure.string :as str])) 27 | 28 | (println "\nTesting with Clojure" (clojure-version)) 29 | (def with-spec? (try 30 | (require 'clojure.java.jdbc.spec) 31 | (require 'clojure.spec.test.alpha) 32 | ;; require this to workaround rebinding of report multi-fn 33 | (require 'clojure.test.check.clojure-test) 34 | (let [syms ((resolve 'clojure.spec.test.alpha/enumerate-namespace) 'clojure.java.jdbc)] 35 | ((resolve 'clojure.spec.test.alpha/instrument) syms)) 36 | (println "Instrumenting clojure.java.jdbc with clojure.spec") 37 | true 38 | (catch Exception _ 39 | false))) 40 | 41 | ;; Set test-databases according to whether you have the local database available: 42 | ;; Possible values so far: [:mysql :postgres :derby :hsqldb :mysql-str :postgres-str] 43 | ;; Apache Derby and HSQLDB can run without an external setup. 44 | (def test-databases 45 | (if-let [dbs (System/getenv "TEST_DBS")] 46 | (map keyword (.split dbs " ")) 47 | ;; enable more by default once the build server is equipped? 48 | [:derby :hsqldb :h2 :sqlite])) 49 | 50 | ;; MS SQL Server requires more specialized configuration: 51 | (def mssql-host (or (System/getenv "TEST_MSSQL_HOST") "127.0.0.1\\SQLEXPRESS")) 52 | (def mssql-port (or (System/getenv "TEST_MSSQL_PORT") "1433")) 53 | (def mssql-user (or (System/getenv "TEST_MSSQL_USER") "sa")) 54 | (def mssql-pass (or (System/getenv "TEST_MSSQL_PASS") "")) 55 | (def mssql-dbname (or (System/getenv "TEST_MSSQL_NAME") "clojure_test")) 56 | (def jtds-host (or (System/getenv "TEST_JTDS_HOST") mssql-host)) 57 | (def jtds-port (or (System/getenv "TEST_JTDS_PORT") mssql-port)) 58 | (def jtds-user (or (System/getenv "TEST_JTDS_USER") mssql-user)) 59 | (def jtds-pass (or (System/getenv "TEST_JTDS_PASS") mssql-pass)) 60 | (def jtds-dbname (or (System/getenv "TEST_JTDS_NAME") mssql-dbname)) 61 | 62 | ;; PostgreSQL host/port 63 | (def postgres-host (or (System/getenv "TEST_POSTGRES_HOST") "127.0.0.1")) 64 | (def postgres-port (or (System/getenv "TEST_POSTGRES_PORT") "5432")) 65 | 66 | ;; database connections used for testing: 67 | 68 | (def mysql-db {:dbtype "mysql" 69 | :dbname "clojure_test" 70 | :user "clojure_test" 71 | :password "clojure_test" 72 | :useSSL false}) 73 | 74 | (def derby-db {:dbtype "derby" 75 | :dbname "clojure_test_derby" 76 | :create true}) 77 | 78 | (def hsqldb-db {:dbtype "hsql" 79 | :dbname "clojure_test_hsqldb"}) 80 | 81 | ;; test with new (0.7.6) in-memory H2 database 82 | (def h2-db {:dbtype "h2:mem" 83 | :dbname "clojure_test_h2"}) 84 | 85 | (def sqlite-db {:dbtype "sqlite" 86 | :dbname "clojure_test_sqlite"}) 87 | 88 | (def postgres-db {:dbtype "postgres" 89 | :dbname "clojure_test" 90 | :host postgres-host 91 | :port postgres-port 92 | :user "clojure_test" 93 | :password "clojure_test"}) 94 | 95 | (def pgsql-db {:dbtype "pgsql" 96 | :dbname "clojure_test" 97 | :host postgres-host 98 | :port postgres-port 99 | :user "clojure_test" 100 | :password "clojure_test"}) 101 | 102 | (def mssql-db {:dbtype "mssql" 103 | :dbname mssql-dbname 104 | :host mssql-host 105 | :port mssql-port 106 | :user mssql-user 107 | :password mssql-pass}) 108 | 109 | (def jtds-db {:dbtype "jtds" 110 | :dbname jtds-dbname 111 | :host jtds-host 112 | :port jtds-port 113 | :user jtds-user 114 | :password jtds-pass}) 115 | 116 | ;; To test against the stringified DB connection settings: 117 | (def mysql-str-db 118 | "mysql://clojure_test:clojure_test@localhost:3306/clojure_test") 119 | 120 | (def mysql-jdbc-str-db 121 | "jdbc:mysql://clojure_test:clojure_test@localhost:3306/clojure_test") 122 | 123 | (def postgres-str-db 124 | "postgres://clojure_test:clojure_test@localhost/clojure_test") 125 | 126 | (defn- test-specs 127 | "Return a sequence of db-spec maps that should be used for tests" 128 | [] 129 | (for [db test-databases] 130 | @(ns-resolve 'clojure.java.jdbc-test (symbol (str (name db) "-db"))))) 131 | 132 | (defn- clean-up 133 | "Attempt to drop any test tables before we start a test." 134 | [t] 135 | (doseq [db (test-specs)] 136 | (doseq [table [:fruit :fruit2 :veggies :veggies2]] 137 | (try 138 | (sql/db-do-commands db (sql/drop-table-ddl table)) 139 | (catch java.sql.SQLException _)))) 140 | ;; ignore 141 | 142 | (t)) 143 | 144 | (use-fixtures 145 | :each clean-up) 146 | 147 | ;; We start with all tables dropped and each test has to create the tables 148 | ;; necessary for it to do its job, and populate it as needed... 149 | 150 | (defn- string->type [db] 151 | (last (re-find #"^(jdbc:)?([^:]+):" db))) 152 | 153 | (defn- db-type [db] 154 | (or (:subprotocol db) (:dbtype db) 155 | (and (string? db) (string->type db)) 156 | (and (:connection-uri db) (string->type (:connection-uri db))))) 157 | 158 | (defn- derby? [db] 159 | (= "derby" (db-type db))) 160 | 161 | (defn- hsqldb? [db] 162 | (#{"hsql" "hsqldb"} (db-type db))) 163 | 164 | (defn- mssql? [db] 165 | (#{"jtds" "jtds:sqlserver" "mssql" "sqlserver"} (db-type db))) 166 | 167 | (defn- mysql? [db] 168 | (= "mysql" (db-type db))) 169 | 170 | (defn- postgres? [db] 171 | (#{"postgres" "pgsql"} (db-type db))) 172 | 173 | (defn- pgsql? [db] 174 | (= "pgsql" (db-type db))) 175 | 176 | (defn- sqlite? [db] 177 | (= "sqlite" (db-type db))) 178 | 179 | (defmulti create-test-table 180 | "Create a standard test table. Uses db-do-commands. 181 | For MySQL, ensure table uses an engine that supports transactions!" 182 | (fn [table db] 183 | (cond 184 | (mysql? db) :mysql 185 | (postgres? db) :postgres 186 | :else :default))) 187 | 188 | (defmethod create-test-table :mysql 189 | [table db] 190 | (sql/db-do-commands 191 | db (sql/create-table-ddl 192 | table 193 | [[:id :int "PRIMARY KEY AUTO_INCREMENT"] 194 | [:name "VARCHAR(32)"] 195 | [:appearance "VARCHAR(32)"] 196 | [:cost :int] 197 | [:grade :real]] 198 | {:table-spec "ENGINE=InnoDB"}))) 199 | 200 | (defmethod create-test-table :postgres 201 | [table db] 202 | (sql/db-do-commands 203 | db (sql/create-table-ddl 204 | table 205 | [[:id :serial "PRIMARY KEY"] 206 | [:name "VARCHAR(32)"] 207 | [:appearance "VARCHAR(32)"] 208 | [:cost :int] 209 | [:grade :real]] 210 | {:table-spec ""}))) 211 | 212 | (defmethod create-test-table :default 213 | [table db] 214 | (sql/db-do-commands 215 | db (sql/create-table-ddl 216 | table 217 | [[:id :int "DEFAULT 0"] 218 | [:name "VARCHAR(32)" "PRIMARY KEY"] 219 | [:appearance "VARCHAR(32)"] 220 | [:cost :int] 221 | [:grade :real]] 222 | {:table-spec ""}))) 223 | 224 | (deftest test-drop-table-ddl 225 | (is (= "DROP TABLE something" (sql/drop-table-ddl :something)))) 226 | 227 | (deftest test-uri-spec-parsing 228 | (is (= {:advanced "false" :ssl "required" :password "clojure_test" 229 | :user "clojure_test" :subname "//localhost/clojure_test" 230 | :subprotocol "postgresql"} 231 | (@#'sql/parse-properties-uri 232 | (java.net.URI. 233 | (str "postgres://clojure_test:clojure_test@localhost/clojure_test?" 234 | "ssl=required&advanced=false"))))) 235 | (is (= {:password "clojure_test" :user "clojure_test" 236 | :subname "//localhost:3306/clojure_test", :subprotocol "mysql"} 237 | (@#'sql/parse-properties-uri 238 | (java.net.URI. 239 | "mysql://clojure_test:clojure_test@localhost:3306/clojure_test"))))) 240 | 241 | (defn- returned-key [db k] 242 | (case (db-type db) 243 | "derby" {(keyword "1") nil} 244 | ("hsql" "hsqldb") nil 245 | ("h2" "h2:mem") {:id 0} 246 | "mysql" {:generated_key k} 247 | nil (if (mysql? db) ; string-based tests 248 | {:generated_key k} 249 | k) 250 | ("jtds" "jtds:sqlserver") {:id nil} 251 | ("mssql" "sqlserver") {:generated_keys nil} 252 | "sqlite" {(keyword "last_insert_rowid()") k} 253 | k)) 254 | 255 | (defn- select-key [db] 256 | (case (db-type db) 257 | ("postgres" "postgresql" "pgsql") :id 258 | identity)) 259 | 260 | (defn- generated-key [db k] 261 | (case (db-type db) 262 | "derby" 0 263 | ("hsql" "hsqldb") 0 264 | ("h2" "h2:mem") 0 265 | ("jtds" "jtds:sqlserver") 0 266 | ("mssql" "sqlserver") 0 267 | "sqlite" 0 268 | k)) 269 | 270 | (defn- float-or-double [db v] 271 | (case (db-type db) 272 | "derby" (Float. v) 273 | ("h2" "h2:mem") (Float. v) 274 | ("jtds" "jtds:sqlserver") (Float. v) 275 | ("mssql" "sqlserver") (Float. v) 276 | ("postgres" "postgresql" "pgsql") (Float. v) 277 | v)) 278 | 279 | (deftest test-create-table 280 | (doseq [db (test-specs)] 281 | (create-test-table :fruit db) 282 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 283 | 284 | (deftest test-drop-table 285 | (doseq [db (test-specs)] 286 | (create-test-table :fruit2 db) 287 | (sql/db-do-commands db (sql/drop-table-ddl :fruit2)) 288 | (is (thrown? java.sql.SQLException 289 | (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 290 | 291 | (deftest test-do-commands 292 | (doseq [db (test-specs)] 293 | (create-test-table :fruit2 db) 294 | (sql/db-do-commands db "DROP TABLE fruit2") 295 | (is (thrown? java.sql.SQLException 296 | (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 297 | 298 | (deftest test-do-commands-transaction 299 | (doseq [db (test-specs)] 300 | (create-test-table :fruit2 db) 301 | (sql/db-do-commands db true "DROP TABLE fruit2") 302 | (is (thrown? java.sql.SQLException 303 | (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 304 | 305 | (deftest test-do-commands-multi 306 | (doseq [db (test-specs)] 307 | (sql/db-do-commands db 308 | [(sql/create-table-ddl :fruit3 309 | [[:name "VARCHAR(32)"] 310 | [:appearance "VARCHAR(32)"] 311 | [:cost :int]]) 312 | "DROP TABLE fruit3"]) 313 | (is (thrown? java.sql.SQLException 314 | (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 315 | 316 | (deftest test-do-commands-multi-transaction 317 | (doseq [db (test-specs)] 318 | (sql/db-do-commands db true 319 | [(sql/create-table-ddl :fruit3 320 | [[:name "VARCHAR(32)"] 321 | [:appearance "VARCHAR(32)"] 322 | [:cost :int]]) 323 | "DROP TABLE fruit3"]) 324 | (is (thrown? java.sql.SQLException 325 | (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 326 | 327 | (deftest test-do-prepared1a 328 | (doseq [db (test-specs)] 329 | (create-test-table :fruit2 db) 330 | ;; single string is acceptable 331 | (sql/db-do-prepared db "INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )") 332 | (is (= 1 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 333 | 334 | (deftest test-do-prepared1b 335 | (doseq [db (test-specs)] 336 | (create-test-table :fruit2 db) 337 | (with-open [con (sql/get-connection db)] 338 | (let [stmt (sql/prepare-statement con "INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )")] 339 | ;; single PreparedStatement is acceptable 340 | (is (= [1] (sql/db-do-prepared db stmt))))) 341 | (is (= 1 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 342 | 343 | (deftest test-do-prepared1ci 344 | (doseq [db (test-specs)] 345 | (create-test-table :fruit2 db) 346 | (is (= (returned-key db 1) 347 | ((select-key db) (sql/db-do-prepared-return-keys db "INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )")))) 348 | (is (= 1 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 349 | 350 | (deftest test-do-prepared1cii 351 | (doseq [db (test-specs)] 352 | (create-test-table :fruit2 db) 353 | (is (= (returned-key db 1) 354 | ((select-key db) (sql/db-do-prepared-return-keys db ["INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )"])))) 355 | (is (= 1 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 356 | 357 | (deftest test-do-prepared1di 358 | (doseq [db (test-specs)] 359 | (create-test-table :fruit db) 360 | (with-open [con (sql/get-connection db)] 361 | (let [stmt (sql/prepare-statement con "INSERT INTO fruit ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )" 362 | {:return-keys true})] 363 | (is (= (returned-key db 1) 364 | ((select-key db) (sql/db-do-prepared-return-keys db [stmt])))))) 365 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 366 | 367 | (deftest test-do-prepared1dii 368 | (doseq [db (test-specs)] 369 | (create-test-table :fruit db) 370 | (with-open [con (sql/get-connection db)] 371 | (let [stmt (sql/prepare-statement con "INSERT INTO fruit ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )" 372 | {:return-keys true})] 373 | (is (= (returned-key db 1) 374 | ((select-key db) (sql/db-do-prepared-return-keys db stmt)))))) 375 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 376 | 377 | (deftest test-do-prepared1e 378 | (doseq [db (test-specs)] 379 | ;; Derby/SQL Server does not have auto-generated id column which we're testing here 380 | (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) 381 | (create-test-table :fruit db) 382 | (with-open [con (sql/get-connection db)] 383 | (let [stmt (sql/prepare-statement con "INSERT INTO fruit ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )" 384 | {:return-keys ["id"]})] 385 | ;; HSQLDB returns the named key if you ask 386 | (is (= (if (hsqldb? db) {:id 0} (returned-key db 1)) 387 | ((select-key db) (sql/db-do-prepared-return-keys db stmt)))))) 388 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count})))))) 389 | 390 | (deftest test-do-prepared2 391 | (doseq [db (test-specs)] 392 | (create-test-table :fruit2 db) 393 | (sql/db-do-prepared db "DROP TABLE fruit2") 394 | (is (thrown? java.sql.SQLException 395 | (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 396 | 397 | (deftest test-do-prepared3a 398 | (doseq [db (test-specs)] 399 | (create-test-table :fruit2 db) 400 | (sql/db-do-prepared db ["INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( ?, ?, ?, ? )" ["test" "test" 1 1.0]] {:multi? true}) 401 | (is (= 1 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 402 | 403 | (deftest test-do-prepared3b 404 | (doseq [db (test-specs)] 405 | (create-test-table :fruit2 db) 406 | (sql/db-do-prepared db ["INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( ?, ?, ?, ? )" "test" "test" 1 1.0]) 407 | (is (= 1 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 408 | 409 | (deftest test-do-prepared4 410 | (doseq [db (test-specs)] 411 | (create-test-table :fruit2 db) 412 | (sql/db-do-prepared db ["INSERT INTO fruit2 ( name, appearance, cost, grade ) VALUES ( ?, ?, ?, ? )" ["test" "test" 1 1.0] ["two" "two" 2 2.0]] {:multi? true}) 413 | (is (= 2 (sql/query db ["SELECT * FROM fruit2"] {:result-set-fn count}))))) 414 | 415 | (deftest test-insert-rows 416 | (doseq [db (test-specs)] 417 | (create-test-table :fruit db) 418 | (let [r (sql/insert-multi! db 419 | :fruit 420 | nil 421 | [[1 "Apple" "red" 59 87] 422 | [2 "Banana" "yellow" 29 92.2] 423 | [3 "Peach" "fuzzy" 139 90.0] 424 | [4 "Orange" "juicy" 139 88.6]])] 425 | (is (= '(1 1 1 1) r))) 426 | (is (= 4 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 427 | (is (= 4 (sql/with-db-connection [con db {}] 428 | (sql/query con (sql/prepare-statement (sql/db-connection con) "SELECT * FROM fruit") {:result-set-fn count})))) 429 | (when-not (pgsql? db) 430 | ;; maxRows does not appear to be supported on Impossibl pgsql? 431 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count :max-rows 2}))) 432 | (is (= 2 (sql/with-db-connection [con db] 433 | (sql/query con [(sql/prepare-statement (sql/db-connection con) "SELECT * FROM fruit" {:max-rows 2})] {:result-set-fn count}))))) 434 | (is (= "Apple" (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"] {:row-fn :name :result-set-fn first}))) 435 | (is (= "juicy" (sql/query db ["SELECT * FROM fruit WHERE name = ?" "Orange"] {:row-fn :appearance :result-set-fn first}))) 436 | (is (= "Apple" (:name (sql/get-by-id db :fruit 1)))) 437 | (is (= ["Apple"] (map :name (sql/find-by-keys db :fruit {:appearance "red"})))) 438 | (is (= "Peach" (:name (sql/get-by-id db :fruit 3 :id)))) 439 | (is (= ["Peach"] (map :name (sql/find-by-keys db :fruit {:id 3 :cost 139})))) 440 | (is (= ["Peach" "Orange"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [:id]})))) 441 | (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))) 442 | ;; reduce with init (and ensure we can pass :fetch-size & connection opts through) 443 | (is (= 466 (reduce (fn [n r] (+ n (:cost r))) 100 444 | (sql/reducible-query db "SELECT * FROM fruit" 445 | (cond-> {:fetch-size 100 :raw? true} 446 | (not (sqlite? db)) 447 | (assoc :read-only? true) 448 | (not (derby? db)) 449 | (assoc :auto-commit? false)))))) 450 | ;; reduce without init -- uses first row as init! 451 | (is (= 366 452 | (:cost (reduce (fn 453 | ([] (throw (ex-info "I should not be called!" {}))) 454 | ([m r] (update-in m [:cost] + (:cost r)))) 455 | (sql/reducible-query db "SELECT * FROM fruit"))))) 456 | ;; verify reduce without init on empty rs calls 0-arity only 457 | (is (= "Zero-arity!" 458 | (reduce (fn 459 | ([] "Zero-arity!") 460 | ([m r] (throw (ex-info "I should not be called!" 461 | {:m m :r r})))) 462 | (sql/reducible-query db "SELECT * FROM fruit WHERE ID = -99")))) 463 | ;; verify reduce with init does not call f for empty rs 464 | (is (= "Unchanged!" 465 | (reduce (fn 466 | ([] (throw (ex-info "I should not be called!" {}))) 467 | ([m r] (throw (ex-info "I should not be called!" 468 | {:m m :r r})))) 469 | "Unchanged!" 470 | (sql/reducible-query db "SELECT * FROM fruit WHERE ID = -99")))) 471 | ;; verify reduce without init does not call f if only one row is in the rs 472 | (is (= "Orange" 473 | (:name (reduce (fn 474 | ([] (throw (ex-info "I should not be called!" {}))) 475 | ([m r] (throw (ex-info "I should not be called!" 476 | {:m m :r r})))) 477 | (sql/reducible-query db "SELECT * FROM fruit WHERE ID = 4"))))) 478 | ;; verify reduce with init does not call 0-arity f and 479 | ;; only calls 2-arity f once if only one row is in the rs 480 | (is (= 239 481 | (reduce (fn 482 | ([] (throw (ex-info "I should not be called!" {}))) 483 | ;; cannot be called on its own result: 484 | ([m r] (+ (:a m) (:cost r)))) 485 | {:a 100} 486 | (sql/reducible-query db "SELECT * FROM fruit WHERE ID = 4")))) 487 | ;; plain old into (uses (reduce conj coll) behind the scenes) 488 | (is (= 4 (count (into [] (sql/reducible-query db "SELECT * FROM fruit"))))) 489 | ;; transducing into 490 | (is (= [29 59 139 139] 491 | (into [] 492 | (map :cost) 493 | (sql/reducible-query db (str "SELECT * FROM fruit" 494 | " ORDER BY cost") 495 | {:raw? true})))) 496 | ;; transduce without init (calls (+) to get init value) 497 | (is (= 366 (transduce (map :cost) + 498 | (sql/reducible-query db "SELECT * FROM fruit")))) 499 | ;; transduce with init 500 | (is (= 466 (transduce (map :cost) + 100 501 | (sql/reducible-query db "SELECT * FROM fruit")))))) 502 | 503 | (deftest test-insert-values 504 | (doseq [db (test-specs)] 505 | (create-test-table :fruit db) 506 | (let [r (sql/insert-multi! db 507 | :fruit 508 | [:name :cost] 509 | [["Mango" 722] 510 | ["Feijoa" 441]])] 511 | (is (= '(1 1) r))) 512 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 513 | (is (= "Mango" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 722] {:row-fn :name :result-set-fn first}))))) 514 | 515 | (deftest test-insert-records 516 | (doseq [db (test-specs)] 517 | (create-test-table :fruit db) 518 | (let [r (map (select-key db) (sql/insert-multi! db 519 | :fruit 520 | [{:name "Pomegranate" :appearance "fresh" :cost 585} 521 | {:name "Kiwifruit" :grade 93}]))] 522 | (is (= (list (returned-key db 1) (returned-key db 2)) r))) 523 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 524 | (is (= "Pomegranate" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 585] {:row-fn :name :result-set-fn first}))))) 525 | 526 | (deftest test-insert-via-execute 527 | (doseq [db (test-specs)] 528 | (create-test-table :fruit db) 529 | (sql/execute! db [(str "INSERT INTO fruit ( name, appearance, cost ) " 530 | "VALUES ( ?, ?, ? )") 531 | "Apple" "Green" 75]) 532 | (sql/execute! db [(str "INSERT INTO fruit ( name, appearance, cost ) " 533 | "VALUES ( 'Pear', 'Yellow', 99 )")]) 534 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 535 | (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] 536 | {:row-fn :name :result-set-fn first}))))) 537 | 538 | (deftest execute-with-prepared-statement 539 | (doseq [db (test-specs)] 540 | (create-test-table :fruit db) 541 | (sql/with-db-connection [conn db] 542 | (let [connection (:connection conn) 543 | prepared-statement (sql/prepare-statement connection (str "INSERT INTO fruit ( name, appearance, cost ) " 544 | "VALUES ( ?, ?, ? )"))] 545 | 546 | (sql/execute! db [prepared-statement "Apple" "Green" 75]) 547 | (sql/execute! db [prepared-statement "Pear" "Yellow" 99]))) 548 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 549 | (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] 550 | {:row-fn :name :result-set-fn first}))))) 551 | 552 | (deftest execute-with-prepared-statement-with-return-keys 553 | (doseq [db (test-specs)] 554 | ;; Derby/SQL Server does not have auto-generated id column which we're testing here 555 | (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) 556 | (create-test-table :fruit db) 557 | (sql/with-db-connection [conn db] 558 | (let [connection (:connection conn) 559 | ;; although we ask for keys to come back, execute! cannot see into 560 | ;; the PreparedStatement so it doesn't know to call things in a 561 | ;; different way, so we get affected row counts instead! 562 | prepared-statement (sql/prepare-statement connection (str "INSERT INTO fruit ( name, appearance, cost ) " 563 | "VALUES ( ?, ?, ? )") 564 | {:return-keys ["id"]})] 565 | ;; what is returned is affected row counts due to how execute! works 566 | (is (= [1] (sql/execute! db [prepared-statement "Apple" "Green" 75]))) 567 | (is (= [1] (sql/execute! db [prepared-statement "Pear" "Yellow" 99]))))) 568 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 569 | (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] 570 | {:row-fn :name :result-set-fn first})))))) 571 | 572 | (deftest execute-with-return-keys-option 573 | (doseq [db (test-specs)] 574 | ;; Derby/SQL Server does not have auto-generated id column which we're testing here 575 | (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) 576 | (create-test-table :fruit db) 577 | (sql/with-db-connection [conn db] 578 | (let [sql-stmt (str "INSERT INTO fruit ( name, appearance, cost ) " 579 | "VALUES ( ?, ?, ? )") 580 | selector (select-key db)] 581 | ;; HSQLDB returns the named key if you ask 582 | (is (= (if (hsqldb? db) {:id 0} (returned-key db 1)) 583 | (selector (sql/execute! db [sql-stmt "Apple" "Green" 75] 584 | {:return-keys ["id"]})))) 585 | ;; HSQLDB returns the named key if you ask 586 | (is (= (if (hsqldb? db) {:id 0} (returned-key db 2)) 587 | (sql/execute! db [sql-stmt "Pear" "Yellow" 99] 588 | {:return-keys ["id"] 589 | :row-fn selector}))))) 590 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 591 | (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] 592 | {:row-fn :name :result-set-fn first})))))) 593 | 594 | (deftest test-nested-with-connection 595 | (doseq [db (test-specs)] 596 | (create-test-table :fruit db) 597 | (sql/with-db-connection [conn1 db] 598 | (sql/query conn1 "select * from fruit") 599 | (sql/with-db-connection [conn2 conn1] 600 | (sql/query conn2 "select * from fruit")) 601 | ;; JDBC-171 bug: this blows up because with-db-connection won't nest 602 | (is (= [] (sql/query conn1 "select * from fruit")))))) 603 | 604 | (deftest test-update-values 605 | (doseq [db (test-specs)] 606 | (create-test-table :fruit db) 607 | (let [r (sql/insert-multi! db 608 | :fruit 609 | nil 610 | [[1 "Apple" "red" 59 87] 611 | [2 "Banana" "yellow" 29 92.2] 612 | [3 "Peach" "fuzzy" 139 90.0] 613 | [4 "Orange" "juicy" 89 88.6]])] 614 | (is (= '(1 1 1 1) r))) 615 | (sql/update! db 616 | :fruit 617 | {:appearance "bruised" :cost 14} 618 | ["name=?" "Banana"]) 619 | (is (= 4 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 620 | (is (= "Apple" (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"] 621 | {:row-fn :name :result-set-fn first}))) 622 | (is (= "Banana" (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "bruised"] 623 | {:row-fn :name :result-set-fn first}))) 624 | (is (= 14 (sql/query db ["SELECT * FROM fruit WHERE name = ?" "Banana"] 625 | {:row-fn :cost :result-set-fn first}))))) 626 | 627 | (defn update-or-insert-values 628 | [db table row where] 629 | (sql/with-db-transaction [t-conn db] 630 | (let [result (sql/update! t-conn table row where)] 631 | (if (zero? (first result)) 632 | (sql/insert! t-conn table row) 633 | result)))) 634 | 635 | (defn update-or-insert-values-with-isolation 636 | [db table row where] 637 | (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] 638 | (let [result (sql/update! t-conn table row where)] 639 | (if (zero? (first result)) 640 | (sql/insert! t-conn table row) 641 | result)))) 642 | 643 | (deftest test-update-or-insert-values 644 | (doseq [db (test-specs)] 645 | (create-test-table :fruit db) 646 | (update-or-insert-values db 647 | :fruit 648 | {:name "Pomegranate" :appearance "fresh" :cost 585} 649 | ["name=?" "Pomegranate"]) 650 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 651 | (is (= 585 (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "fresh"] 652 | {:row-fn :cost :result-set-fn first}))) 653 | (update-or-insert-values db 654 | :fruit 655 | {:name "Pomegranate" :appearance "ripe" :cost 565} 656 | ["name=?" "Pomegranate"]) 657 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 658 | (is (= 565 (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "ripe"] 659 | {:row-fn :cost :result-set-fn first}))) 660 | (update-or-insert-values db 661 | :fruit 662 | {:name "Apple" :appearance "green" :cost 74} 663 | ["name=?" "Apple"]) 664 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 665 | 666 | (deftest test-update-or-insert-values-with-isolation 667 | (doseq [db (test-specs)] 668 | (create-test-table :fruit db) 669 | (update-or-insert-values-with-isolation db 670 | :fruit 671 | {:name "Pomegranate" :appearance "fresh" :cost 585} 672 | ["name=?" "Pomegranate"]) 673 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 674 | (is (= 585 (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "fresh"] 675 | {:row-fn :cost :result-set-fn first}))) 676 | (update-or-insert-values db 677 | :fruit 678 | {:name "Pomegranate" :appearance "ripe" :cost 565} 679 | ["name=?" "Pomegranate"]) 680 | (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) 681 | (is (= 565 (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "ripe"] 682 | {:row-fn :cost :result-set-fn first}))) 683 | (update-or-insert-values db 684 | :fruit 685 | {:name "Apple" :appearance "green" :cost 74} 686 | ["name=?" "Apple"]) 687 | (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 688 | 689 | (defn- file-not-found-exception-via-reflection 690 | "In Clojure 1.3.0 this caused a wrapped exception and we introduced throw-non-rte 691 | to workaround that. This was fixed in 1.4.0 but we never removed the workaround. 692 | Added this hack from the mailing list specifically to test the exception handling 693 | so that we can verify only Clojure 1.3.0 fails the tests and drop support for it." 694 | [f] 695 | (java.io.FileReader. f)) 696 | 697 | (deftest test-partial-exception 698 | (doseq [db (test-specs)] 699 | (create-test-table :fruit db) 700 | (try 701 | (sql/with-db-transaction [t-conn db] 702 | (sql/insert-multi! t-conn 703 | :fruit 704 | [:name :appearance] 705 | [["Grape" "yummy"] 706 | ["Pear" "bruised"]]) 707 | (is (= 2 (sql/query t-conn ["SELECT * FROM fruit"] {:result-set-fn count}))) 708 | (file-not-found-exception-via-reflection "/etc/password_no_such_file")) 709 | (catch java.io.FileNotFoundException _ 710 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count})))) 711 | (catch Exception _ 712 | (is false "Unexpected exception encountered (not wrapped?)."))))) 713 | 714 | (deftest test-partial-exception-with-isolation 715 | (doseq [db (test-specs)] 716 | (create-test-table :fruit db) 717 | (try 718 | (sql/with-db-transaction [t-conn db {:isolation :serializable}] 719 | (sql/insert-multi! t-conn 720 | :fruit 721 | [:name :appearance] 722 | [["Grape" "yummy"] 723 | ["Pear" "bruised"]]) 724 | (is (= 2 (sql/query t-conn ["SELECT * FROM fruit"] {:result-set-fn count}))) 725 | (throw (Exception. "deliberate exception"))) 726 | (catch Exception _ 727 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))))) 728 | 729 | (defmacro illegal-arg-or-spec 730 | "Execute a form in the context of a try/catch that verifies either 731 | IllegalArgumentException was thrown or a spec violation occurred 732 | so that we can test transparently across Clojure 1.7 to 1.9+." 733 | [fn-name & body] 734 | `(try 735 | ~@body 736 | (is false (str "Illegal arguments to " ~fn-name " were not detected!")) 737 | (catch IllegalArgumentException _#) 738 | (catch clojure.lang.ExceptionInfo e# 739 | (is (re-find #"did not conform to spec" (.getMessage e#)))))) 740 | 741 | (deftest test-sql-exception 742 | (doseq [db (test-specs)] 743 | (create-test-table :fruit db) 744 | (illegal-arg-or-spec "insert!" 745 | (sql/with-db-transaction [t-conn db] 746 | (sql/insert! t-conn 747 | :fruit 748 | [:name :appearance] 749 | ["Apple" "strange" "whoops"]))) 750 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 751 | 752 | (deftest test-sql-exception-with-isolation 753 | (doseq [db (test-specs)] 754 | (create-test-table :fruit db) 755 | (illegal-arg-or-spec "insert!" 756 | (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] 757 | (sql/insert! t-conn 758 | :fruit 759 | [:name :appearance] 760 | ["Apple" "strange" "whoops"]))) 761 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 762 | 763 | (deftest test-insert-values-exception 764 | (doseq [db (test-specs)] 765 | (create-test-table :fruit db) 766 | (illegal-arg-or-spec "insert-multi!" 767 | (sql/with-db-transaction [t-conn db] 768 | (sql/insert-multi! t-conn 769 | :fruit 770 | [:name :appearance] 771 | [["Grape" "yummy"] 772 | ["Pear" "bruised"] 773 | ["Apple" "strange" "whoops"]]))) 774 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 775 | 776 | (deftest test-insert-values-exception-with-isolation 777 | (doseq [db (test-specs)] 778 | (create-test-table :fruit db) 779 | (illegal-arg-or-spec 780 | (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] 781 | (sql/insert-multi! t-conn 782 | :fruit 783 | [:name :appearance] 784 | [["Grape" "yummy"] 785 | ["Pear" "bruised"] 786 | ["Apple" "strange" "whoops"]]))) 787 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 788 | 789 | (deftest test-rollback 790 | (doseq [db (test-specs)] 791 | (create-test-table :fruit db) 792 | (try 793 | (sql/with-db-transaction [t-conn db] 794 | (is (not (sql/db-is-rollback-only t-conn))) 795 | (sql/db-set-rollback-only! t-conn) 796 | (is (sql/db-is-rollback-only t-conn)) 797 | (sql/insert-multi! t-conn 798 | :fruit 799 | [:name :appearance] 800 | [["Grape" "yummy"] 801 | ["Pear" "bruised"] 802 | ["Apple" "strange"]]) 803 | (is (= 3 (sql/query t-conn ["SELECT * FROM fruit"] {:result-set-fn count})))) 804 | (catch java.sql.SQLException _ 805 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 806 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 807 | 808 | (deftest test-rollback-with-isolation 809 | (doseq [db (test-specs)] 810 | (create-test-table :fruit db) 811 | (try 812 | (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] 813 | (is (not (sql/db-is-rollback-only t-conn))) 814 | (sql/db-set-rollback-only! t-conn) 815 | (is (sql/db-is-rollback-only t-conn)) 816 | (sql/insert-multi! t-conn 817 | :fruit 818 | [:name :appearance] 819 | [["Grape" "yummy"] 820 | ["Pear" "bruised"] 821 | ["Apple" "strange"]]) 822 | (is (= 3 (sql/query t-conn ["SELECT * FROM fruit"] {:result-set-fn count})))) 823 | (catch java.sql.SQLException _ 824 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 825 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 826 | 827 | (deftest test-transactions-with-possible-generated-keys-result-set 828 | (doseq [db (test-specs)] 829 | (create-test-table :fruit db) 830 | (sql/with-db-transaction [t-conn db] 831 | (sql/db-set-rollback-only! t-conn) 832 | (sql/insert! t-conn 833 | :fruit 834 | [:name :appearance] 835 | ["Grape" "yummy"]) 836 | (is (= 1 (sql/query t-conn ["SELECT * FROM fruit"] {:result-set-fn count})))) 837 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 838 | 839 | (deftest test-transactions-with-possible-generated-keys-result-set-and-isolation 840 | (doseq [db (test-specs)] 841 | (create-test-table :fruit db) 842 | (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] 843 | (sql/db-set-rollback-only! t-conn) 844 | (sql/insert! t-conn 845 | :fruit 846 | [:name :appearance] 847 | ["Grape" "yummy"]) 848 | (is (= 1 (sql/query t-conn ["SELECT * FROM fruit"] {:result-set-fn count})))) 849 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 850 | 851 | (deftest test-nested-transactions-check-transaction-isolation-level 852 | (doseq [db (test-specs)] 853 | (create-test-table :fruit db) 854 | (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] 855 | (is (thrown? IllegalStateException 856 | (sql/with-db-transaction [t-conn' t-conn {:isolation :serializable}] 857 | (sql/insert! t-conn' 858 | :fruit 859 | [:name :appearance] 860 | ["Grape" "yummy"]))))) 861 | (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) 862 | 863 | (deftest test-raw-metadata 864 | (doseq [db (test-specs)] 865 | (create-test-table :fruit db) 866 | (let [table-info (with-open [conn (sql/get-connection db)] 867 | (into [] 868 | (sql/result-set-seq 869 | (-> conn 870 | (.getMetaData) 871 | (.getTables nil nil nil 872 | (into-array ["TABLE" "VIEW"]))))))] 873 | (is (not= [] table-info)) 874 | (is (= "fruit" (-> table-info 875 | first 876 | :table_name 877 | clojure.string/lower-case)))))) 878 | 879 | (deftest test-metadata-managed 880 | (doseq [db (test-specs)] 881 | (create-test-table :fruit db) 882 | (sql/with-db-metadata [metadata db {}] 883 | (let [table-info (sql/metadata-query (.getTables metadata 884 | nil nil nil 885 | (into-array ["TABLE" "VIEW"])))] 886 | (is (not= [] table-info)) 887 | (is (= "fruit" (-> table-info 888 | first 889 | :table_name 890 | clojure.string/lower-case)))) 891 | (sql/with-db-connection [conn db] 892 | (sql/with-db-metadata [metadata conn {}] 893 | (let [table-info (sql/metadata-query (.getTables metadata 894 | nil nil nil 895 | (into-array ["TABLE" "VIEW"])))] 896 | (is (not= [] table-info)) 897 | (is (= "fruit" (-> table-info 898 | first 899 | :table_name 900 | clojure.string/lower-case))))) 901 | ;; JDBC-171 this used to blow up because the connnection is closed 902 | (sql/with-db-metadata [metadata conn {}] 903 | (let [table-info (sql/metadata-query (.getTables metadata 904 | nil nil nil 905 | (into-array ["TABLE" "VIEW"])))] 906 | (is (not= [] table-info)) 907 | (is (= "fruit" (-> table-info 908 | first 909 | :table_name 910 | clojure.string/lower-case))))))))) 911 | 912 | (deftest test-metadata-managed-computed 913 | (doseq [db (test-specs)] 914 | (create-test-table :fruit db) 915 | (is (= "fruit" 916 | (sql/with-db-metadata [metadata db] 917 | (sql/metadata-query (.getTables metadata 918 | nil nil nil 919 | (into-array ["TABLE" "VIEW"])) 920 | {:row-fn (comp clojure.string/lower-case str :table_name) 921 | :result-set-fn first})))))) 922 | 923 | (deftest test-metadata 924 | (doseq [db (test-specs)] 925 | (create-test-table :fruit db) 926 | (sql/with-db-metadata [metadata db] 927 | ;; make sure to close the ResultSet 928 | (with-open [table-info-result (.getTables metadata 929 | nil nil nil 930 | (into-array ["TABLE" "VIEW"]))] 931 | (let [table-info (sql/metadata-result table-info-result)] 932 | (is (not= [] table-info)) 933 | (is (= "fruit" (-> table-info 934 | first 935 | :table_name 936 | clojure.string/lower-case)))))))) 937 | 938 | (deftest empty-query 939 | (doseq [db (test-specs)] 940 | (create-test-table :fruit db) 941 | (is (= [] (sql/query db ["SELECT * FROM fruit"]))))) 942 | 943 | (deftest query-with-string 944 | (doseq [db (test-specs)] 945 | (create-test-table :fruit db) 946 | (is (= [] (sql/query db "SELECT * FROM fruit"))))) 947 | 948 | (deftest insert-one-via-execute 949 | (doseq [db (test-specs)] 950 | (create-test-table :fruit db) 951 | (let [new-key ((select-key db) 952 | (sql/execute! db [(str "INSERT INTO fruit ( name )" 953 | " VALUES ( ? )") 954 | "Apple"] 955 | {:return-keys true}))] 956 | (is (= (returned-key db 1) new-key))))) 957 | 958 | (deftest insert-two-via-execute 959 | (doseq [db (test-specs)] 960 | (create-test-table :fruit db) 961 | (let [execute-multi-insert 962 | (fn [db] 963 | (sql/execute! db [(str "INSERT INTO fruit ( name )" 964 | " VALUES ( ? )") 965 | ["Apple"] 966 | ["Orange"]] 967 | {:return-keys true 968 | :multi? true})) 969 | new-keys (map (select-key db) 970 | (if (#{"jtds" "jtds:sqlserver"} (db-type db)) 971 | (do 972 | (is (thrown? java.sql.BatchUpdateException 973 | (execute-multi-insert db))) 974 | []) 975 | (execute-multi-insert db)))] 976 | (case (db-type db) 977 | ;; SQLite returns nothing useful now 978 | "sqlite" (is (= [] new-keys)) 979 | ;; Derby returns a single row count 980 | "derby" (is (= [(returned-key db 1)] new-keys)) 981 | ;; H2 returns dummy keys 982 | ("h2" "h2:mem") 983 | (is (= [(returned-key db 1) (returned-key db 2)] new-keys)) 984 | ;; HSQL returns nothing useful 985 | "hsql" (is (= [] new-keys)) 986 | ;; MS SQL returns row counts 987 | "mssql" (is (= [1 1] new-keys)) 988 | ;; jTDS disallows batch updates returning keys (handled above) 989 | ("jtds" "jtds:sqlserver") 990 | (is (= [] new-keys)) 991 | ;; otherwise expect two rows with the correct keys 992 | (do 993 | (when-not (= [(returned-key db 1) (returned-key db 2)] new-keys) 994 | (println "FAIL FOR" db)) 995 | (is (= [(returned-key db 1) 996 | (returned-key db 2)] 997 | new-keys))))))) 998 | 999 | (deftest insert-two-via-execute-result-set-fn 1000 | (doseq [db (test-specs)] 1001 | (create-test-table :fruit db) 1002 | (let [execute-multi-insert 1003 | (fn [db] 1004 | (sql/execute! db [(str "INSERT INTO fruit ( name )" 1005 | " VALUES ( ? )") 1006 | ["Apple"] 1007 | ["Orange"]] 1008 | {:return-keys true 1009 | :multi? true 1010 | :result-set-fn count})) 1011 | n (if (#{"jtds" "jtds:sqlserver"} (db-type db)) 1012 | (do 1013 | (is (thrown? java.sql.BatchUpdateException 1014 | (execute-multi-insert db))) 1015 | 0) 1016 | (execute-multi-insert db))] 1017 | (case (db-type db) 1018 | ;; SQLite returns nothing useful now 1019 | "sqlite" (is (= 0 n)) 1020 | ;; Derby returns a single row count 1021 | "derby" (is (= 1 n)) 1022 | ;; H2 returns (zero) keys now 1023 | ("h2" "h2:mem") (is (= 2 n)) 1024 | ;; HSQL returns nothing useful 1025 | "hsql" (is (= 0 n)) 1026 | ;; MS SQL returns row counts (we still apply result-set-fn) 1027 | "mssql" (is (= 2 n)) 1028 | ;; jTDS disallows batch updates returning keys (handled above) 1029 | ("jtds" "jtds:sqlserver") 1030 | (is (= 0 n)) 1031 | ;; otherwise expect two rows with the correct keys 1032 | (do 1033 | (when-not (= 2 n) 1034 | (println "FAIL FOR" db)) 1035 | (is (= 2 n))))))) 1036 | 1037 | (deftest insert-one-row 1038 | (doseq [db (test-specs)] 1039 | (create-test-table :fruit db) 1040 | (let [new-keys (map (select-key db) (sql/insert! db :fruit {:name "Apple"}))] 1041 | (is (= [(returned-key db 1)] new-keys))))) 1042 | 1043 | (deftest insert-one-row-opts 1044 | (doseq [db (test-specs)] 1045 | (create-test-table :fruit db) 1046 | (let [new-keys (map (select-key db) (sql/insert! db :fruit {:name "Apple"} {}))] 1047 | (is (= [(returned-key db 1)] new-keys))))) 1048 | 1049 | (deftest insert-one-row-opts-row-fn 1050 | (doseq [db (test-specs)] 1051 | (create-test-table :fruit db) 1052 | (let [new-keys (sql/insert! db :fruit {:name "Apple"} {:row-fn (select-key db)})] 1053 | (is (= [(returned-key db 1)] new-keys))))) 1054 | 1055 | (deftest insert-one-col-val 1056 | (doseq [db (test-specs)] 1057 | (create-test-table :fruit db) 1058 | (let [new-keys (sql/insert! db :fruit [:name] ["Apple"])] 1059 | (is (= [1] new-keys))))) 1060 | 1061 | (deftest insert-one-col-val-opts 1062 | (doseq [db (test-specs)] 1063 | (create-test-table :fruit db) 1064 | (let [new-keys (sql/insert! db :fruit [:name] ["Apple"] {})] 1065 | (is (= [1] new-keys))))) 1066 | 1067 | (deftest insert-query 1068 | (doseq [db (test-specs)] 1069 | (create-test-table :fruit db) 1070 | (let [new-keys (map (select-key db) (sql/insert! db :fruit {:name "Apple"}))] 1071 | (is (= [(returned-key db 1)] new-keys)) 1072 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil}] 1073 | (sql/query db "SELECT * FROM fruit"))) 1074 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil}] 1075 | (sql/query db ["SELECT * FROM fruit"]))) 1076 | (is (= [{:ID (generated-key db 1) :NAME "Apple" :APPEARANCE nil :GRADE nil :COST nil}] 1077 | (sql/query db ["SELECT * FROM fruit"] {:identifiers str/upper-case}))) 1078 | (when (map? db) 1079 | (is (= [{:ID (generated-key db 1) :NAME "Apple" :APPEARANCE nil :GRADE nil :COST nil}] 1080 | (sql/query (assoc db :identifiers str/upper-case) ["SELECT * FROM fruit"])))) 1081 | (is (= [{:fruit/id (generated-key db 1) :fruit/name "Apple" :fruit/appearance nil 1082 | :fruit/grade nil :fruit/cost nil}] 1083 | (sql/query db ["SELECT * FROM fruit"] {:qualifier "fruit"}))) 1084 | (is (= [{:fruit/name "Apple"}] 1085 | (sql/query db ["SELECT name FROM fruit"] 1086 | {:identifiers (comp (partial str "fruit/") str/lower-case)}))) 1087 | (is (= [{:name "Apple"}] 1088 | (sql/query db ["SELECT name FROM fruit"] 1089 | {:identifiers (comp keyword str/lower-case)}))) 1090 | (when (map? db) 1091 | (is (= [{:fruit/id (generated-key db 1) :fruit/name "Apple" :fruit/appearance nil 1092 | :fruit/grade nil :fruit/cost nil}] 1093 | (sql/query (assoc db :qualifier "fruit") ["SELECT * FROM fruit"])))) 1094 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil}] 1095 | (with-open [con (sql/get-connection db)] 1096 | (sql/query db [(sql/prepare-statement con "SELECT * FROM fruit")])))) 1097 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil}] 1098 | (sql/query db ["SELECT * FROM fruit"] {:max-rows 1}))) 1099 | (cond (derby? db) nil 1100 | (hsqldb? db) (is (seq (with-out-str 1101 | (sql/query db ["SELECT * FROM fruit"] 1102 | {:explain? "EXPLAIN PLAN FOR"})))) 1103 | (mssql? db) nil 1104 | :else (is (seq (with-out-str 1105 | (sql/query db ["SELECT * FROM fruit"] 1106 | {:explain? true})))))))) 1107 | 1108 | (deftest insert-two-by-map-and-query 1109 | (doseq [db (test-specs)] 1110 | (create-test-table :fruit db) 1111 | (let [new-keys (map (select-key db) (sql/insert-multi! db :fruit [{:name "Apple"} {:name "Pear"}])) 1112 | rows (sql/query db ["SELECT * FROM fruit ORDER BY name"])] 1113 | (is (= [(returned-key db 1) (returned-key db 2)] new-keys)) 1114 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil} 1115 | {:id (generated-key db 2) :name "Pear" :appearance nil :grade nil :cost nil}] rows))))) 1116 | 1117 | (deftest insert-two-by-map-row-fn 1118 | (doseq [db (test-specs)] 1119 | (create-test-table :fruit db) 1120 | (let [new-keys (sql/insert-multi! db :fruit [{:name "Apple"} {:name "Pear"}] 1121 | {:row-fn (select-key db)})] 1122 | (is (= [(returned-key db 1) (returned-key db 2)] new-keys))))) 1123 | 1124 | (deftest insert-two-by-map-result-set-fn 1125 | (doseq [db (test-specs)] 1126 | (create-test-table :fruit db) 1127 | (is (= 2 (sql/insert-multi! db :fruit [{:name "Apple"} {:name "Pear"}] 1128 | {:result-set-fn count}))))) 1129 | 1130 | (deftest insert-identifiers-respected-1 1131 | (doseq [db (filter postgres? (test-specs))] 1132 | (create-test-table :fruit db) 1133 | (let [inserted (sql/insert! db 1134 | :fruit 1135 | {:name "Apple"} 1136 | {:identifiers clojure.string/upper-case 1137 | :qualifier "foo"}) 1138 | rows (sql/query db ["SELECT * FROM fruit ORDER BY name"] 1139 | {:identifiers clojure.string/upper-case 1140 | :qualifier "foo"})] 1141 | (is (= rows inserted))))) 1142 | 1143 | (deftest insert-identifiers-respected-2 1144 | (doseq [db (filter postgres? (test-specs))] 1145 | (create-test-table :fruit db) 1146 | (let [inserted (sql/insert-multi! db 1147 | :fruit 1148 | [{:name "Apple"} {:name "Pear"}] 1149 | {:identifiers clojure.string/upper-case}) 1150 | rows (sql/query db ["SELECT * FROM fruit ORDER BY name"] 1151 | {:identifiers clojure.string/upper-case})] 1152 | (is (= rows inserted))))) 1153 | 1154 | (deftest insert-two-by-map-and-query-as-arrays 1155 | ;; this test also serves to illustrate qualified keyword usage 1156 | (doseq [db (test-specs)] 1157 | ;; qualifier on table name ignored by default 1158 | (create-test-table :table/fruit db) 1159 | (let [new-keys (map (select-key db) 1160 | ;; insert ignores namespace qualifier by default 1161 | (sql/insert-multi! db :table/fruit 1162 | [{:fruit/name "Apple"} 1163 | {:fruit/name "Pear"}])) 1164 | rows (sql/query db ["SELECT * FROM fruit ORDER BY name"] 1165 | {:as-arrays? :cols-as-is 1166 | :qualifier "fruit"})] 1167 | (is (= [(returned-key db 1) (returned-key db 2)] new-keys)) 1168 | (is (= [[:fruit/id :fruit/name :fruit/appearance :fruit/cost :fruit/grade] 1169 | [(generated-key db 1) "Apple" nil nil nil] 1170 | [(generated-key db 2) "Pear" nil nil nil]] rows))))) 1171 | 1172 | (deftest insert-two-by-cols-and-query 1173 | (doseq [db (test-specs)] 1174 | (create-test-table :fruit db) 1175 | (let [update-counts (sql/insert-multi! db :fruit [:name] [["Apple"] ["Pear"]]) 1176 | rows (sql/query db ["SELECT * FROM fruit ORDER BY name"])] 1177 | (is (= [1 1] update-counts)) 1178 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil} 1179 | {:id (generated-key db 2) :name "Pear" :appearance nil :grade nil :cost nil}] rows))))) 1180 | 1181 | (deftest insert-update-and-query 1182 | (doseq [db (test-specs)] 1183 | (create-test-table :fruit db) 1184 | (let [new-keys (map (select-key db) (sql/insert! db :fruit {:name "Apple"})) 1185 | update-result (sql/update! db :fruit {:cost 12 :grade 1.2 :appearance "Green"} 1186 | ["id = ?" (generated-key db 1)]) 1187 | rows (sql/query db ["SELECT * FROM fruit"])] 1188 | (is (= [(returned-key db 1)] new-keys)) 1189 | (is (= [1] update-result)) 1190 | (is (= [{:id (generated-key db 1) 1191 | :name "Apple" :appearance "Green" 1192 | :grade (float-or-double db 1.2) 1193 | :cost 12}] rows))))) 1194 | 1195 | (deftest insert-delete-and-query 1196 | (doseq [db (test-specs)] 1197 | (create-test-table :fruit db) 1198 | (let [new-keys (map (select-key db) (sql/insert! db :fruit {:name "Apple"})) 1199 | delete-result (sql/delete! db :fruit 1200 | ["id = ?" (generated-key db 1)]) 1201 | rows (sql/query db ["SELECT * FROM fruit"])] 1202 | (is (= [(returned-key db 1)] new-keys)) 1203 | (is (= [1] delete-result)) 1204 | (is (= [] rows))))) 1205 | 1206 | (deftest insert-delete-and-query-in-connection 1207 | (doseq [db (test-specs)] 1208 | (sql/with-db-connection [con-db db] 1209 | (create-test-table :fruit con-db) 1210 | (let [new-keys (map (select-key db) (sql/insert! con-db :fruit {:name "Apple"})) 1211 | delete-result (sql/delete! con-db :fruit 1212 | ["id = ?" (generated-key con-db 1)]) 1213 | rows (sql/query con-db ["SELECT * FROM fruit"])] 1214 | (is (= [(returned-key con-db 1)] new-keys)) 1215 | (is (= [1] delete-result)) 1216 | (is (= [] rows)))))) 1217 | 1218 | #_{:clj-kondo/ignore [:invalid-arity]} 1219 | (deftest illegal-insert-arguments 1220 | (doseq [db (test-specs)] 1221 | (illegal-arg-or-spec "insert!" (sql/insert! db)) 1222 | (illegal-arg-or-spec "insert!" (sql/insert! db {:name "Apple"} [:name])) 1223 | (illegal-arg-or-spec "insert!" (sql/insert! db {:name "Apple"} [:name] {:entities identity})) 1224 | (illegal-arg-or-spec "insert!" (sql/insert! db [:name])) 1225 | (if with-spec? ; clojure.spec catches this differently 1226 | (is (thrown? clojure.lang.ExceptionInfo (sql/insert! db [:name] {:entities identity}))) 1227 | (is (thrown? ClassCastException (sql/insert! db [:name] {:entities identity})))))) 1228 | 1229 | (deftest test-execute!-fails-with-multi-param-groups 1230 | (doseq [db (test-specs)] 1231 | (create-test-table :fruit db) 1232 | ;; RuntimeException -> SQLException -> ArrayIndexOutOfBoundsException 1233 | (is (thrown? Exception 1234 | (sql/execute! 1235 | db 1236 | ["INSERT INTO fruit (name,appearance) VALUES (?,?)" 1237 | ["Apple" "rosy"] 1238 | ["Pear" "yellow"] 1239 | ["Orange" "round"]]))))) 1240 | 1241 | (deftest test-execute!-with-multi?-true-param-groups 1242 | (doseq [db (test-specs)] 1243 | (create-test-table :fruit db) 1244 | ;; RuntimeException -> SQLException -> ArrayIndexOutOfBoundsException 1245 | (let [counts (sql/execute! 1246 | db 1247 | ["INSERT INTO fruit (name,appearance) VALUES (?,?)" 1248 | ["Apple" "rosy"] 1249 | ["Pear" "yellow"] 1250 | ["Orange" "round"]] 1251 | {:multi? true}) 1252 | rows (sql/query db ["SELECT * FROM fruit ORDER BY name"])] 1253 | (is (= [1 1 1] counts)) 1254 | (is (= [{:id (generated-key db 1) :name "Apple" :appearance "rosy" :cost nil :grade nil} 1255 | {:id (generated-key db 3) :name "Orange" :appearance "round" :cost nil :grade nil} 1256 | {:id (generated-key db 2) :name "Pear" :appearance "yellow" :cost nil :grade nil}] rows))))) 1257 | 1258 | (deftest check-prepared-performance 1259 | (let [ones (repeat 100 1) 1260 | select-1 [(str "select " (str/join ", " ones))] 1261 | select-? (into [(str "select " 1262 | (str/join ", " (map (fn [_] "?") ones)))] 1263 | ones)] 1264 | (println "\nSanity check on prepared statement parameter performance.") 1265 | (doseq [db (test-specs) 1266 | :when (not (or (derby? db) (hsqldb? db)))] 1267 | (println " " db) 1268 | (time (dotimes [n 100] (sql/query db select-1))) 1269 | (time (dotimes [n 100] (sql/query db select-?))) 1270 | (sql/with-db-connection [con db] 1271 | (time (dotimes [n 100] (sql/query con select-1))) 1272 | (time (dotimes [n 100] (sql/query con select-?))))))) 1273 | 1274 | (deftest test-create-table-ddl 1275 | (is (re-find #"`foo` int default 0" 1276 | (sql/create-table-ddl :table 1277 | [[:foo :int :default 0]] 1278 | {:entities (sql/quoted :mysql)})))) 1279 | 1280 | #_{:clj-kondo/ignore [:unresolved-symbol]} 1281 | (comment 1282 | db (sql/create-table-ddl 1283 | table 1284 | [[:id :int "PRIMARY KEY AUTO_INCREMENT"] 1285 | [:name "VARCHAR(32)"] 1286 | [:appearance "VARCHAR(32)"] 1287 | [:cost :int] 1288 | [:grade :real]] 1289 | {:table-spec "ENGINE=InnoDB"})) 1290 | 1291 | (deftest test-resultset-read-column 1292 | (extend-protocol sql/IResultSetReadColumn 1293 | String 1294 | (result-set-read-column [s _ _] ::FOO)) 1295 | 1296 | (try 1297 | (doseq [db (test-specs)] 1298 | (create-test-table :fruit db) 1299 | (sql/insert-multi! db 1300 | :fruit 1301 | [:name :cost :grade] 1302 | [["Crepes" 12 87.7] 1303 | ["Vegetables" -88 nil] 1304 | ["Teenage Mutant Ninja Turtles" 0 100.0]]) 1305 | (is (= {:name ::FOO, :cost -88, :grade nil} 1306 | (sql/query db ["SELECT name, cost, grade FROM fruit WHERE name = ?" 1307 | "Vegetables"] 1308 | {:result-set-fn first})))) 1309 | 1310 | ;; somewhat "undo" the first extension 1311 | (finally 1312 | (extend-protocol sql/IResultSetReadColumn 1313 | String 1314 | (result-set-read-column [s _ _] s))))) 1315 | 1316 | (deftest test-sql-value 1317 | (extend-protocol sql/ISQLValue 1318 | clojure.lang.Keyword 1319 | (sql-value [_] "KW")) 1320 | 1321 | (doseq [db (test-specs)] 1322 | (create-test-table :fruit db) 1323 | (sql/insert! db 1324 | :fruit 1325 | [:name :cost :grade] 1326 | [:test 12 nil]) 1327 | (is (= {:name "KW", :cost 12, :grade nil} 1328 | (sql/query db ["SELECT name, cost, grade FROM fruit"] 1329 | {:result-set-fn first})))) 1330 | 1331 | ;; somewhat "undo" the first extension 1332 | (extend-protocol sql/ISQLValue 1333 | clojure.lang.Keyword 1334 | (sql-value [k] k))) 1335 | 1336 | (deftest test-sql-parameter 1337 | (extend-protocol sql/ISQLParameter 1338 | clojure.lang.Keyword 1339 | (set-parameter [v ^java.sql.PreparedStatement s ^long i] 1340 | (if (= :twelve v) 1341 | (.setLong s i 12) 1342 | (.setString s i (str (name v) i))))) 1343 | 1344 | (doseq [db (test-specs)] 1345 | (create-test-table :fruit db) 1346 | (sql/insert! db 1347 | :fruit 1348 | [:name :cost :grade] 1349 | [:test :twelve nil]) 1350 | (is (= {:name "test1", :cost 12, :grade nil} 1351 | (sql/query db ["SELECT name, cost, grade FROM fruit"] 1352 | {:result-set-fn first})))) 1353 | 1354 | ;; somewhat "undo" the first extension 1355 | (extend-protocol sql/ISQLParameter 1356 | clojure.lang.Keyword 1357 | (set-parameter [v ^java.sql.PreparedStatement s ^long i] 1358 | (.setObject s i (sql/sql-value v))))) 1359 | --------------------------------------------------------------------------------