diff --git a/README.md b/README.md index b98dff9..cb521ce 100644 --- a/README.md +++ b/README.md @@ -388,6 +388,7 @@ Common args for all commands: | `:migrations-dir` | Path to store migrations dir, relative to the `resources` dir. | `false` | string path (example: `"path/to/migrations"`) | `"db/migrations"` | | `:resources-dir` | Path to resources dir to create migrations dir when it doesn't exist yet. | `false` | string path (example: `"path/to/resources"`) | `"resources"` | | `:migrations-table` | Model name for storing applied migrations. | `false` | string (example: `"migrations"`) | `"automigrate_migrations"` | +| `:custom-types` | Set of custom field types to be used in models. | `false` | `nil` | ### `make` @@ -800,3 +801,29 @@ make release :patch # bump git tag version by semver rules and push to remote r Copyright © 2021 Andrey Bogoyavlenskiy Distributed under the MIT License. + +### Custom field types + +You can use custom field types in your models by providing a set of custom types to the commands: + +```clojure +;; Using with make command +(make {:custom-types #{:dml-type}}) + +;; Using with migrate command +(migrate {:custom-types #{:dml-type}}) + +;; Using with explain command +(explain {:number 7 + :custom-types #{:dml-type}}) + +;; Using in models.edn +{:users-change-history + {:fields [[:changed-dml :dml-type {:null false}]]}} +``` + +Note: The custom type must be already defined in your database before using it in migrations. For example: + +```sql +CREATE TYPE dml_type AS ENUM ('INSERT', 'UPDATE', 'DELETE'); +``` diff --git a/src/automigrate/core.clj b/src/automigrate/core.clj index d3fc1f2..72f7a52 100644 --- a/src/automigrate/core.clj +++ b/src/automigrate/core.clj @@ -9,7 +9,8 @@ [automigrate.util.spec :as spec-util] [automigrate.util.file :as file-util] [automigrate.errors :as errors] - [automigrate.help :as automigrate-help]) + [automigrate.help :as automigrate-help] + [automigrate.fields :as fields]) (:refer-clojure :exclude [list])) @@ -21,6 +22,7 @@ (s/def ::jdbc-url (s/and some? (s/conformer str))) (s/def ::jdbc-url-env-var string?) (s/def ::number int?) +(s/def ::custom-types (s/coll-of keyword? :kind set?)) (s/def ::cmd @@ -64,7 +66,8 @@ ::name ::models-file ::migrations-dir - ::resources-dir])) + ::resources-dir + ::custom-types])) (s/def ::migrate-args @@ -123,9 +126,11 @@ Available options: Set `:empty-sql` - for creating an empty raw SQL migration. (optional) :models-file - Path to the file with model definitions relative to the `resources` dir. Default: `db/models.edn`. (optional) :migrations-dir - Path to directory containing migration files relative to the `resources` dir. Default: `db/migrations`. (optional) - :resources-dir - Path to resources dir to create migrations dir, if it doesn't exist. Default: `resources` (optional)" - [args] - (run-fn migrations/make-migration args ::make-args)) + :resources-dir - Path to resources dir to create migrations dir, if it doesn't exist. Default: `resources` (optional) + :custom-types - Set of custom field types to be used in models. Example: #{:dml-type}. (optional)" + [{:keys [custom-types] :as args}] + (binding [fields/*custom-types* custom-types] + (run-fn migrations/make-migration args ::make-args))) (defn migrate @@ -136,14 +141,16 @@ Available options: :jdbc-url - JDBC url for the database connection. Default: get from `DATABASE_URL` env var. (optional) :jdbc-url-env-var - Name of environment variable for jdbc-url. Default: `DATABASE_URL`. (optional) :migrations-dir - Path to directory containing migration files relative to the `resources` dir. Default: `db/migrations`. (optional) - :migrations-table - Custom name for the migrations table in the database. (optional)" + :migrations-table - Custom name for the migrations table in the database. (optional) + :custom-types - Set of custom field types to be used in models. Example: #{:dml-type}. (optional)" ([] ; 0-arity function can be used inside application code if there are no any options. (migrate {})) - ([{:keys [jdbc-url-env-var] :as args}] - (let [jdbc-url-env-var* (or jdbc-url-env-var JDBC-URL-ENV-VAR) - args* (update args :jdbc-url #(or % (System/getenv jdbc-url-env-var*)))] - (run-fn migrations/migrate args* ::migrate-args)))) + ([{:keys [jdbc-url-env-var custom-types] :as args}] + (binding [fields/*custom-types* custom-types] + (let [jdbc-url-env-var* (or jdbc-url-env-var JDBC-URL-ENV-VAR) + args* (update args :jdbc-url #(or % (System/getenv jdbc-url-env-var*)))] + (run-fn migrations/migrate args* ::migrate-args))))) (defn explain @@ -153,9 +160,11 @@ Available options: :number - Integer number of the migration to explain. (required) :direction - Direction of the migration to explain, can be `forward` (default) or `backward`. (optional) :format - Format of explanation, can be `sql` (default) or `human`. (optional) - :migrations-dir - Path to directory containing migration files relative to the `resources` dir. Default: `db/migrations`. (optional)" - [args] - (run-fn migrations/explain args ::explain-args)) + :migrations-dir - Path to directory containing migration files relative to the `resources` dir. Default: `db/migrations`. (optional) + :custom-types - Set of custom field types to be used in models. Example: #{:dml-type}. (optional)" + [{:keys [custom-types] :as args}] + (binding [fields/*custom-types* custom-types] + (run-fn migrations/explain args ::explain-args))) (defn list @@ -204,8 +213,10 @@ Available options: (def cli-options-make (concat cli-options-common - [[nil "--name NAME"]] - [[nil "--type TYPE"]])) + [[nil "--name NAME"] + [nil "--type TYPE"] + [nil "--custom-types TYPES" + :parse-fn #(set (map keyword (str/split % #",")))]])) (def cli-options-migrate diff --git a/src/automigrate/fields.clj b/src/automigrate/fields.clj index 2bfce2f..12ee2ff 100644 --- a/src/automigrate/fields.clj +++ b/src/automigrate/fields.clj @@ -118,6 +118,9 @@ :datemultirange})) +(def ^:dynamic *custom-types* nil) + + (defn- field-type-dispatch [value] (cond @@ -128,9 +131,15 @@ (defmulti field-type field-type-dispatch) +(s/def ::custom-type keyword?) + + (defmethod field-type :keyword - [_] - ::keyword-type) + [value] + (if (and (some? *custom-types*) + (contains? *custom-types* value)) + ::custom-type + ::keyword-type)) (defmethod field-type :char diff --git a/src/automigrate/sql.clj b/src/automigrate/sql.clj index 0886c95..c30d3e4 100644 --- a/src/automigrate/sql.clj +++ b/src/automigrate/sql.clj @@ -174,6 +174,10 @@ [{array-value :array type-value :type}] (let [type-sql (cond + (and (some? fields/*custom-types*) + (contains? fields/*custom-types* type-value)) + [:raw (name (model-util/kw->snake-case type-value))] + ; :add-column clause in honeysql converts type name in kebab case into ; two separated words. So, for custom enum types we have to convert ; custom type name to snake case to use it in SQL as a single word. diff --git a/test/automigrate/help_test.clj b/test/automigrate/help_test.clj index 103fb9b..ca45c55 100644 --- a/test/automigrate/help_test.clj +++ b/test/automigrate/help_test.clj @@ -25,6 +25,7 @@ " Set `:empty-sql` - for creating an empty raw SQL migration. (optional)\n" " :models-file - Path to the file with model definitions relative to the `resources` dir. Default: `db/models.edn`. (optional)\n" " :migrations-dir - Path to directory containing migration files relative to the `resources` dir. Default: `db/migrations`. (optional)\n" - " :resources-dir - Path to resources dir to create migrations dir, if it doesn't exist. Default: `resources` (optional)\n\n") + " :resources-dir - Path to resources dir to create migrations dir, if it doesn't exist. Default: `resources` (optional)\n" + " :custom-types - Set of custom field types to be used in models. Example: #{:dml-type}. (optional)\n\n") (with-out-str (help/show-help! {:cmd 'make})))))