├── .circleci └── config.yml ├── .dir-locals.el ├── .github └── CODEOWNERS ├── .gitignore ├── LICENSE ├── README.md ├── bin └── reflection-linter ├── project.clj ├── src └── metabase │ └── connection_pool.clj └── test └── metabase └── connection_pool_test.clj /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | ######################################################################################################################## 4 | # EXECUTORS # 5 | ######################################################################################################################## 6 | 7 | executors: 8 | default: 9 | working_directory: /home/circleci/metabase/connection-pool/ 10 | docker: 11 | - image: circleci/clojure:lein-2.8.1 12 | 13 | java-11: 14 | working_directory: /home/circleci/metabase/connection-pool/ 15 | docker: 16 | - image: circleci/clojure:openjdk-11-lein-2.8.1 17 | 18 | 19 | ######################################################################################################################## 20 | # COMMANDS # 21 | ######################################################################################################################## 22 | 23 | commands: 24 | 25 | attach-workspace: 26 | steps: 27 | - attach_workspace: 28 | at: /home/circleci/ 29 | 30 | restore-deps-cache: 31 | steps: 32 | - restore_cache: 33 | keys: 34 | - deps-{{ checksum "project.clj" }} 35 | - deps- 36 | 37 | jobs: 38 | 39 | checkout: 40 | executor: default 41 | steps: 42 | - restore_cache: 43 | keys: 44 | - source-{{ .Branch }}-{{ .Revision }} 45 | - source-{{ .Branch }} 46 | - source- 47 | - checkout 48 | - save_cache: 49 | key: source-{{ .Branch }}-{{ .Revision }} 50 | paths: 51 | - .git 52 | - persist_to_workspace: 53 | root: /home/circleci/ 54 | paths: 55 | - metabase/connection-pool 56 | 57 | deps: 58 | executor: default 59 | steps: 60 | - attach-workspace 61 | - restore-deps-cache 62 | - run: lein deps 63 | - save_cache: 64 | key: deps-{{ checksum "project.clj" }} 65 | paths: 66 | - /home/circleci/.m2 67 | 68 | lein: 69 | parameters: 70 | e: 71 | type: executor 72 | default: default 73 | lein-command: 74 | type: string 75 | executor: << parameters.e >> 76 | steps: 77 | - attach-workspace 78 | - restore-deps-cache 79 | - run: 80 | command: lein << parameters.lein-command >> 81 | no_output_timeout: 5m 82 | 83 | linter-reflection-warnings: 84 | executor: default 85 | steps: 86 | - attach-workspace 87 | - restore-deps-cache 88 | - run: 89 | name: Run reflection warnings checker 90 | command: ./bin/reflection-linter 91 | no_output_timeout: 2m 92 | 93 | deploy: 94 | executor: default 95 | steps: 96 | - attach-workspace 97 | - run: 98 | name: Deploy to clojars 99 | command: lein deploy clojars 100 | 101 | 102 | ######################################################################################################################## 103 | # WORKFLOWS # 104 | ######################################################################################################################## 105 | 106 | workflows: 107 | version: 2 108 | build: 109 | jobs: 110 | - checkout 111 | 112 | - deps: 113 | requires: 114 | - checkout 115 | 116 | - lein: 117 | name: tests 118 | requires: 119 | - deps 120 | lein-command: test 121 | 122 | - lein: 123 | name: tests-java-11 124 | requires: 125 | - deps 126 | e: java-11 127 | lein-command: test 128 | 129 | - lein: 130 | name: linter-eastwood 131 | requires: 132 | - deps 133 | lein-command: eastwood 134 | 135 | - lein: 136 | name: linter-docstring-checker 137 | requires: 138 | - deps 139 | lein-command: docstring-checker 140 | 141 | - lein: 142 | name: linter-namespace-decls 143 | requires: 144 | - deps 145 | lein-command: check-namespace-decls 146 | 147 | - lein: 148 | name: linter-bikeshed 149 | requires: 150 | - deps 151 | lein-command: bikeshed 152 | 153 | - linter-reflection-warnings: 154 | requires: 155 | - deps 156 | 157 | - deploy: 158 | requires: 159 | - linter-bikeshed 160 | - linter-docstring-checker 161 | - linter-eastwood 162 | - linter-namespace-decls 163 | - linter-reflection-warnings 164 | - tests 165 | - tests-java-11 166 | filters: 167 | branches: 168 | only: master 169 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((indent-tabs-mode . nil) ; always use spaces for tabs 2 | (require-final-newline . t))) ; add final newline on save 3 | (clojure-mode . (;; if you're using clj-refactor (highly recommended!), prefer prefix notation when cleaning the ns form 4 | (cljr-favor-prefix-notation . t) 5 | ;; prefer keeping source width about ~118, GitHub seems to cut off stuff at either 119 or 120 and 6 | ;; it's nicer to look at code in GH when you don't have to scroll back and forth 7 | (fill-column . 118) 8 | (clojure-docstring-fill-column . 118)))) 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | *.* @camsaul 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.jar 3 | .\#* 4 | \#*\# 5 | /*.iml 6 | /.eastwood 7 | /.env 8 | /.envrc 9 | /.idea 10 | /.lein-env 11 | /.lein-failures 12 | /.lein-repl-history 13 | /.nrepl-port 14 | /build.xml 15 | /checkouts 16 | /classes 17 | /pom.xml 18 | /pom.xml.asc 19 | /target 20 | profiles.clj 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM 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 content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Downloads](https://versions.deps.co/metabase/connection-pool/downloads.svg)](https://versions.deps.co/metabase/connection-pool) 2 | [![Dependencies Status](https://versions.deps.co/metabase/connection-pool/status.svg)](https://versions.deps.co/metabase/connection-pool) 3 | [![Circle CI](https://circleci.com/gh/metabase/connection-pool.svg?style=svg)](https://circleci.com/gh/metabase/connection-pool) 4 | [![License](https://img.shields.io/badge/license-Eclipse%20Public%20License-blue.svg)](https://raw.githubusercontent.com/metabase/connection-pool/master/LICENSE) 5 | [![cljdoc badge](https://cljdoc.org/badge/metabase/connection-pool)](https://cljdoc.org/d/metabase/connection-pool/CURRENT) 6 | 7 | [![Clojars Project](https://clojars.org/metabase/connection-pool/latest-version.svg)](http://clojars.org/metabase/connection-pool) 8 | 9 | ### Creating a Connection Pool 10 | 11 | #### From a `clojure.java.jdbc` spec 12 | 13 | You can create a C3P0 connection pool with any `clojure.java.jdbc` connection spec map with `:subname` and 14 | `:subprotocol` keys. `connection-pool-spec` will return a `clojure.java.jdbc` connection spec you can use directly: 15 | 16 | ```clj 17 | (require '[clojure.java.jdbc :as jdbc] 18 | '[metabase.connection-pool :as connection-pool]) 19 | 20 | ;;; Create a C3P0 connection pool 21 | 22 | (let [pool-spec (connection-pool/connection-pool-spec my-jdbc-spec)] 23 | (jdbc/query pool-spec ["SELECT *"])) 24 | ``` 25 | 26 | (You will almost certainly want to store your pool somewhere, such as in an atom). 27 | 28 | #### From a JDBC URL String: 29 | 30 | You can create a pooled `DataSource` (e.g., for use with [`next-jdbc`](https://github.com/seancorfield/next-jdbc)) by calling `pooled-data-source-from-url`: 31 | 32 | ```clj 33 | (require '[next.jdbc :as jdbc] 34 | '[metabase.connection-pool :as connection-pool]) 35 | 36 | (with-open [connection (jdbc/get-connection (connection-pool/pooled-data-source-from-url "jdbc:postgresql:localhost:3000/my_db"))] 37 | (reduce my-fn init-value (jdbc/plan connection ["SELECT *"]))) 38 | ``` 39 | 40 | ### Configuring the connection pool 41 | 42 | You can set connection pool options such as size in a `c3p0.properties` file, or by passing them as a map to `connection-pool-spec`: 43 | 44 | ```clj 45 | (def ^:private connection-pool-properties 46 | {"maxIdleTime" (* 3 60 60) 47 | "minPoolSize" 1 48 | "initialPoolSize" 1 49 | "maxPoolSize" 15}) 50 | 51 | (def my-pool-spec 52 | (connection-pool/connection-pool-spec my-jdbc-spec connection-pool-properties)) 53 | ``` 54 | 55 | See [https://www.mchange.com/projects/c3p0/#configuration_properties](https://www.mchange.com/projects/c3p0/#configuration_properties) for a list of all options. 56 | 57 | ### Destroying connection pools 58 | 59 | `destroy-connection-pool!` will destroy the connection pool you created: 60 | 61 | ```clj 62 | (connection-pool/destroy-connection-pool! pool-spec) 63 | ``` 64 | 65 | ### Legal Stuff 66 | 67 | Copyright © 2019 [Metabase, Inc](https://metabase.com/). This project is licensed under the Eclipse Public License, same as Clojure. 68 | -------------------------------------------------------------------------------- /bin/reflection-linter: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | printf "\e[1;34mChecking for reflection warnings. This may take a few minutes, so sit tight...\e[0m\n" 4 | 5 | warnings=`lein check 2>&1 | grep Reflection | grep metabase | sort | uniq` 6 | 7 | if [ ! -z "$warnings" ]; then 8 | printf "\e[1;31mYour code has introduced some reflection warnings.\e[0m 😞\n" 9 | echo "$warnings"; 10 | exit -1; 11 | fi 12 | 13 | printf "\e[1;32mNo reflection warnings! Success.\e[0m\n" 14 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject metabase/connection-pool "1.3.0" 2 | :description "Connection pools for JDBC databases. Simple wrapper around C3P0." 3 | :url "https://github.com/metabase/connection-pool" 4 | :min-lein-version "2.5.0" 5 | 6 | :license {:name "Eclipse Public License" 7 | :url "https://raw.githubusercontent.com/metabase/connection-pool/master/LICENSE"} 8 | 9 | :aliases 10 | {"bikeshed" ["with-profile" "+bikeshed" "bikeshed" "--max-line-length" "120"] 11 | "check-namespace-decls" ["with-profile" "+check-namespace-decls" "check-namespace-decls"] 12 | "eastwood" ["with-profile" "+eastwood" "eastwood"] 13 | "docstring-checker" ["with-profile" "+docstring-checker" "docstring-checker"] 14 | ;; `lein lint` will run all linters 15 | "lint" ["do" ["eastwood"] ["bikeshed"] ["check-namespace-decls"] ["docstring-checker"]]} 16 | 17 | :dependencies 18 | [[com.mchange/c3p0 "0.10.1"]] 19 | 20 | :profiles 21 | {:dev 22 | {:dependencies 23 | [[org.clojure/clojure "1.10.1"] 24 | [com.h2database/h2 "1.4.197"] 25 | [pjstadig/humane-test-output "0.9.0"]] 26 | 27 | :global-vars {*warn-on-reflection* true} 28 | 29 | :jvm-opts 30 | ["-Xverify:none"]} 31 | 32 | :eastwood 33 | {:plugins 34 | [[jonase/eastwood "0.3.6" :exclusions [org.clojure/clojure]]] 35 | 36 | :add-linters 37 | [:unused-private-vars 38 | :unused-namespaces 39 | :unused-fn-args 40 | :unused-locals] 41 | 42 | :exclude-linters 43 | [:deprecations]} 44 | 45 | :docstring-checker 46 | {:plugins 47 | [[docstring-checker "1.0.3"]] 48 | 49 | :docstring-checker 50 | {:exclude [#"test"]}} 51 | 52 | :bikeshed 53 | {:plugins 54 | [[lein-bikeshed "0.5.2" 55 | :exclusions [org.clojure/tools.namespace]]]} 56 | 57 | :check-namespace-decls 58 | {:plugins [[lein-check-namespace-decls "1.0.2"]] 59 | :source-paths ["test"] 60 | :check-namespace-decls {:prefix-rewriting true}}} 61 | 62 | :deploy-repositories 63 | [["clojars" 64 | {:url "https://clojars.org/repo" 65 | :username :env/clojars_username 66 | :password :env/clojars_password 67 | :sign-releases false}]]) 68 | -------------------------------------------------------------------------------- /src/metabase/connection_pool.clj: -------------------------------------------------------------------------------- 1 | (ns metabase.connection-pool 2 | "Low-level logic for creating connection pools for a JDBC-based database. Used by both application DB and connected 3 | data warehouse DBs. 4 | 5 | The aim here is to completely encapsulate the connection pool library we use -- that way we can swap it out if we 6 | want to at some point without having to touch any other files. (TODO - this is currently true of everything except 7 | for the options, which are c3p0-specific -- consider abstracting those as well?)" 8 | (:import com.mchange.v2.c3p0.DataSources 9 | [java.sql Driver DriverManager] 10 | java.util.Properties 11 | javax.sql.DataSource)) 12 | 13 | ;;; ------------------------------------------------ Proxy DataSource ------------------------------------------------ 14 | 15 | (defn- set-username-and-password! [^Properties properties username password] 16 | (let [properties (or properties (Properties.))] 17 | (doseq [[k v] {"user" username, "password" password}] 18 | (if (some? v) 19 | (.setProperty properties k (name v)) 20 | (.remove properties k))) 21 | properties)) 22 | 23 | (defn- proxy-data-source 24 | "Normal c3p0 DataSource classes do not properly work with our JDBC proxy drivers for whatever reason. Use our own 25 | instead, which works nicely." 26 | (^DataSource [^String jdbc-url, ^Properties properties] 27 | (reify DataSource 28 | (getConnection [_] 29 | (DriverManager/getConnection jdbc-url properties)) 30 | 31 | (getConnection [_ username password] 32 | (DriverManager/getConnection jdbc-url (set-username-and-password! properties username password))))) 33 | 34 | (^DataSource [^Driver driver, ^String jdbc-url, ^Properties properties] 35 | (reify DataSource 36 | (getConnection [_] 37 | (.connect driver jdbc-url properties)) 38 | 39 | (getConnection [_ username password] 40 | (.connect driver jdbc-url (set-username-and-password! properties username password)))))) 41 | 42 | 43 | ;;; ------------------------------------------- Creating Connection Pools -------------------------------------------- 44 | 45 | (defn map->properties 46 | "Create a `Properties` object from a JDBC connection spec map. Properties objects are maps of String -> String, so all 47 | keys and values are converted to Strings appropriately." 48 | ^Properties [m] 49 | (let [properties (Properties.)] 50 | (doseq [[k v] m] 51 | (.setProperty properties (name k) (if (keyword? v) 52 | (name v) 53 | (str v)))) 54 | properties)) 55 | 56 | (defn- spec->properties ^Properties [spec] 57 | (map->properties (dissoc spec :classname :subprotocol :subname))) 58 | 59 | (defn- unpooled-data-source 60 | (^DataSource [{:keys [subname subprotocol], :as spec}] 61 | (proxy-data-source (format "jdbc:%s:%s" subprotocol subname) (spec->properties spec))) 62 | 63 | (^DataSource [driver {:keys [subname subprotocol], :as spec}] 64 | (proxy-data-source driver (format "jdbc:%s:%s" subprotocol subname) (spec->properties spec)))) 65 | 66 | (defn pooled-data-source 67 | "Create a new pooled DataSource from a `clojure.java.jdbc` spec." 68 | (^DataSource [spec] 69 | (DataSources/pooledDataSource (unpooled-data-source spec))) 70 | 71 | (^DataSource [spec pool-properties-map] 72 | (DataSources/pooledDataSource (unpooled-data-source spec) (map->properties pool-properties-map))) 73 | 74 | (^DataSource [driver spec pool-properties-map] 75 | (DataSources/pooledDataSource (unpooled-data-source driver spec) (map->properties pool-properties-map)))) 76 | 77 | (defn connection-pool-spec 78 | "Create a new connection pool for a JDBC `spec` and return a spec for it. Optionally pass a map of connection pool 79 | properties -- see https://www.mchange.com/projects/c3p0/#configuration_properties for a description of valid options 80 | and their default values." 81 | ([spec] 82 | {:datasource (pooled-data-source spec)}) 83 | 84 | ([spec pool-properties-map] 85 | {:datasource (pooled-data-source spec pool-properties-map)}) 86 | 87 | ([driver spec pool-properties-map] 88 | {:datasource (pooled-data-source driver spec pool-properties-map)})) 89 | 90 | (defn pooled-data-source-from-url 91 | "Create a new pooled DataSource from a JDBC URL string." 92 | (^DataSource [url] 93 | (DataSources/pooledDataSource (proxy-data-source url nil))) 94 | 95 | (^DataSource [url pool-properties-map] 96 | (DataSources/pooledDataSource (proxy-data-source url nil) (map->properties pool-properties-map))) 97 | 98 | (^DataSource [driver url pool-properties-map] 99 | (DataSources/pooledDataSource (proxy-data-source driver url nil) (map->properties pool-properties-map)))) 100 | 101 | (defn destroy-connection-pool! 102 | "Immediately release all resources held by a connection pool." 103 | [spec-or-data-source] 104 | (cond 105 | (map? spec-or-data-source) 106 | (recur (:datasource spec-or-data-source)) 107 | 108 | (instance? DataSource spec-or-data-source) 109 | (DataSources/destroy ^DataSource spec-or-data-source) 110 | 111 | :else 112 | (throw 113 | (IllegalArgumentException. 114 | "Don't know how to destroy conn pool: expected JDBC spec with `:datasource` or instance of `DataSource`.")))) 115 | -------------------------------------------------------------------------------- /test/metabase/connection_pool_test.clj: -------------------------------------------------------------------------------- 1 | (ns metabase.connection-pool-test 2 | (:require [clojure.test :refer :all] 3 | [metabase.connection-pool :as connection-pool])) 4 | 5 | (def ^:private spec 6 | {:classname "org.h2.Driver", :subprotocol "h2", :subname "mem:db"}) 7 | 8 | (deftest properties-test 9 | (testing "Options passed in to `connection-pool-spec` should get parsed correctly" 10 | (let [description (-> (connection-pool/connection-pool-spec spec {"acquireIncrement" 1 11 | "testConnectionOnCheckin" true}) 12 | :datasource 13 | str)] 14 | (is (= "acquireIncrement -> 1" 15 | (re-find #"acquireIncrement -> \d" description)) 16 | "numeric options should get converted correctly") 17 | (is (= "testConnectionOnCheckin -> true" 18 | (re-find #"testConnectionOnCheckin -> \w+" description)) 19 | "boolean options should get converted correctly")))) 20 | 21 | (deftest map->properties-test 22 | (testing "Properties should be converted to strings" 23 | ;; Properties are equality-comparable to maps 24 | (is (= {"A" "true", "B" "false", "C" "100"} 25 | (connection-pool/map->properties {:A "true", "B" false, "C" 100}))))) 26 | 27 | (defrecord ^:private FakeConnection [url props] 28 | java.sql.Connection) 29 | 30 | (defrecord ^:private FakeDriver [] 31 | java.sql.Driver 32 | (connect [_ url props] 33 | (FakeConnection. url props))) 34 | 35 | (defn- proxy-data-source ^javax.sql.DataSource [& args] 36 | (apply #'connection-pool/proxy-data-source args)) 37 | 38 | (deftest proxy-data-source-test 39 | (testing "Make sure we can create a data source with an explicit driver instance" 40 | (is (= (FakeConnection. "jdbc:my-fake-db:localhost" nil) 41 | (.getConnection (proxy-data-source (FakeDriver.) "jdbc:my-fake-db:localhost" nil))))) 42 | 43 | (testing "Make sure username/password are set when using the 3-arg getConnection method" 44 | (doseq [props [(java.util.Properties.) nil (doto (java.util.Properties.) 45 | (.setProperty "password" "abc") 46 | (.setProperty "user" "cam-2"))]] 47 | (testing (format "with initial properties = %s" (pr-str props)) 48 | (is (= (FakeConnection. "jdbc:my-fake-db:localhost" {"password" "passw0rd", "user" "cam"}) 49 | (.getConnection (proxy-data-source (FakeDriver.) "jdbc:my-fake-db:localhost" props) 50 | "cam" 51 | "passw0rd")))))) 52 | 53 | (testing "passing nil username/password to 3-arg getConnection method should existing props" 54 | (let [props (doto (java.util.Properties.) 55 | (.setProperty "password" "abc") 56 | (.setProperty "user" "cam-2"))] 57 | (is (= (FakeConnection. "jdbc:my-fake-db:localhost" {}) 58 | (.getConnection (proxy-data-source (FakeDriver.) "jdbc:my-fake-db:localhost" props) 59 | nil 60 | nil)))))) 61 | 62 | (deftest connection-pool-spec-test 63 | (let [{:keys [^javax.sql.DataSource datasource]} (connection-pool/connection-pool-spec 64 | {:subprotocol "h2", :subname "mem:in-memory"})] 65 | (with-open [conn (.getConnection datasource) 66 | stmt (.prepareStatement conn "SELECT 1 AS one;") 67 | rset (.executeQuery stmt)] 68 | (.next rset) 69 | (is (= 1 70 | (.getObject rset 1)))))) 71 | 72 | (deftest pooled-data-source-from-url-test 73 | (with-open [conn (.getConnection (connection-pool/pooled-data-source-from-url "jdbc:h2:mem:in-memory")) 74 | stmt (.prepareStatement conn "SELECT 1 AS one;") 75 | rset (.executeQuery stmt)] 76 | (.next rset) 77 | (is (= 1 78 | (.getObject rset 1))))) 79 | --------------------------------------------------------------------------------