source: main/xbase.lisp

Last change on this file was main,8, checked in by David Owen <dsowen@…>, 19 years ago

Can create connection-temporary and transaction-temporary tables.

File size: 7.0 KB
Line 
1(require '#:asdf)
2(require '#:cl-ppcre)
3(require '#:dso-binary-class)
4
5(use-package '#:cl-ppcre)
6(use-package '#:binary-class)
7
8
9
10;;; xBase stuff.
11
12(define-file-struct dbf-header
13  (version :unsigned 1)
14  (last-update :date "YYMMDD")
15  (record-count :unsigned 4)
16  (header-length :unsigned 2)
17  (record-length :unsigned 2)
18  (nil nil 2)
19  (incomplete-transaction :unsigned 1)
20  (encrypted :unsigned 1)
21  (nil nil 12)
22  (mdx-flag :unsigned 1)
23  (language :unsigned 1)
24  (nil nil 2))
25
26(define-file-struct field-desc
27  (name :string 11)
28  (type :string 1)
29  (nil nil 4)
30  (size :unsigned 1)
31  (precision :unsigned 1)
32  (nil nil 5)
33  (set-fields-flag :unsigned 1)
34  (nil nil 7)
35  (is-indexed :unsigned 1))
36
37(defun peek-byte (in)
38  (let ((b (read-byte in)))
39    (file-position in (1- (file-position in)))
40    b))
41
42(defun read-field-descs (in &optional descs)
43  (let ((b (peek-byte in)))
44    (if (= b 13)
45        (nreverse descs)
46        (read-field-descs in (cons (read-binary 'field-desc in) descs)))))
47
48(defun read-dbf-structure (file)
49  (with-open-file (in file :element-type '(unsigned-byte 8))
50    (let* ((dbf-header (read-binary 'dbf-header in))
51           (field-descs (read-field-descs in)))
52      (values dbf-header field-descs))))
53
54
55
56(defun slot-from-field (desc)
57  (with-slots (name type size) desc
58    (let ((type (cond
59                  ((string= type "C") :string)
60                  ((string= type "N") :string)
61                  ((string= type "D") :string)
62                  ((string= type "M") :string)
63                  ((string= type "L") :string)
64                  (t (abort)))))
65      `(,(intern name) ,type ,size))))
66
67(defun struct-from-field-descs (name descs)
68  `(define-file-struct ,name
69     (%deleted :unsigned 1)
70     ,@(mapcar #'slot-from-field descs)))
71
72(defstruct dbf-desc
73  name
74  filename
75  sql-create)
76
77(defvar *dbfs* (make-hash-table :test 'eq))
78
79(defun sql-escape-fieldname (name)
80  (let ((name (string-downcase name)))
81    (if (member name '("group" "user") :test 'equal)
82        (concatenate 'string "\"" name "\"")
83        name)))
84
85(defun recode (str)
86  (let (r
87        e)
88    (dotimes (i (length str))
89      (let* ((ch (aref str i))
90             (code (char-code ch)))
91        (cond
92          ((or (<= (char-code #\Space) code (char-code #\[))
93               (<= (char-code #\]) code (char-code #\~)))
94           (push (string ch) r))
95          ((<= 0 code 255)
96           (push (format nil "\\x~2,'0X" code) r)
97           (setf e t))
98          (t
99           (error "Bad character!")))))
100    (values
101     (apply #'concatenate 'string (nreverse r))
102     e)))
103
104(defun sql-escape-data (value)
105  (if (string= value "")
106      "null"
107      (multiple-value-bind (s recoded) (recode value)
108        (concatenate 'string
109                     (if recoded "E'" "'")
110                     (regex-replace-all "'" s "''")
111                     "'"))))
112
113(defun sql-column (desc)
114  (with-slots (name type size precision) desc
115    (format nil "~A ~A"
116            (sql-escape-fieldname name)
117            (cond
118              ((string= type "C")
119               (format nil "VARCHAR(~A)" size))
120              ((string= type "N")
121               (if (= precision 0)
122                   (cond
123                     ((< size 10) "INTEGER")
124                     ((< size 19) "BIGINT")
125                     (t "NUMERIC(~A)" size))
126                   (format nil "NUMERIC(~A,~A)" (1- size) precision)))
127              ((string= type "D")
128               "DATE")
129              ((string= type "M")
130               (warn "MEMOs aren't implemented yet.")
131               "NUMERIC(10)")
132              ((string= type "L")
133               "BOOLEAN")
134              (t (error "Bad type ~S." type))))))
135
136(defvar *temporary-tables* nil)
137
138(defun sql-create (name descs)
139  (format nil "CREATE ~:[~;TEMP ~]TABLE ~A~%   (~{~A~@{,~%    ~A~}~})~:[~;~%ON COMMIT DROP~]"
140          *temporary-tables*
141          name
142          (mapcar 'sql-column descs)
143          (eql *temporary-tables* 1)))
144
145(defgeneric sql-insert (obj))
146
147(defun make-sql-function (type name descs)
148  `(defmethod sql-insert ((obj ,type))
149     (format
150      nil
151      ,(format nil "INSERT INTO ~A(~{~A~@{,~A~}~}) VALUES(~{~A~@{,~A~}~})"
152               name
153               (mapcar (lambda (desc)
154                         (sql-escape-fieldname (field-desc-name desc)))
155                       descs)
156               (mapcar (lambda (d)
157                         (declare (ignore d))
158                         "~A")
159                       descs))
160      ,@(mapcar (lambda (desc)
161                  `(sql-escape-data
162                    (slot-value obj ',(intern (field-desc-name desc)))))
163                descs))))
164
165(defmacro define-dbf-struct (name filename)
166  (let ((descs (nth-value 1 (read-dbf-structure filename))))
167    `(progn
168       (setf (gethash ',name *dbfs*)
169             (make-dbf-desc :name ',name
170                            :filename ,filename
171                            :sql-create ,(sql-create name descs)))
172       ,(struct-from-field-descs name descs)
173       ,(make-sql-function name name descs))))
174
175
176
177;;; Now file stuff.
178
179(defun read-records (in type &optional records)
180  (if (or (not (listen in)) (= (peek-byte in) 26))
181      (nreverse records)
182      (read-records in type (cons (read-binary type in) records))))
183
184(defun read-dbf (type)
185  (let ((dbf-desc (gethash type *dbfs*)))
186    (with-slots (filename) dbf-desc
187      (let ((header (read-dbf-structure filename)))
188        (with-open-file (in filename :element-type '(unsigned-byte 8))
189          (file-position in (slot-value header 'header-length))
190          (read-records in type))))))
191
192
193
194(defun import-dbf (filename table-name)
195  "Reads the DBF file, analyzing meta-data to create Lisp structures,
196populating those structures, and constructing descriptive SQL."
197  (let ((field-descs (nth-value 1 (read-dbf-structure filename))))
198    (setf (gethash 'import-struct *dbfs*)
199          (make-dbf-desc :name 'import-struct
200                         :filename filename
201                         :sql-create (sql-create table-name field-descs)))
202    (eval (struct-from-field-descs 'import-struct field-descs))
203    (eval (make-sql-function 'import-struct table-name field-descs))
204    (read-dbf 'import-struct)))
205
206(defun split-name (filename)
207  (unless (pathnamep filename)
208    (setf filename (pathname filename)))
209  (let ((name (pathname-name filename)))
210    (values (subseq name 0 4)
211            (subseq name 4))))
212
213(defun do-dbf (filename)
214  (multiple-value-bind (db-name table-name) (split-name filename)
215    (let ((recs (import-dbf filename table-name)))
216      (format t "~A;~4%"
217              (slot-value (gethash 'import-struct *dbfs*) 'sql-create))
218      (dolist (rec recs)
219        (unless (/= (slot-value rec '%deleted) 32)
220          (format t "~2&~A;~%" (sql-insert rec)))))))
221
222(defun main ()
223  (unless (member (length *posix-argv*) '(2 3))
224    (format *error-output* "Usage: xbase [-t | -tt] dbf-file~%")
225    (quit))
226  (let* ((*temporary-tables* (position (second *posix-argv*) '("-t" "-tt") :test 'string=))
227         (file (if *temporary-tables*
228                   (third *posix-argv*)
229                   (second *posix-argv*))))
230    (do-dbf file))
231  (quit))
Note: See TracBrowser for help on using the repository browser.