Back

get-datasource (clj)

(source)

function

(get-datasource spec)
Given some sort of specification of a database, return a `DataSource`. A specification can be a JDBC URL string (which is passed to the JDBC driver as-is), or a hash map. For the hash map, there are two formats accepted: In the first format, these keys are required: * `:dbtype` -- a string indicating the type of the database * `:dbname` -- a string indicating the name of the database to be used The following optional keys are commonly used: * `:user` -- the username to authenticate with * `:password` -- the password to authenticate with * `:host` -- the hostname or IP address of the database (default: `127.0.0.1`); can be `:none` which means the host/port segment of the JDBC URL should be omitted entirely (for 'local' databases) * `:port` -- the port for the database connection (the default is database- specific -- see below); can be `:none` which means the port segment of the JDBC URL should be omitted entirely * `:classname` -- if you need to override the default for the `:dbtype` (or you want to use a database that next.jdbc does not know about!) The following optional keys can be used to control how JDBC URLs are assembled. This may be needed for `:dbtype` values that `next.jdbc` does not recognize: * `:dbname-separator` -- override the `/` or `:` that normally precedes the database name in the JDBC URL * `:host-prefix` -- override the `//` that normally precedes the IP address or hostname in the JDBC URL * `:property-separator` -- an optional string that can be used to override the separators used in `jdbc-url` for the properties (after the initial JDBC URL portion); by default `?` and `&` are used to build JDBC URLs with properties; for SQL Server drivers (both MS and jTDS) `:property-separator ";"` is used In the second format, this key is required: * `:jdbcUrl` -- a JDBC URL string Any additional options provided will be passed to the JDBC driver's `.getConnection` call as a `java.util.Properties` structure. Database types supported (for `:dbtype`), and their defaults: * `derby` -- `org.apache.derby.jdbc.EmbeddedDriver` -- also pass `:create true` if you want the database to be automatically created * `duckdb` -- `org.duckdb.DuckDBDriver` -- embedded database * `h2` -- `org.h2.Driver` -- for an on-disk database * `h2:mem` -- `org.h2.Driver` -- for an in-memory database * `hsqldb`, `hsql` -- `org.hsqldb.jdbcDriver` * `jtds:sqlserver`, `jtds` -- `net.sourceforge.jtds.jdbc.Driver` -- `1433` * `mariadb` -- `org.mariadb.jdbc.Driver` -- `3306` * `mysql` -- `com.mysql.cj.jdbc.Driver`, `com.mysql.jdbc.Driver` -- `3306` * `oracle:oci` -- `oracle.jdbc.OracleDriver` -- `1521` * `oracle:thin`, `oracle` -- `oracle.jdbc.OracleDriver` -- `1521` * `oracle:sid` -- `oracle.jdbc.OracleDriver` -- `1521` -- uses the legacy `:` separator for the database name but otherwise behaves like `oracle:thin` * `postgresql`, `postgres` -- `org.postgresql.Driver` -- `5432` * `pgsql` -- `com.impossibl.postgres.jdbc.PGDriver` -- no default port * `redshift` -- `com.amazon.redshift.jdbc.Driver` -- no default port * `sqlite` -- `org.sqlite.JDBC` * `sqlserver`, `mssql` -- `com.microsoft.sqlserver.jdbc.SQLServerDriver` -- `1433` * `timesten:client` -- `com.timesten.jdbc.TimesTenClientDriver` * `timesten:direct` -- `com.timesten.jdbc.TimesTenDriver` For more details about `:dbtype` and `:classname` values, see: https://cljdoc.org/d/com.github.seancorfield/next.jdbc/CURRENT/api/next.jdbc.connection#dbtypes

Examples

next-jdbc
(ns next.jdbc-test
  "Basic tests for the primary API of `next.jdbc`."
  (:require [clojure.core.reducers :as r]
            [clojure.string :as str]
            [clojure.test :refer [deftest is testing use-fixtures]]
            [next.jdbc :as jdbc]
            [next.jdbc.connection :as c]
            [next.jdbc.test-fixtures
             :refer [with-test-db db ds column
                     default-options stored-proc?
                     derby? hsqldb? jtds? mssql? mysql? postgres? sqlite?]]
            [next.jdbc.prepare :as prep]
            [next.jdbc.result-set :as rs]
            [next.jdbc.specs :as specs]
            [next.jdbc.types :as types])
  (:import (com.zaxxer.hikari HikariDataSource)
           (com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource)
           (java.sql ResultSet ResultSetMetaData)))

