;;; ;;; Tools to handle MS SQL data type casting rules ;;; (in-package :pgloader.mssql) (defparameter *mssql-default-cast-rules* `((:source (:type "char") :target (:type "text" :drop-typemod t)) (:source (:type "nchar") :target (:type "text" :drop-typemod t)) (:source (:type "varchar") :target (:type "text" :drop-typemod t)) (:source (:type "nvarchar") :target (:type "text" :drop-typemod t)) (:source (:type "xml") :target (:type "text" :drop-typemod t)) (:source (:type "bit") :target (:type "boolean") :using pgloader.transforms::sql-server-bit-to-boolean) (:source (:type "uniqueidentifier") :target (:type "uuid") :using pgloader.transforms::sql-server-uniqueidentifier-to-uuid) (:source (:type "hierarchyid") :target (:type "bytea") :using pgloader.transforms::byte-vector-to-bytea) (:source (:type "geography") :target (:type "bytea") :using pgloader.transforms::byte-vector-to-bytea) (:source (:type "tinyint") :target (:type "smallint")) (:source (:type "float") :target (:type "float") :using pgloader.transforms::float-to-string) (:source (:type "real") :target (:type "real") :using pgloader.transforms::float-to-string) (:source (:type "double") :target (:type "double precision") :using pgloader.transforms::float-to-string) (:source (:type "numeric") :target (:type "numeric") :using pgloader.transforms::float-to-string) (:source (:type "decimal") :target (:type "numeric") :using pgloader.transforms::float-to-string) (:source (:type "money") :target (:type "numeric") :using pgloader.transforms::float-to-string) (:source (:type "smallmoney") :target (:type "numeric") :using pgloader.transforms::float-to-string) (:source (:type "binary") :target (:type "bytea") :using pgloader.transforms::byte-vector-to-bytea) (:source (:type "varbinary") :target (:type "bytea") :using pgloader.transforms::byte-vector-to-bytea) (:source (:type "datetime") :target (:type "timestamptz")) (:source (:type "datetime2") :target (:type "timestamptz"))) "Data Type Casting to migrate from MSSQL to PostgreSQL") ;;; ;;; Specific implementation of schema migration, see the API in ;;; src/pgsql/schema.lisp ;;; (defstruct (mssql-column (:constructor make-mssql-column (schema table-name name type default nullable identity character-maximum-length numeric-precision numeric-precision-radix numeric-scale datetime-precision character-set-name collation-name))) schema table-name name type default nullable identity character-maximum-length numeric-precision numeric-precision-radix numeric-scale datetime-precision character-set-name collation-name) (defmethod mssql-column-ctype ((col mssql-column)) "Build the ctype definition from the full mssql-column information." (let ((type (mssql-column-type col))) (cond ((and (string= type "int") (mssql-column-identity col)) "bigserial") ((member type '("float" "real") :test #'string=) ;; see https://msdn.microsoft.com/en-us/library/ms173773.aspx ;; scale is supposed to be nil, and useless in PostgreSQL, so we ;; just ignore it (format nil "~a(~a)" type (mssql-column-numeric-precision col))) ((member type '("decimal" "numeric" ) :test #'string=) ;; https://msdn.microsoft.com/en-us/library/ms187746.aspx (cond ((null (mssql-column-numeric-precision col)) type) (t (format nil "~a(~a,~a)" type (mssql-column-numeric-precision col) (or (mssql-column-numeric-scale col) 0))))) (t type)))) (defmethod cast ((field mssql-column)) "Return the PostgreSQL type definition from given MS SQL column definition." (with-slots (schema table-name name type default nullable) field (declare (ignore schema)) ; FIXME (let* ((ctype (mssql-column-ctype field)) (pgcol (apply-casting-rules table-name name type ctype default nullable nil))) ;; the MS SQL driver smartly maps data to the proper CL type, but the ;; pgloader API only wants to see text representations to send down the ;; COPY protocol. (unless (column-transform pgcol) (setf (column-transform pgcol) (lambda (val) (if val (format nil "~a" val) :null)))) pgcol)))