Skip to content

Commit

Permalink
Adding mysql support
Browse files Browse the repository at this point in the history
  • Loading branch information
kno3comma14 committed Aug 19, 2023
1 parent a97b69e commit bf9efe1
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 14 deletions.
1 change: 1 addition & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
org.clojure/java.jdbc {:mvn/version "0.7.12"},
clj-test-containers/clj-test-containers {:mvn/version "0.7.2"},
org.postgresql/postgresql {:mvn/version "42.3.3"},
com.mysql/mysql-connector-j {:mvn/version "8.1.0"},
piotr-yuxuan/closeable-map {:mvn/version "0.35.0"},
seancorfield/next.jdbc {:mvn/version "1.2.659"},
yogthos/config {:mvn/version "1.2.0"}
Expand Down
9 changes: 9 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
version: "3.3"

services:
mysql:
image: percona:5.7
environment:
- MYSQL_ROOT_PASSWORD
ports:
- "3306:3306"
command:
[--character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci]

postgres:
build:
context: .
Expand Down
4 changes: 4 additions & 0 deletions script/mysql-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
docker-compose -f docker-compose.yml up mysql --no-start
docker-compose -f docker-compose.yml start mysql
docker ps
4 changes: 2 additions & 2 deletions script/postgres-start.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
docker-compose -f docker-compose.yml up --no-start
docker-compose -f docker-compose.yml start
docker-compose -f docker-compose.yml up postgres --no-start
docker-compose -f docker-compose.yml start postgres
docker ps
181 changes: 180 additions & 1 deletion src/xiana/db/client/mysql.clj
Original file line number Diff line number Diff line change
@@ -1 +1,180 @@
(ns xiana.db.client.mysql)
(ns xiana.db.client.mysql
(:require [xiana.db.protocol :as db-protocol]
[clj-test-containers.core :as tc]
[next.jdbc :as jdbc]
[hikari-cp.core :as hcp]
[honeysql.core :as sql]
[xiana.db.migrate :as migr])
(:import (java.sql
Connection)
(java.lang
AutoCloseable)))

(def default-opts {:return-keys true})

(defn get-pool-datasource
[{:xiana/keys [hikari-pool-params mysql]}]
(when (and hikari-pool-params mysql)
(fn [{:keys [port dbname host dbtype user password]}]
(hcp/make-datasource
(merge {:adapter dbtype
:username user
:password password
:database-name dbname
:server-name host
:port-number port}
hikari-pool-params)))))

(defn get-datasource
([config]
(get-datasource config 0))
([config count]
(let [create-datasource (or (:xiana/create-custom-datasource config)
(get-pool-datasource config)
jdbc/get-datasource)
jdbc-opts (merge default-opts
(:xiana/jdbc-opts config))]
(try (-> config
create-datasource
(jdbc/with-options jdbc-opts))
(catch Exception e (if (< count 10)
(get-datasource config (inc count))
(throw e)))))))

(defn docker-mysql!
[{mysql-config :xiana/mysql :as config}]
(let [{:keys [dbname user password image-name]} mysql-config
container (tc/start!
(tc/create
{:image-name image-name
:exposed-ports [5432]
:env-vars {"MYSQL_DB" dbname
"MYSQL_USER" user
"MYSQL_PASSWORD" password}}))

port (get (:mapped-ports container) 5432)
mysql-config (assoc
mysql-config
:port port
:embedded container
:subname (str "//localhost:" port "/" dbname))]
(tc/wait {:wait-strategy :log
:message "accept connections"} (:container container))
(assoc config :xiana/mysql mysql-config)))

(defn migrate!
([config]
(migrate! config 0))
([config count]
(try
(migr/migrate (migr/get-db-config config))
(catch Exception e (if (< count 10)
(migrate! config (inc count))
(throw e))))
config))

(defn connect
"Adds `:datasource` key to the `:xiana/mysql` config section
and duplicates `:xiana/mysql` under the top-level `:db` key."
[{mysql-config :xiana/mysql :as config}]
(let [new-mysql-config (assoc-in mysql-config [:config :datasource] (get-datasource (:config mysql-config)))]
(assoc config
:xiana/mysql (:config new-mysql-config)
:db (:config new-mysql-config))))

