Improve user code parsing, fix #297.

To be able to use "t" (or "nil") as a column name, pgloader needs to be
able to generate lisp code where those symbols are available. It's
simple enough in that a Common Lisp package that doesn't :use :cl
fullfills the condition, so intern user symbols in a specially crafted
package that doesn't :use :cl.

Now, we still need to be able to run transformation code that is using
the :cl package symbols and the pgloader.transforms functions too. In
this commit we introduce a heuristic to pick symbols either as functions
from pgloader.transforms or anything else in pgloader.user-symbols.

And so that user code may use NIL too, we provide an override mechanism
to the intern-symbol heuristic and use it only when parsing user code,
not when producing Common Lisp code from the parsed load command.
This commit is contained in:
Dimitri Fontaine 2015-09-21 13:19:21 +02:00
parent fe812061c4
commit 598c860cf5
7 changed files with 77 additions and 12 deletions

View File

@ -155,7 +155,7 @@
(:use #:cl
#:pgloader.params #:pgloader.utils #:pgloader.connection
#:pgloader.schema)
(:import-from #:pgloader.transforms #:precision #:scale)
(:import-from #:pgloader.transforms #:precision #:scale #:intern-symbol)
(:import-from #:pgloader.parse-date
#:parse-date-string
#:parse-date-format)

View File

@ -266,7 +266,7 @@
(defrule sexp-symbol (and (symbol-first-character-p character)
(* (symbol-character-p character)))
(:lambda (schars)
(pgloader.transforms:intern-symbol (text schars))))
(pgloader.transforms:intern-symbol (text schars) '(("nil" . cl:nil)))))
(defrule sexp-string-char (or (not-doublequote character) (and #\\ #\")))

View File

@ -26,8 +26,8 @@
(field-name-as-symbol (field-name-or-list)
"we need to deal with symbols as we generate code"
(typecase field-name-or-list
(list (pgloader.transforms:intern-symbol (car field-name-or-list)))
(t (pgloader.transforms:intern-symbol field-name-or-list))))
(list (intern-symbol (car field-name-or-list)))
(t (intern-symbol field-name-or-list))))
(process-field (field-name-or-list)
"Given a field entry, return a function dealing with nulls for it"

View File

@ -7,6 +7,44 @@
(in-package :pgloader.transforms)
;;;
;;; This package is used to generate symbols in the CL code that
;;; project-fields produces, allowing to use symbols such as t. It's
;;; important that the user-symbols package doesn't :use cl.
;;;
(defpackage #:pgloader.user-symbols (:use))
(defun intern-symbol (symbol-name &optional (overrides '()))
"Return a symbol in either PGLOADER.TRANSFORMS if it exists there
already (it's a user provided function) or a PGLOADER.USER-SYMBOLS
package.
OVERRIDES is an alist of symbol . value, allowing called to force certain
values: the classic example is how to parse the \"nil\" symbol-name.
Given OVERRIDES as '((nil . nil)) the returned symbol will be cl:nil
rather than pgloader.user-symbols::nil."
(let ((overriden (assoc symbol-name overrides :test #'string-equal)))
(if overriden
(cdr overriden)
(multiple-value-bind (symbol status)
(find-symbol (string-upcase symbol-name)
(find-package "PGLOADER.TRANSFORMS"))
;; pgloader.transforms package (:use :cl) so we might find variable
;; names in there that we want to actually intern in
;; pgloader.user-symbols so that users may use e.g. t as a column name...
;; so only use transform symbol when it denotes a function
(cond
((and status (fboundp symbol)) symbol) ; a transform function
(t
(intern (string-upcase symbol-name)
(find-package "PGLOADER.USER-SYMBOLS"))))))))
;;;
;;; Some optimisation stanza
;;;
(declaim (inline intern-symbol
zero-dates-to-null
date-with-no-separator
@ -25,14 +63,6 @@
sql-server-uniqueidentifier-to-uuid
sql-server-bit-to-boolean))
;;;
;;; Some tools for reading expressions in the parser, and evaluating them.
;;;
(defun intern-symbol (symbol-name)
(intern (string-upcase symbol-name)
(find-package "PGLOADER.TRANSFORMS")))
;;;
;;; Transformation functions

View File

@ -16,6 +16,7 @@ REGRESS= allcols.load \
csv-keep-extra-blanks.load \
csv-non-printable.load \
csv-nulls.load \
csv-temp.load \
csv-trim-extra-blanks.load \
csv.load \
copy.load \

29
test/csv-temp.load Normal file
View File

@ -0,0 +1,29 @@
--
-- See https://github.com/dimitri/pgloader/issues/297
--
-- The "t" field would be "temperature", for instance.
--
LOAD CSV
FROM inline (a, b, nil, t)
INTO postgresql:///pgloader?temp(a,b,nil,t)
WITH fields terminated by ';'
BEFORE LOAD DO
$$ drop table if exists temp; $$,
$$ CREATE TABLE temp
(
a integer,
b timestamp without time zone,
nil real,
t real
);
$$;
100;2015-01-01 00:00:00;-6;10
101;2015-01-02 00:00:00;-2.1;12.5
102;2015-01-03 00:00:00;3.4;5.5
103;2015-01-04 00:00:00;4.7;-2.3
104;2015-01-05 00:00:00;0.4;0

View File

@ -0,0 +1,5 @@
100 2015-01-01 00:00:00 -6 10
101 2015-01-02 00:00:00 -2.1 12.5
102 2015-01-03 00:00:00 3.4 5.5
103 2015-01-04 00:00:00 4.7 -2.3
104 2015-01-05 00:00:00 0.4 0