mirror of
https://github.com/dimitri/pgloader.git
synced 2025-08-09 07:47:00 +02:00
Implement casting of MySQL ENUM types.
This commit is contained in:
parent
e6d4c73c1b
commit
be0738d70a
@ -4,8 +4,39 @@
|
|||||||
|
|
||||||
(in-package :pgloader.mysql)
|
(in-package :pgloader.mysql)
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; Some functions to deal with ENUM types
|
||||||
|
;;;
|
||||||
|
(defun explode-mysql-enum (ctype)
|
||||||
|
"Convert MySQL ENUM expression into a list of labels."
|
||||||
|
;; from: "ENUM('small', 'medium', 'large')"
|
||||||
|
;; to: ("small" "medium" "large")
|
||||||
|
(mapcar (lambda (x) (string-trim "' )" x))
|
||||||
|
(sq:split-sequence #\, ctype :start (position #\' ctype))))
|
||||||
|
|
||||||
|
(defun get-enum-type-name (table-name column-name)
|
||||||
|
"Return the Type Name we're going to use in PostgreSQL."
|
||||||
|
(format nil "~a_~a" table-name column-name))
|
||||||
|
|
||||||
|
(defun get-create-enum (table-name column-name ctype)
|
||||||
|
"Return a PostgreSQL CREATE ENUM TYPE statement from MySQL enum column."
|
||||||
|
(with-output-to-string (s)
|
||||||
|
(format s "CREATE TYPE ~a AS ENUM (~{'~a'~^, ~});"
|
||||||
|
(get-enum-type-name table-name column-name)
|
||||||
|
(explode-mysql-enum ctype))))
|
||||||
|
|
||||||
|
(defun cast-enum (table-name column-name type ctype typemod)
|
||||||
|
"Cast MySQL inline ENUM type to using a PostgreSQL named type.
|
||||||
|
|
||||||
|
The type naming is hardcoded to be table-name_column-name"
|
||||||
|
(declare (ignore type ctype typemod))
|
||||||
|
(format nil "~a_~a" table-name column-name))
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; The default MySQL Type Casting Rules
|
||||||
|
;;;
|
||||||
(defparameter *default-cast-rules*
|
(defparameter *default-cast-rules*
|
||||||
'((:source (:type "int" :auto-increment t :typemod (< 10))
|
`((:source (:type "int" :auto-increment t :typemod (< 10))
|
||||||
:target (:type "serial"))
|
:target (:type "serial"))
|
||||||
|
|
||||||
(:source (:type "int" :auto-increment t :typemod (>= 10))
|
(:source (:type "int" :auto-increment t :typemod (>= 10))
|
||||||
@ -28,19 +59,30 @@
|
|||||||
(:source (:type "date" :default "0000-00-00")
|
(:source (:type "date" :default "0000-00-00")
|
||||||
:target (:type "date" :drop-default t))
|
:target (:type "date" :drop-default t))
|
||||||
|
|
||||||
(:source (:type "datetime") :target (:type "timestamptz")))
|
(:source (:type "datetime")
|
||||||
|
:target (:type "timestamptz"))
|
||||||
|
|
||||||
|
(:source (:type "enum")
|
||||||
|
:target (:type ,#'cast-enum)))
|
||||||
"Data Type Casting rules to migrate from MySQL to PostgreSQL")
|
"Data Type Casting rules to migrate from MySQL to PostgreSQL")
|
||||||
|
|
||||||
(defvar *cast-rules* nil "Specific casting rules added in the command.")
|
(defvar *cast-rules* nil "Specific casting rules added in the command.")
|
||||||
|
|
||||||
(defun parse-column-typemod (column-type)
|
;;;
|
||||||
"Given int(7), returns the number 7."
|
;;; Handling typmod in the general case, don't apply to ENUM types
|
||||||
(let ((splits (sq:split-sequence-if (lambda (c) (member c '(#\( #\))))
|
;;;
|
||||||
column-type
|
(defun parse-column-typemod (data-type column-type)
|
||||||
:remove-empty-subseqs t)))
|
"Given int(7), returns the number 7.
|
||||||
(if (= 1 (length splits))
|
|
||||||
nil
|
Beware that some data-type are using a typmod looking definition for
|
||||||
(parse-integer (nth 1 splits)))))
|
things that are not typmods at all: enum."
|
||||||
|
(unless (string= "enum" data-type)
|
||||||
|
(let ((splits (sq:split-sequence-if (lambda (c) (member c '(#\( #\))))
|
||||||
|
column-type
|
||||||
|
:remove-empty-subseqs t)))
|
||||||
|
(if (= 1 (length splits))
|
||||||
|
nil
|
||||||
|
(parse-integer (nth 1 splits))))))
|
||||||
|
|
||||||
(defun typemod-expr-matches-p (rule-typemod-expr typemod)
|
(defun typemod-expr-matches-p (rule-typemod-expr typemod)
|
||||||
"Check if an expression such as (< 10) matches given typemod."
|
"Check if an expression such as (< 10) matches given typemod."
|
||||||
@ -60,14 +102,16 @@
|
|||||||
((:not-null rule-source-not-null) nil n-s-p)
|
((:not-null rule-source-not-null) nil n-s-p)
|
||||||
((:auto-increment rule-source-auto-increment) nil ai-s-p))
|
((:auto-increment rule-source-auto-increment) nil ai-s-p))
|
||||||
rule-source
|
rule-source
|
||||||
(destructuring-bind (&key type
|
(destructuring-bind (&key table-name
|
||||||
|
column-name
|
||||||
|
type
|
||||||
ctype
|
ctype
|
||||||
typemod
|
typemod
|
||||||
default
|
default
|
||||||
not-null
|
not-null
|
||||||
auto-increment)
|
auto-increment)
|
||||||
source
|
source
|
||||||
(declare (ignore ctype))
|
(declare (ignore table-name column-name ctype))
|
||||||
(when
|
(when
|
||||||
(and
|
(and
|
||||||
(string= type rule-source-type)
|
(string= type rule-source-type)
|
||||||
@ -79,8 +123,11 @@
|
|||||||
|
|
||||||
(defun format-pgsql-type (source target using)
|
(defun format-pgsql-type (source target using)
|
||||||
"Returns a string suitable for a PostgreSQL type definition"
|
"Returns a string suitable for a PostgreSQL type definition"
|
||||||
(destructuring-bind (&key ((:type source-type))
|
(destructuring-bind (&key ((:table-name source-table-name))
|
||||||
ctype
|
((:column-name source-column-name))
|
||||||
|
((:type source-type))
|
||||||
|
((:ctype source-ctype))
|
||||||
|
((:typemod source-typemod))
|
||||||
default
|
default
|
||||||
not-null
|
not-null
|
||||||
&allow-other-keys)
|
&allow-other-keys)
|
||||||
@ -91,7 +138,11 @@
|
|||||||
target
|
target
|
||||||
(format nil
|
(format nil
|
||||||
"~a~:[~; not null~]~:[~; default '~a'~]"
|
"~a~:[~; not null~]~:[~; default '~a'~]"
|
||||||
type
|
(typecase type
|
||||||
|
(function (funcall type
|
||||||
|
source-table-name source-column-name
|
||||||
|
source-type source-ctype source-typemod))
|
||||||
|
(t type))
|
||||||
(and not-null (not drop-not-null))
|
(and not-null (not drop-not-null))
|
||||||
(and default (not drop-default))
|
(and default (not drop-default))
|
||||||
;; apply the transformation function to the default value
|
;; apply the transformation function to the default value
|
||||||
@ -101,16 +152,20 @@
|
|||||||
;;
|
;;
|
||||||
;; prefer char(24) over just char, that is the column type over the
|
;; prefer char(24) over just char, that is the column type over the
|
||||||
;; data type.
|
;; data type.
|
||||||
ctype)))
|
source-ctype)))
|
||||||
|
|
||||||
(defun apply-casting-rules (dtype ctype default nullable extra
|
(defun apply-casting-rules (dtype ctype default nullable extra
|
||||||
&optional (rules (append *cast-rules*
|
&key
|
||||||
*default-cast-rules*)))
|
table-name column-name ; ENUM support
|
||||||
|
(rules (append *cast-rules*
|
||||||
|
*default-cast-rules*)))
|
||||||
"Apply the given RULES to the MySQL SOURCE type definition"
|
"Apply the given RULES to the MySQL SOURCE type definition"
|
||||||
(let* ((typemod (parse-column-typemod ctype))
|
(let* ((typemod (parse-column-typemod dtype ctype))
|
||||||
(not-null (string-equal nullable "NO"))
|
(not-null (string-equal nullable "NO"))
|
||||||
(auto-increment (string= "auto_increment" extra))
|
(auto-increment (string= "auto_increment" extra))
|
||||||
(source (append (list :type dtype)
|
(source (append (list :table-name table-name)
|
||||||
|
(list :column-name column-name)
|
||||||
|
(list :type dtype)
|
||||||
(list :ctype ctype)
|
(list :ctype ctype)
|
||||||
(when typemod (list :typemod typemod))
|
(when typemod (list :typemod typemod))
|
||||||
(list :default default)
|
(list :default default)
|
||||||
@ -133,13 +188,15 @@
|
|||||||
(apply-casting-rules dtype ctype default nullable extra)
|
(apply-casting-rules dtype ctype default nullable extra)
|
||||||
transform-fn))
|
transform-fn))
|
||||||
|
|
||||||
(defun cast (dtype ctype default nullable extra)
|
(defun cast (table-name column-name dtype ctype default nullable extra)
|
||||||
"Convert a MySQL datatype to a PostgreSQL datatype.
|
"Convert a MySQL datatype to a PostgreSQL datatype.
|
||||||
|
|
||||||
DYTPE is the MySQL data_type and CTYPE the MySQL column_type, for example
|
DYTPE is the MySQL data_type and CTYPE the MySQL column_type, for example
|
||||||
that would be int and int(7) or varchar and varchar(25)."
|
that would be int and int(7) or varchar and varchar(25)."
|
||||||
(destructuring-bind (&key pgtype &allow-other-keys)
|
(destructuring-bind (&key pgtype &allow-other-keys)
|
||||||
(apply-casting-rules dtype ctype default nullable extra)
|
(apply-casting-rules dtype ctype default nullable extra
|
||||||
|
:table-name table-name
|
||||||
|
:column-name column-name)
|
||||||
pgtype))
|
pgtype))
|
||||||
|
|
||||||
(defun transforms (columns)
|
(defun transforms (columns)
|
||||||
@ -184,11 +241,12 @@ that would be int and int(7) or varchar and varchar(25)."
|
|||||||
("c" "varchar" "varchar(25)" nil nil nil)
|
("c" "varchar" "varchar(25)" nil nil nil)
|
||||||
("d" "tinyint" "tinyint(4)" "0" nil nil)
|
("d" "tinyint" "tinyint(4)" "0" nil nil)
|
||||||
("e" "datetime" "datetime" "0000-00-00 00:00:00" nil nil)
|
("e" "datetime" "datetime" "0000-00-00 00:00:00" nil nil)
|
||||||
("f" "date" "date" "0000-00-00" "NO" nil))))
|
("f" "date" "date" "0000-00-00" "NO" nil)
|
||||||
|
("g" "enum" "ENUM('a', 'b')" nil nil nil))))
|
||||||
|
|
||||||
(loop
|
(loop
|
||||||
for (name dtype ctype nullable default extra) in columns
|
for (name dtype ctype nullable default extra) in columns
|
||||||
for pgtype = (cast dtype ctype nullable default extra)
|
for pgtype = (cast "table" name dtype ctype nullable default extra)
|
||||||
for fn in (transforms columns)
|
for fn in (transforms columns)
|
||||||
do
|
do
|
||||||
(format t "~a~14T~a~45T~:[~;using ~a~]~%" ctype pgtype fn fn))))
|
(format t "~a~18T~a~45T~:[~;using ~a~]~%" ctype pgtype fn fn))))
|
||||||
|
14
mysql.lisp
14
mysql.lisp
@ -72,13 +72,23 @@ order by table_name, ordinal_position" dbname)))
|
|||||||
;; free resources
|
;; free resources
|
||||||
(cl-mysql:disconnect)))
|
(cl-mysql:disconnect)))
|
||||||
|
|
||||||
|
(defun get-create-type (table-name cols &key include-drop)
|
||||||
|
"MySQL declares ENUM types inline, PostgreSQL wants explicit CREATE TYPE."
|
||||||
|
(loop
|
||||||
|
for (name dtype ctype default nullable extra) in cols
|
||||||
|
when (string-equal "enum" dtype)
|
||||||
|
collect (when include-drop
|
||||||
|
(let ((type-name (get-enum-type-name table-name name)))
|
||||||
|
(format nil "DROP TYPE IF EXISTS ~a;" type-name)))
|
||||||
|
and collect (get-create-enum table-name name ctype)))
|
||||||
|
|
||||||
(defun get-create-table (table-name cols)
|
(defun get-create-table (table-name cols)
|
||||||
"Return a PostgreSQL CREATE TABLE statement from MySQL columns"
|
"Return a PostgreSQL CREATE TABLE statement from MySQL columns"
|
||||||
(with-output-to-string (s)
|
(with-output-to-string (s)
|
||||||
(format s "CREATE TABLE ~a ~%(~%" table-name)
|
(format s "CREATE TABLE ~a ~%(~%" table-name)
|
||||||
(loop
|
(loop
|
||||||
for ((name dtype ctype default nullable extra) . last?) on cols
|
for ((name dtype ctype default nullable extra) . last?) on cols
|
||||||
for pg-coldef = (cast dtype ctype default nullable extra)
|
for pg-coldef = (cast table-name name dtype ctype default nullable extra)
|
||||||
do (format s " ~a ~22t ~a~:[~;,~]~%" name pg-coldef last?))
|
do (format s " ~a ~22t ~a~:[~;,~]~%" name pg-coldef last?))
|
||||||
(format s ");~%")))
|
(format s ");~%")))
|
||||||
|
|
||||||
@ -90,7 +100,9 @@ order by table_name, ordinal_position" dbname)))
|
|||||||
"Return the list of CREATE TABLE statements to run against PostgreSQL"
|
"Return the list of CREATE TABLE statements to run against PostgreSQL"
|
||||||
(loop
|
(loop
|
||||||
for (table-name . cols) in all-columns
|
for (table-name . cols) in all-columns
|
||||||
|
for extra-types = (get-create-type table-name cols :include-drop include-drop)
|
||||||
when include-drop collect (get-drop-table-if-exists table-name)
|
when include-drop collect (get-drop-table-if-exists table-name)
|
||||||
|
when extra-types append extra-types
|
||||||
collect (get-create-table table-name cols)))
|
collect (get-create-table table-name cols)))
|
||||||
|
|
||||||
(defun pgsql-create-tables (dbname all-columns
|
(defun pgsql-create-tables (dbname all-columns
|
||||||
|
Loading…
Reference in New Issue
Block a user