~ubuntu-branches/ubuntu/vivid/cl-csv/vivid-proposed

« back to all changes in this revision

Viewing changes to clsql.lisp

  • Committer: Package Import Robot
  • Author(s): Dimitri Fontaine
  • Date: 2014-08-04 19:57:54 UTC
  • mfrom: (1.1.2)
  • Revision ID: package-import@ubuntu.com-20140804195754-vo64b5r1daxwg8ld
Tags: 20140211-1
Quicklisp release update.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
(in-package :cl-csv)
 
2
(cl-interpol:enable-interpol-syntax)
 
3
 
 
4
(defun exec (sql)
 
5
  (clsql-sys:execute-command sql))
 
6
 
 
7
(defmethod format-csv-value ((val clsql-sys:date))
 
8
  (clsql-helper:print-nullable-date val))
 
9
 
 
10
(defmethod format-csv-value ((val clsql-sys:wall-time))
 
11
  (clsql-helper:print-nullable-datetime val))
 
12
 
 
13
(defun export-query ( sql &key stream path)
 
14
  (with-csv-output-stream (s (or stream path))
 
15
    (multiple-value-bind (rows cols)
 
16
        (clsql:query sql :flatp t)
 
17
      (write-csv-row cols :stream s)
 
18
      (write-csv rows :stream s))))
 
19
 
 
20
(defun import-from-csv (table-name &rest keys
 
21
                        &key file data-table (sample-size 1000)
 
22
                        schema (should-have-serial-id "id")
 
23
                        excluded-columns row-fn
 
24
                        (log-fn #'(lambda (&rest args) (declare (ignore args))))
 
25
                        (log-frequency 1000)
 
26
                        &aux (cl-interpol:*list-delimiter* ",")
 
27
                        (*print-pretty* nil))
 
28
  "Will make a best effor to create a table matching the csv's schema and then
 
29
 
 
30
   row-fn (data-row schema table columns)
 
31
          allows you to take actions on a row  (before insert).
 
32
          returning false will prevent the default insert
 
33
 
 
34
   log-fn (msg &rest args)
 
35
          function to log progress
 
36
 
 
37
   log-frequency : how frequent (measured in rows) to log progress
 
38
 
 
39
  "
 
40
;;; TODO: accept column names from params
 
41
;;; TODO: figure out how to process files without columns in the first row
 
42
;;; TODO: figure out how to override type guesses
 
43
;;; TODO: check if the table already exists and skip the guessing
 
44
  (funcall log-fn "Starting import ~a" table-name)
 
45
  (let ((dt (or data-table (get-data-table-from-csv file t t sample-size)))
 
46
        (keys (copy-list keys)))
 
47
    (dolist (k '(:file :data-table :row-fn :sample-size :log-fn :log-frequency))
 
48
      (remf keys k))
 
49
    (funcall log-fn "CSV scanned for type information")
 
50
    (when (and should-have-serial-id
 
51
               (member should-have-serial-id (data-table:column-names dt) :test #'string-equal))
 
52
      (error #?"This table already has an id column name `${should-have-serial-id}` Column! Perhaps you wish to turn off should-have-serial-id or assign it a different name?"))
 
53
    (apply #'data-table:ensure-table-for-data-table dt table-name keys)
 
54
    (funcall log-fn "Created table, starting import")
 
55
    (let ((start-time (get-universal-time)))
 
56
      (flet ((log-progress (row-num &optional (msg "Processing row"))
 
57
               (let ((elapsed (- (get-universal-time) start-time)))
 
58
                 (funcall log-fn "~a ~a. ~ds elapsed (~,2f rows/sec) "
 
59
                          msg row-num elapsed
 
60
                          (if (zerop elapsed) "Inf"
 
61
                              (/ row-num elapsed))))))
 
62
        (iter
 
63
          (with importer = (data-table::make-row-importer
 
64
                            dt table-name :schema schema :excluded-columns excluded-columns
 
65
                            :row-fn row-fn))
 
66
          (for row in-csv file SKIPPING-HEADER T)
 
67
          (for row-num from 1)
 
68
          (funcall importer row)
 
69
          (when (zerop (mod row-num log-frequency)) (log-progress row-num))
 
70
          (finally (log-progress row-num "Finished, total processed: ")))))))
 
71
 
 
72
(defun serial-import-from-csv (table-name
 
73
                               &key file
 
74
                               (column-names :first-row)
 
75
                               (schema "public") (column-transform
 
76
                                                  #'data-table::english->postgres)
 
77
                               (progress-stream t) (progress-mod 5000)
 
78
                               (data-munger (lambda (row)
 
79
                                              (mapcar #'clsql-helper:format-value-for-database
 
80
                                                      row)))
 
81
                               (log (lambda (msg &rest args)
 
82
                                      (apply #'format progress-stream msg args)))
 
83
                               (on-error 'continue-importing)
 
84
                               &aux columns (cl-interpol:*list-delimiter* ", "))
 
85
  "Will make a best effor to create a table matching the csv's schema and then
 
86
 
 
87
   data-munger : a function that changes the the data row to be inserted
 
88
                 (for conversion to other types etc)
 
89
   progress-stream: the default log stream to print to
 
90
   progress-mod: how often we should report progress (in number of rows)
 
91
   log: is a function that accepts msg and args to display status updates to the user
 
92
   on-error: is a restart to run when an error is experienced, if nil, it will simply
 
93
            allow the error to go up the stack.  the default is to continue with the
 
94
            import, skipping the row that caused errors
 
95
  "
 
96
  (unless (eql column-names :first-row)
 
97
    (setf columns (data-table::sql-escaped-column-names
 
98
                   column-names
 
99
                   :transform column-transform)))
 
100
  (iter
 
101
    (for row in-csv file)
 
102
    (for cnt from 0)
 
103
    (if (and (eql column-names :first-row) (first-iteration-p))
 
104
        (setf columns (data-table::sql-escaped-column-names row :transform column-transform))
 
105
        (restart-case
 
106
            (handler-bind
 
107
                ((error (lambda (c)
 
108
                          (funcall log "Error importing ROW ~D of file: ~S~%~S~%~A~%~S"
 
109
                                   cnt file row c c)
 
110
                          (when on-error
 
111
                            (when (find-restart on-error) (invoke-restart on-error))))))
 
112
              (exec #?"INSERT INTO ${schema}.${table-name} (@{ columns })
 
113
VALUES ( @{ (funcall data-munger row) } )")
 
114
              (when (zerop (mod cnt progress-mod))
 
115
                (funcall log "Imported ~D rows~%" cnt )))
 
116
          (continue-importing ()
 
117
            :report "Continue Importing the file, skipping this row of data")))))