(defn ->sql-params
"Parse sql-map using honeysql format function with pre-defined
options that target mysql."
[sql-map]
(sql/format sql-map
{:quoting :ansi
:parameterizer :mysql
:return-param-names false}))

(defn execute
"Gets datasource, parse the given sql-map (query) and
execute it using `jdbc/execute!`, and returns the modified keys"
[datasource sql-map]
(let [sql-params (->sql-params sql-map)]
(jdbc/execute! datasource sql-params default-opts)))

(defn in-transaction
([tx sql-map]
(in-transaction tx sql-map nil))
([tx sql-map jdbc-opts]
{:pre [(instance? Connection tx)]}
(let [sql-params (->sql-params sql-map)]
(jdbc/execute! tx sql-params (merge default-opts jdbc-opts)))))

(defn multi-execute!
[datasource {:keys [queries transaction?]}]
(if transaction?
(jdbc/with-transaction
[tx datasource]
(mapv #(in-transaction tx % (:options datasource)) queries))
(mapv #(execute datasource %) queries)))

(defrecord MySQLDB [config jdbc-opts embedded]
db-protocol/DatabaseP

(define-container [_this]
(docker-mysql! config))

(define-migration [_this]
(migrate! config))

(define-migration [_this count]
(migrate! config count))

(connect [_this]
(let [new-config {:xiana/mysql config
:xiana/jdbc-opts jdbc-opts}]
(connect new-config)))

(define-parameters [_this sql-map]
(->sql-params sql-map))

(execute [this sql-map]
(let [ds (get-in this [:config :datasource])]
(execute ds sql-map)))

(in-transaction [_this tx sql-map]
(in-transaction tx sql-map))

(multi-execute [this query-map]
(let [ds (get-in this [:config :datasource])]
(multi-execute! ds query-map)))

AutoCloseable
(close [this]
(when-let [emb (embedded this)]
(.close emb))))

(def db-access
"Database access interceptor, works from `:query` and from `db-queries` keys
Enter: nil.
Leave: Fetch and execute a given query using the chosen database
driver, if succeeds associate its results into state response data.
Remember the entry query must be a sql-map, e.g:
{:select [:*] :from [:users]}."
{:leave
(fn [{query-or-fn :query
db-queries :db-queries
:as state}]
(let [datasource (get-in state [:deps :db :datasource])
query (cond
(fn? query-or-fn) (query-or-fn state)
:else query-or-fn)
db-data (cond-> []
query (into (execute datasource query))
db-queries (into (multi-execute! datasource db-queries))
:always seq)]
(assoc-in state [:response-data :db-data] db-data)))
:error
(fn [state]
(merge state
{:response {:status 500
:body (pr-str (:error state))}}))})



7 changes: 1 addition & 6 deletions src/xiana/db/client/postgres.clj
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,7 @@

(defrecord PostgresDB [config jdbc-opts embedded]
db-protocol/DatabaseP
(->db-object [_this obj]
(->pgobject obj))

(<-db-object [_this obj]
(<-pgobject obj))


(define-container [_this]
(docker-postgres! config))

Expand Down
4 changes: 1 addition & 3 deletions src/xiana/db/protocol.clj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
(ns xiana.db.protocol)

(defprotocol DatabaseP
(->db-object [this obj])
(<-db-object [this obj])
(defprotocol DatabaseP
(define-container [this])
(define-migration [this] [this count])
(connect [this])
Expand Down
6 changes: 4 additions & 2 deletions src/xiana/db_provisional.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
(ns xiana.db-provisional
(:require [xiana.db.client.postgres :as pg]))
(:require [xiana.db.client.postgres :as pg]
[xiana.db.client.mysql :as mysql]))

(def dbms-map {:xiana/postgresql pg/->PostgresDB})
(def dbms-map {:xiana/postgresql pg/->PostgresDB
:xiana/mysql mysql/->MySQLDB})

0 comments on commit bf9efe1

Please sign in to comment.