Implement MySQL migration option "downcase|quote identifier".

This commit is contained in:
Dimitri Fontaine 2013-09-20 22:52:45 +02:00
parent 020cd7d3ed
commit c9dd959fbd
3 changed files with 65 additions and 24 deletions

View File

@ -7,6 +7,12 @@
;;; ;;;
;;; Some functions to deal with ENUM types ;;; Some functions to deal with ENUM types
;;; ;;;
(defun apply-identifier-case (identifier case)
"Return a SQL string to use in the output part of a MySQL query."
(ecase case
(:downcase (string-downcase identifier))
(:quote (format nil "\"~a\"" identifier))))
(defun explode-mysql-enum (ctype) (defun explode-mysql-enum (ctype)
"Convert MySQL ENUM expression into a list of labels." "Convert MySQL ENUM expression into a list of labels."
;; from: "ENUM('small', 'medium', 'large')" ;; from: "ENUM('small', 'medium', 'large')"
@ -14,15 +20,17 @@
(mapcar (lambda (x) (string-trim "' )" x)) (mapcar (lambda (x) (string-trim "' )" x))
(sq:split-sequence #\, ctype :start (position #\' ctype)))) (sq:split-sequence #\, ctype :start (position #\' ctype))))
(defun get-enum-type-name (table-name column-name) (defun get-enum-type-name (table-name column-name identifier-case)
"Return the Type Name we're going to use in PostgreSQL." "Return the Type Name we're going to use in PostgreSQL."
(format nil "~a_~a" table-name column-name)) (apply-identifier-case (format nil "~a_~a" table-name column-name)
identifier-case))
(defun get-create-enum (table-name column-name ctype) (defun get-create-enum (table-name column-name ctype
&key (identifier-case :downcase))
"Return a PostgreSQL CREATE ENUM TYPE statement from MySQL enum column." "Return a PostgreSQL CREATE ENUM TYPE statement from MySQL enum column."
(with-output-to-string (s) (with-output-to-string (s)
(format s "CREATE TYPE ~a AS ENUM (~{'~a'~^, ~});" (format s "CREATE TYPE ~a AS ENUM (~{'~a'~^, ~});"
(get-enum-type-name table-name column-name) (get-enum-type-name table-name column-name identifier-case)
(explode-mysql-enum ctype)))) (explode-mysql-enum ctype))))
(defun cast-enum (table-name column-name type ctype typemod) (defun cast-enum (table-name column-name type ctype typemod)

View File

@ -72,45 +72,65 @@ 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) (defun get-create-type (table-name cols &key identifier-case include-drop)
"MySQL declares ENUM types inline, PostgreSQL wants explicit CREATE TYPE." "MySQL declares ENUM types inline, PostgreSQL wants explicit CREATE TYPE."
(loop (loop
for (name dtype ctype default nullable extra) in cols for (name dtype ctype default nullable extra) in cols
when (string-equal "enum" dtype) when (string-equal "enum" dtype)
collect (when include-drop collect (when include-drop
(let ((type-name (get-enum-type-name table-name name))) (let* ((type-name
(get-enum-type-name table-name name identifier-case)))
(format nil "DROP TYPE IF EXISTS ~a;" type-name))) (format nil "DROP TYPE IF EXISTS ~a;" type-name)))
and collect (get-create-enum table-name name ctype))) and collect (get-create-enum table-name name ctype
:identifier-case identifier-case)))
(defun get-create-table (table-name cols) (defun get-create-table (table-name cols &key identifier-case)
"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) (let ((table-name (apply-identifier-case table-name identifier-case)))
(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 table-name name 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?)) for colname = (apply-identifier-case name identifier-case)
do (format s " ~a ~22t ~a~:[~;,~]~%" colname pg-coldef last?))
(format s ");~%"))) (format s ");~%")))
(defun get-drop-table-if-exists (table-name) (defun get-drop-table-if-exists (table-name &key identifier-case)
"Return the PostgreSQL DROP TABLE IF EXISTS statement for TABLE-NAME." "Return the PostgreSQL DROP TABLE IF EXISTS statement for TABLE-NAME."
(format nil "DROP TABLE IF EXISTS ~a;~%" table-name)) (let ((table-name (apply-identifier-case table-name identifier-case)))
(format nil "DROP TABLE IF EXISTS ~a;~%" table-name)))
(defun get-pgsql-create-tables (all-columns &key include-drop) (defun get-pgsql-create-tables (all-columns
&key
include-drop
(identifier-case :downcase))
"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) for extra-types = (get-create-type table-name cols
when include-drop collect (get-drop-table-if-exists table-name) :identifier-case identifier-case
:include-drop include-drop)
when include-drop
collect (get-drop-table-if-exists table-name
:identifier-case identifier-case)
when extra-types append extra-types when extra-types append extra-types
collect (get-create-table table-name cols)))
collect (get-create-table table-name cols
:identifier-case identifier-case)))
(defun pgsql-create-tables (dbname all-columns (defun pgsql-create-tables (dbname all-columns
&key (pg-dbname dbname) include-drop) &key
(identifier-case :downcase)
(pg-dbname dbname)
include-drop)
"Create all MySQL tables in database dbname in PostgreSQL" "Create all MySQL tables in database dbname in PostgreSQL"
(loop (loop
for nb-tables from 0 for nb-tables from 0
for sql in (get-pgsql-create-tables all-columns :include-drop include-drop) for sql in (get-pgsql-create-tables all-columns
:identifier-case identifier-case
:include-drop include-drop)
do (pgloader.pgsql:execute pg-dbname sql) do (pgloader.pgsql:execute pg-dbname sql)
finally (return nb-tables))) finally (return nb-tables)))
@ -430,6 +450,7 @@ order by ordinal_position" dbname table-name)))
(include-drop nil) (include-drop nil)
(create-indexes t) (create-indexes t)
(reset-sequences t) (reset-sequences t)
(identifier-case :downcase) ; or :quote
(truncate t) (truncate t)
only-tables) only-tables)
"Export MySQL data and Import it into PostgreSQL" "Export MySQL data and Import it into PostgreSQL"
@ -443,6 +464,7 @@ order by ordinal_position" dbname table-name)))
(with-silent-timing *state* dbname (with-silent-timing *state* dbname
(format nil "~:[~;DROP then ~]CREATE TABLES" include-drop) (format nil "~:[~;DROP then ~]CREATE TABLES" include-drop)
(pgsql-create-tables dbname all-columns (pgsql-create-tables dbname all-columns
:identifier-case identifier-case
:pg-dbname pg-dbname :pg-dbname pg-dbname
:include-drop include-drop))) :include-drop include-drop)))