(deftest spec-tests
  (let [db-spec {:dbtype "h2:mem" :dbname "clojure_test"}]
    ;; some sanity checks on instrumented function calls:
    (jdbc/get-datasource db-spec)
    (jdbc/get-connection db-spec)
    ;; and again with options:
    (let [db-spec' (jdbc/with-options db-spec {})]
      (jdbc/get-datasource db-spec')
      (jdbc/get-connection db-spec'))))

(deftest connection-tests
  (testing "datasource via jdbcUrl"
    (when-not (postgres?)
      (let [[url etc] (#'c/spec->url+etc (db))
            ds (jdbc/get-datasource (assoc etc :jdbcUrl url))]
        (cond (derby?) (is (= {:create true} etc))
              (mssql?) (is (= (cond-> #{:user :password}
                                (not (jtds?))
                                (conj :encrypt :trustServerCertificate))
                              (set (keys etc))))
              (mysql?) (is (= #{:user :password :useSSL :allowMultiQueries}
                              (disj (set (keys etc)) :disableMariaDbDriver)))
              :else    (is (= {} etc)))
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) (str "jdbc:"
                                           (cond (jtds?)
                                                 "jtds:sqlserver"
                                                 (mssql?)
                                                 "sqlserver"
                                                 :else
                                                 (:dbtype (db))))))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (jdbc/get-datasource ds)))
        (with-open [con (jdbc/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))))
next-jdbc
  At some point, the datasource/connection tests should probably be extended
  to accept EDN specs from an external source (environment variables?)."
  (:require [clojure.string :as str]
            [clojure.test :refer [deftest is testing use-fixtures]]
            [next.jdbc.connection :as c]
            [next.jdbc.protocols :as p]
            [next.jdbc.specs :as specs]
            [next.jdbc.test-fixtures :refer [with-test-db db]]))

(deftest test-uri-strings
  (testing "datasource via String"
    (let [db-spec (db)
          db-spec (if (= "embedded-postgres" (:dbtype db-spec))
                    (assoc db-spec :dbtype "postgresql")
                    db-spec)
          [url etc] (#'c/spec->url+etc db-spec)
          {:keys [user password]} etc
          etc (dissoc etc :user :password)
          uri (-> url
                  ;; strip jdbc: prefix for fun
                  (str/replace #"^jdbc:" "")
                  (str/replace #";" "?") ; for SQL Server tests
                  (str/replace #":sqlserver" "") ; for SQL Server tests
                  (cond-> (and user password)
                    (str/replace #"://" (str "://" user ":" password "@"))))
          ds (p/get-datasource (assoc etc :jdbcUrl uri))]
      (when (and user password)
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))))
next-jdbc
(ns next.jdbc.test-fixtures
  "Multi-database testing fixtures."
  (:require [clojure.string :as str]
            [next.jdbc :as jdbc]
            [next.jdbc.prepare :as prep]
            [next.jdbc.sql :as sql])
  (:import (io.zonky.test.db.postgres.embedded EmbeddedPostgres)))

  Tests can reach into here and call ds (above) to get a DataSource for use
  in test functions (that operate inside this fixture)."
  [t]
  (doseq [db test-db-specs]
    (reset! test-db-spec db)
    (if (= "embedded-postgres" (:dbtype db))
      (reset! test-datasource
              (.getPostgresDatabase ^EmbeddedPostgres @embedded-pg))
      (reset! test-datasource (jdbc/get-datasource db)))
    (let [fruit (if (mysql?) "fruit" "FRUIT") ; MySQL is case sensitive!
          btest (if (mysql?) "btest" "BTEST")
          auto-inc-pk
          (cond (or (derby?) (hsqldb?))
                (str "GENERATED ALWAYS AS IDENTITY"
                     " (START WITH 1, INCREMENT BY 1)"
                     " PRIMARY KEY")
                (postgres?)
                (str "GENERATED ALWAYS AS IDENTITY"
                     " PRIMARY KEY")
                (mssql?)
                "IDENTITY PRIMARY KEY"
                (sqlite?)
                "PRIMARY KEY AUTOINCREMENT"
                :else
                "AUTO_INCREMENT PRIMARY KEY")]
      (with-open [con (jdbc/get-connection (ds))]
        (when (stored-proc?)
          (try
            (jdbc/execute-one! con ["DROP PROCEDURE FRUITP"])
            (catch Throwable _)))
        (try
          (do-commands con [(str "DROP TABLE " fruit)])
          (catch Exception _))
        (try
          (do-commands con [(str "DROP TABLE " btest)])
          (catch Exception _))
        (when (postgres?)
          (try
            (do-commands con ["DROP TABLE LANG_TEST"])
            (catch Exception _))
          (try
            (do-commands con ["DROP TYPE LANGUAGE"])
            (catch Exception _))
          (do-commands con ["CREATE TYPE LANGUAGE AS ENUM('en','fr','de')"])
          (do-commands con ["
CREATE TABLE LANG_TEST (
  LANG LANGUAGE NOT NULL
)"]))
        (do-commands con [(str "
CREATE TABLE " fruit " (
  ID INTEGER " auto-inc-pk ",
  NAME VARCHAR(32),
  APPEARANCE VARCHAR(32) DEFAULT NULL,
  COST INT DEFAULT NULL,
  GRADE REAL DEFAULT NULL
)")])
        (let [created (atom false)]
          ;; MS SQL Server does not support bool/boolean:
          (doseq [btype ["BOOL" "BOOLEAN" "BIT"]]
            ;; Derby does not support bit:
            (doseq [bitty ["BIT" "SMALLINT"]]
              (try
                (when-not @created
                  (do-commands con [(str "
CREATE TABLE " btest " (
  NAME VARCHAR(32),
  IS_IT " btype ",
  TWIDDLE " bitty "
)")])
                  (reset! created true))
                (catch Throwable _))))
          (when-not @created
            (println (:dbtype db) "failed btest creation")
            #_(throw (ex-info (str (:dbtype db) " has no boolean type?") {}))))
        (when (stored-proc?)
          (let [[begin end] (if (postgres?) ["$$" "$$"] ["BEGIN" "END"])]
            (try
              (do-commands con [(str "
CREATE PROCEDURE FRUITP" (cond (hsqldb?) "() READS SQL DATA DYNAMIC RESULT SETS 2 "
                               (mssql?) " AS "
                               (postgres?) "() LANGUAGE SQL AS "
                               :else "() ") "
 " begin " " (if (hsqldb?)
               (str "ATOMIC
  DECLARE result1 CURSOR WITH RETURN FOR SELECT * FROM " fruit " WHERE COST < 90;
  DECLARE result2 CURSOR WITH RETURN FOR SELECT * FROM " fruit " WHERE GRADE >= 90.0;
  OPEN result1;
  OPEN result2;")
               (str "
  SELECT * FROM " fruit " WHERE COST < 90;
  SELECT * FROM " fruit " WHERE GRADE >= 90.0;")) "
 " end "
")])
              (catch Throwable t
                (println 'procedure (:dbtype db) (ex-message t))))))
       (sql/insert-multi! con :fruit
                          [:name :appearance :cost :grade]
                          [["Apple" "red" 59 nil]
                           ["Banana" "yellow" nil 92.2]
                           ["Peach" nil 139 90.0]
                           ["Orange" "juicy" 89 88.6]]
                          {:return-keys false})
       (t)))))

(comment
  ;; this is a convenience to bring next.jdbc's test dependencies
  ;; into any REPL running Clojure 1.12.0 Alpha 2's new add-libs API
  ;; which allows me to develop and test next.jdbc inside my work's
  ;; "everything" REPL environment
  (require '[clojure.repl.deps :refer [add-libs]]
           '[clojure.edn :as edn])
  (def test-deps (-> (slurp "https://raw.githubusercontent.com/seancorfield/next-jdbc/develop/deps.edn")
                     (edn/read-string)
                     :aliases
                     :test
                     :extra-deps))
  (add-libs test-deps)
  ;; now you can load this file... and then you can load other test
  ;; files and run their tests as needed... which will leave (ds)
  ;; set to the embedded PostgreSQL datasource -- reset it with this:
  (let [db test-h2-mem #_test-mysql-map]
    (reset! test-db-spec db)
    (reset! test-datasource (jdbc/get-datasource db))))
next-jdbc
  At some point, the datasource/connection tests should probably be extended
  to accept EDN specs from an external source (environment variables?)."
  (:require [clojure.string :as str]
            [clojure.test :refer [deftest is testing]]
            [next.jdbc.connection :as c]
            [next.jdbc.protocols :as p])
  (:import (com.zaxxer.hikari HikariDataSource)
           (com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource)))

(deftest test-sourceable-via-metadata
  (doseq [db test-dbs]
    (let [ds (p/get-datasource
              ^{`p/get-datasource (fn [v] (p/get-datasource (first v)))} [db])]
      (is (instance? javax.sql.DataSource ds)))))

(deftest test-get-connection
  (doseq [db test-dbs]
    (println 'test-get-connection (:dbtype db))
    (testing "datasource via Associative"
      (let [ds (p/get-datasource db)]
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) (str "jdbc:" (:dbtype db))))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via String"
      (let [[url _] (#'c/spec->url+etc db)
            ds (p/get-datasource url)]
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) url))
        (.setLoginTimeout ds 0)
        (is (= 0 (.getLoginTimeout ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via jdbcUrl"
      (let [[url etc] (#'c/spec->url+etc db)
            ds (p/get-datasource (assoc etc :jdbcUrl url))]
        (if (= "derby" (:dbtype db))
          (is (= {:create true} etc))
          (is (= {} etc)))
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) (str "jdbc:" (:dbtype db))))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (.setLoginTimeout ds 1)
        (is (= 1 (.getLoginTimeout ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via HikariCP"
      ;; the type hint is only needed because we want to call .close
      (with-open [^HikariDataSource ds (c/->pool HikariDataSource db)]
        (is (instance? javax.sql.DataSource ds))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via c3p0"
      ;; the type hint is only needed because we want to call .close
      (with-open [^PooledDataSource ds (c/->pool ComboPooledDataSource db)]
        (is (instance? javax.sql.DataSource ds))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "connection via map (Object)"
      (with-open [con (p/get-connection db {})]
        (is (instance? java.sql.Connection con))))))
nextjournal/clerk
;; # How Clerk Works 🕵🏻‍♀️
(ns how-clerk-works
  {:nextjournal.clerk/toc true}
  (:require [next.jdbc :as jdbc]
            [nextjournal.clerk :as clerk]
            [nextjournal.clerk.parser :as parser]
            [nextjournal.clerk.eval :as eval]
            [nextjournal.clerk.analyzer :as ana]
            [weavejester.dependency :as dep]))

;; For side effectful functions that should be cached, like a database query, you can add a value like this `#inst` to control when evaluation should happen.
(def query-results
  (let [_run-at #_(java.util.Date.) #inst "2021-05-20T08:28:29.445-00:00"
        ds (next.jdbc/get-datasource {:dbtype "sqlite" :dbname "chinook.db"})]
    (with-open [conn (next.jdbc/get-connection ds)]
      (clerk/table (next.jdbc/execute! conn ["SELECT AlbumId, Bytes, Name, TrackID, UnitPrice FROM tracks"])))))
clj-kondo/clj-kondo
  The clojure.java.jdbc/with-db-* functions would all be treated the same way."
  (:require [next.jdbc :as jdbc]))

(jdbc/with-transaction [tx (jdbc/get-datasource db) {} {}] ;; 2 or 3 forms
  (jdbc/execute! tx ["select * from table where foo = ?" 123]))

(jdbc/with-transaction [[tx] (jdbc/get-datasource db)] ;; requires a symbol
  (jdbc/execute! tx ["select * from table where foo = ?" 123]))
seancorfield/next-jdbc
(ns ^:no-doc next.jdbc.default-options
  "Implementation of default options logic."
  (:require [next.jdbc.protocols :as p]))

(extend-protocol p/Sourceable
  DefaultOptions
  (get-datasource [this]
                  (p/get-datasource (:connectable this))))
seancorfield/next-jdbc
  At some point, the datasource/connection tests should probably be extended
  to accept EDN specs from an external source (environment variables?)."
  (:require [clojure.string :as str]
            [clojure.test :refer [deftest is testing]]
            [next.jdbc.connection :as c]
            [next.jdbc.protocols :as p])
  (:import (com.zaxxer.hikari HikariDataSource)
           (com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource)))

(deftest test-sourceable-via-metadata
  (doseq [db test-dbs]
    (let [ds (p/get-datasource
              ^{`p/get-datasource (fn [v] (p/get-datasource (first v)))} [db])]
      (is (instance? javax.sql.DataSource ds)))))

(deftest test-get-connection
  (doseq [db test-dbs]
    (println 'test-get-connection (:dbtype db))
    (testing "datasource via Associative"
      (let [ds (p/get-datasource db)]
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) (str "jdbc:" (:dbtype db))))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via String"
      (let [[url _] (#'c/spec->url+etc db)
            ds (p/get-datasource url)]
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) url))
        (.setLoginTimeout ds 0)
        (is (= 0 (.getLoginTimeout ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via jdbcUrl"
      (let [[url etc] (#'c/spec->url+etc db)
            ds (p/get-datasource (assoc etc :jdbcUrl url))]
        (if (= "derby" (:dbtype db))
          (is (= {:create true} etc))
          (is (= {} etc)))
        (is (instance? javax.sql.DataSource ds))
        (is (str/index-of (pr-str ds) (str "jdbc:" (:dbtype db))))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (.setLoginTimeout ds 1)
        (is (= 1 (.getLoginTimeout ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via HikariCP"
      ;; the type hint is only needed because we want to call .close
      (with-open [^HikariDataSource ds (c/->pool HikariDataSource db)]
        (is (instance? javax.sql.DataSource ds))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "datasource via c3p0"
      ;; the type hint is only needed because we want to call .close
      (with-open [^PooledDataSource ds (c/->pool ComboPooledDataSource db)]
        (is (instance? javax.sql.DataSource ds))
        ;; checks get-datasource on a DataSource is identity
        (is (identical? ds (p/get-datasource ds)))
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))
    (testing "connection via map (Object)"
      (with-open [con (p/get-connection db {})]
        (is (instance? java.sql.Connection con))))))
seancorfield/next-jdbc
  At some point, the datasource/connection tests should probably be extended
  to accept EDN specs from an external source (environment variables?)."
  (:require [clojure.string :as str]
            [clojure.test :refer [deftest is testing use-fixtures]]
            [next.jdbc.connection :as c]
            [next.jdbc.protocols :as p]
            [next.jdbc.specs :as specs]
            [next.jdbc.test-fixtures :refer [with-test-db db]]))

(deftest test-uri-strings
  (testing "datasource via String"
    (let [db-spec (db)
          db-spec (if (= "embedded-postgres" (:dbtype db-spec))
                    (assoc db-spec :dbtype "postgresql")
                    db-spec)
          [url etc] (#'c/spec->url+etc db-spec)
          {:keys [user password]} etc
          etc (dissoc etc :user :password)
          uri (-> url
                  ;; strip jdbc: prefix for fun
                  (str/replace #"^jdbc:" "")
                  (str/replace #";" "?") ; for SQL Server tests
                  (str/replace #":sqlserver" "") ; for SQL Server tests
                  (cond-> (and user password)
                    (str/replace #"://" (str "://" user ":" password "@"))))
          ds (p/get-datasource (assoc etc :jdbcUrl uri))]
      (when (and user password)
        (with-open [con (p/get-connection ds {})]
          (is (instance? java.sql.Connection con)))))))