View File

@ -154,7 +154,10 @@ Here's a quick description of the format we're parsing here:
(def-keyword-rule "name") (def-keyword-rule "name")
(def-keyword-rule "tables") (def-keyword-rule "tables")
(def-keyword-rule "indexes") (def-keyword-rule "indexes")
(def-keyword-rule "sequences")) (def-keyword-rule "sequences")
(def-keyword-rule "downcase")
(def-keyword-rule "quote")
(def-keyword-rule "identifiers"))
(defrule kw-auto-increment (and "auto_increment" (* (or #\Tab #\Space))) (defrule kw-auto-increment (and "auto_increment" (* (or #\Tab #\Space)))
(:constant :auto-increment)) (:constant :auto-increment))
@ -370,12 +373,19 @@ Here's a quick description of the format we're parsing here:
(defrule option-reset-sequences (and kw-reset kw-sequences) (defrule option-reset-sequences (and kw-reset kw-sequences)
(:constant (cons :reset-sequences t))) (:constant (cons :reset-sequences t)))
(defrule option-identifiers-case (and (or kw-downcase kw-quote) kw-identifiers)
(:lambda (id-case)
(destructuring-bind (action id) id-case
(declare (ignore id))
(cons :identifier-case action))))
(defrule mysql-option (or option-workers (defrule mysql-option (or option-workers
option-truncate option-truncate
option-drop-tables option-drop-tables
option-create-tables option-create-tables
option-create-indexes option-create-indexes
option-reset-sequences)) option-reset-sequences
option-identifiers-case))
(defrule another-mysql-option (and #\, ignore-whitespace mysql-option) (defrule another-mysql-option (and #\, ignore-whitespace mysql-option)
(:lambda (source) (:lambda (source)
@ -894,7 +904,8 @@ LOAD FROM http:///tapoueh.org/db.t
truncate, truncate,
create tables, create tables,
create indexes, create indexes,
reset sequences reset sequences,
downcase identifiers
SET guc_1 = 'value', guc_2 = 'other value' SET guc_1 = 'value', guc_2 = 'other value'
CAST column col1 to timestamptz drop default using zero-dates-to-null, CAST column col1 to timestamptz drop default using zero-dates-to-null,
type varchar to text, type varchar to text,