File: pgsql.l

package info (click to toggle)
euslisp 9.31%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 55,448 kB
  • sloc: ansic: 41,610; lisp: 3,339; makefile: 286; sh: 238; asm: 138; python: 53
file content (714 lines) | stat: -rw-r--r-- 24,540 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
;;;;****************************************************************
;;;; pgsql.l -- Postgres relational database interface
;;;;Copyright (c) 2000, Toshihiro Matsui, Electrotechnical Laboratory
;;;
;;; Loading this file creates the PQ package, which defines foreign
;;; functions into libpq.so and the pgsql class.  "libpq.so" is assumed
;;; under "/usr/local/pgsql/lib/".
;;;
;;; General notes on the use of Postgres:
;;; You must be registered as a valid postgres user. Use 'createuser' command.
;;; You must have at least one accessible database. Use 'createdb'command.
;;; To get libpq to behave as expected, several environment variables should be
;;; set properly.  They are, PGROOT, PGLIB, PGPORT, PGDATABASE, PGUSER,
;;; PGPASSWORD, PGDATESTYLE, etc.
;;;
;;; Connecting to the Postgres database:
;;;	Instantiate pq:pgsql with proper arguments.  In most cases,
;;;	you just want to specify the database name and the user name.
;;;	If you don't know, just trust the defaults, namely
;;;	(instance pq:pgsql :init) might be ok to make a connection.
;;; Synchronous data transfer:
;;;	There are the synchronous and asynchronous interface in libpq.so.
;;;	Synchronous transfer is easier.  You just send SQL commands by
;;;	:exec method of the pgsql object, and a result is returned.
;;;	(send db :exec "select typname,oid from pg_type order by oid")
;;;	will give you a list of all data types defined in your database.
;;; Asynchronouse database access:
;;;	For the asynchronouse interface, you have to define a function or
;;;	method to receive the query result as the first argument. Let's 
;;;	assume the receiver function is 'print'.  Then a query should be
;;;	issued by the :sendQuery method with the receiver function name
;;;	as the second argument.
;;;	(send db :sendQuery "select oid from pg_type" 'print)
;;; Type conversion
;;;	Postgres database stores data in a variety of forms internally,
;;;	but every data item transferred between the database and the client
;;;	is always converted to the string format. Thus, integer 1234 is "1234",
;;;	and a symbol 'SYMBOL is "symbol".  But, of course, since we want to
;;;	access a database to store lisp data, they should be handled as
;;;	lisp integers and lisp symbols.
;;;	I found the datatype information is stored in the pg_type table.
;;;	When we get data from a table, we can also retrieve the oid (object id)
;;;	attributed to each field.  By looking up pg_type table with the oid,
;;;	we can know the datatype name, such as integer, character, date, etc.
;;;	But postgresql has no symbols!  We can use the 'name' type instead, 
;;;	but still there is incoherency to use it as lisp symbol type, since
;;;	there is no escapes (vertical bar and backslash) and lower-case to
;;;	upcase conversion.  I mean if we use the 'intern' function to
;;;	change the 'name' object to symbol, it becomes a symbol with the
;;;	lower case print-name.  Do we call string-upcase before interning?
;;;	Usually it works, but not always, because escapes are ignored.
;;;	So I defined input and output function for Postgres in 'symbol_io.c'.
;;;	There is also a Makefile for it.  Make symbol_io.so and copy it
;;;	to /usr/local/pgsql/lib.  Invoke psql, and type "\i symbol_io.sql",
;;;	which makes postgres to load the lisp_symbol_io functions, and
;;;	and define the symbol type.
;;;	Call make-type-hashtab function once before any other database retrieval
;;;	for the faster type look-up.
;;;	Then, every data transfered from the database is converted properly.
;;;	Currently, symbol, int, float, char (string), date, time, datetime
;;;	are coerced to corresponding lisp objects. Other unknown type data
;;;	are represented by strings.

;; The following codes put in another file will load this database module,
;; creates the *type-hashtab*, and reads the type list.
;;
;;(load "pgsql")
;;(in-package "USER")
;;(unless (boundp 'db)
;;   (setq db (instance pq:pgsql :init) ))
;;(send db :exec "select * from family")
;;(pq:make-type-hashtab db)
;;(setq types (send db :exec "select typname,oid from pg_type order by oid"))
;;

;; time package is needed.
(let ((*package* (find-package "LISP")))
   (require :time "time")	;;required for date, time and datetime types.
   )

(unless (find-package "PQ") (make-package "PQ"))
(in-package "PQ")

(export '(pgsql query escape-quote table-fields table-attributes
	table-pkey
	tables
	select update-record insert-record insert-record2
	delimit-list delete-record
	record-count))

;****************************************************************
; libpq.so foreign functions
;****************************************************************

#+:linux
(setq *libpq* (cond
               ((probe-file "/usr/lib/libpq.so") (load "/usr/lib/libpq.so"))
               ((probe-file "/usr/lib/x86_64-linux-gnu/libpq.so") (load "/usr/lib/x86_64-linux-gnu/libpq.so"))
               ((probe-file "/usr/lib/i386-linux-gnu/libpq.so") (load "/usr/lib/i386-linux-gnu/libpq.so"))
               (t nil)))
#+:cygwin
(setq *libpq* (cond
               ((probe-file "/usr/bin/cygpq.dll") (load "/usr/bin/cygpq.dll"))
               (t nil)))

(when *libpq*
(defforeign setdbLogin *libpq* "PQsetdbLogin"
	() (:foreign-string 4436))
;; sizeof(PGconn)=4436
;; sizeof(PGresult)=92

(defforeign finish *libpq* "PQfinish" (:string) :integer)
(defforeign pqreset *libpq* "PQreset" (:string) :integer)
(defforeign pqdb *libpq* "PQdb" (:string) (:foreign-string))
(defforeign pquser *libpq* "PQuser" (:string) (:foreign-string))
(defforeign pqpass *libpq* "PQpass" (:string) (:foreign-string))
(defforeign pqhost *libpq* "PQhost" (:string) (:foreign-string))
(defforeign pqport *libpq* "PQport" (:string) (:foreign-string))
(defforeign pqtty *libpq* "PQtty" (:string) (:foreign-string))
(defforeign pqoptions *libpq* "PQoptions" (:string) (:foreign-string))
(defforeign pqstatus *libpq* "PQstatus" (:string) :integer)
(defforeign errorMessage *libpq* "PQerrorMessage" (:string) (:foreign-string))
(defforeign backendPID *libpq* "PQbackendPID" (:string) :integer)
(defforeign pqexec *libpq* "PQexec" (:string :string) :integer)
(defforeign resultStatus *libpq* "PQresultStatus" () :integer)
(defforeign resultErrorMessage *libpq* "PQresultErrorMessage" (:string)
  (:foreign-string))
(defforeign ntuples *libpq* "PQntuples" (:string) :integer)
(defforeign nfields *libpq* "PQnfields" (:string) :integer)
(defforeign binarytuples *libpq* "PQbinaryTuples" (:string) :integer)
(defforeign fname *libpq* "PQfname" (:string :integer) (:foreign-string))
(defforeign fnumber *libpq* "PQfnumber" (:string :string) :integer)
;; ftype returns the field type associated with the given field index.
;; The integer returned is an internal coding of the type. 
;; Field indices start at 0.
(defforeign ftype *libpq* "PQftype" (:string :integer) :integer)
(defforeign fsize *libpq* "PQfsize" (:string :integer) :integer)
(defforeign fmod *libpq* "PQfmod" (:string :integer) :integer)
(defforeign getvalue *libpq* "PQgetvalue" (:string :integer :integer)
		(:foreign-string))
(defforeign getlength *libpq* "PQgetlength" (:string :integer :integer)
		:integer)
(defforeign cmdStatus *libpq* "PQcmdStatus" (:string) (:foreign-string))
(defforeign oidStatus *libpq* "PQoidStatus" () (:foreign-string))
(defforeign clear *libpq* "PQclear" () :integer)

;; Asynchronous interface
(defforeign sendQuery *libpq* "PQsendQuery" (:string :string) :integer)
(defforeign getResult *libpq* "PQgetResult" (:string) :integer)
						;; (:foreign-string 92))
(defforeign consumeInput *libpq* "PQconsumeInput" (:string) :integer)
(defforeign isBusy *libpq* "PQisBusy" (:string) :integer)
(defforeign socket *libpq* "PQsocket" (:string) :integer)


;; Large objects
;; Oid lo_creat(PGconn *conn, int mode)
;; Oid lo_import(PGconn *conn, text *filename)
;; Oid lo_export(PGconn *conn, Oid lobjID, text *filename)
;; int lo_open(PGconn *conn, Oid lobjId, int mode, ...)
;; int lo_write(PGconn *conn, int fd, char  *buf, int len)
;; int lo_lseek(PGconn *conn, int fd, int offset, int whence)
;; int lo_close(PGconn *conn, int fd)
(defforeign lo_creat *libpq* "lo_creat" (:string :integer) :integer)
(defforeign lo_import *libpq* "lo_import" (:string :string) :integer)
(defforeign lo_export *libpq* "lo_export" (:string :integer :string) :integer)
(defforeign lo_open *libpq* "lo_open" (:string :integer :integer) :integer)
(defforeign lo_write *libpq* "lo_write" (:string :integer :string :integer) :integer)
(defforeign lo_read *libpq* "lo_read" (:string :integer :string :integer) :integer)
(defforeign lo_lseek *libpq* "lo_lseek" (:string :integer :integer :integer) :integer)
(defforeign lo_close *libpq* "lo_close" (:string :integer) :integer)
(defforeign lo_unlink *libpq* "lo_unlink" (:string :integer) :integer)
) ;; when *libpq*
;; mode bits for lo_creat, lo_open
;; #define INV_WRITE               0x00020000
;; #define INV_READ                0x00040000
(defconstant INV_WRITE   #x00020000)
(defconstant INV_READ    #x00040000)

(defconstant SEEK_SET 0)
(defconstant SEEK_CUR 1)
(defconstant SEEK_END 2)

(defparameter *pgsql-readtable* (copy-readtable))
;;
;; postgreSQL represents array by enclosing elements with curly braces
;; and by delimiting with commas. Eus reads an array as a list.
;; You might want to coerce the list into a vector.
;;
(set-syntax-from-char #\{ #\lparen *pgsql-readtable*)
(set-syntax-from-char #\} #\rparen *pgsql-readtable*)
(set-syntax-from-char #\, #\space *pgsql-readtable*)

(defvar *pgsql-debug* nil)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; datestyle= ISO	"2000-03-01 10:30:00+09"
;; datestyle= SQL	"03/01/2000 10:30:00.00 JST"
;; datestyle= Postgres	"Wed Mar 01 10:30:00 2000 JST"
;; datestyle= European	"Wed 01 Mar 10:30:00 2000 JST"
;; datestyle= NonEuropean	"Wed Mar 01 10:30:00 2000 JST"
;; datestyle= German	"01.03.2000 10:30:00.00 JST"
;; datestyle= US	"01.03.2000 10:30:00.00 JST" ???
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defun pq-handler (db)
  (format t  "pq-handler db=~s~%" db)
  (send db :consume))

(defun pgsql-field (str)
   (let ((*readtable* *pgsql-readtable*))
      (read-from-string str nil nil)))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; pg_type typically catalogs the following types
;; (oid typname typelem typlen)
;; Note that if typelem==0, then it is a basic (primitive) type,
;; otherwise it is an array type.
;;

#|
((16 "bool" 0 1)
 (17 "bytea" 0 -1)
 (18 "char" 0 1)
 (19 "name" 18 32)
 (20 "int8" 0 8)
 (21 "int2" 0 2)
 (22 "int2vector" 21 32)
 (23 "int4" 0 4)
 (24 "regproc" 0 4)
 (25 "text" 0 -1)
 (26 "oid" 0 4)
 (27 "tid" 0 6)
 (28 "xid" 0 4)
 (29 "cid" 0 4)
 (30 "oidvector" 26 64)
 (32 "SET" 0 -1)
 (210 "smgr" 0 2)
 (600 "point" 701 16)
 (601 "lseg" 600 32)
 (602 "path" 0 -1)
 (603 "box" 600 32)
 (604 "polygon" 0 -1)
 (628 "line" 701 32)
 (629 "_line" 628 -1)
 (700 "float4" 0 4)
 (701 "float8" 0 8)
 (702 "abstime" 0 4)
 (703 "reltime" 0 4)
 (704 "tinterval" 0 12)
 (705 "unknown" 0 -1)
 (718 "circle" 0 24)
 (719 "_circle" 718 -1)
 (790 "money" 0 4)
 (791 "_money" 790 -1)
 (829 "macaddr" 0 6)
 (869 "inet" 0 -1)
 (650 "cidr" 0 -1)
 (1000 "_bool" 16 -1)
 (1001 "_bytea" 17 -1)
 (1002 "_char" 18 -1)
 (1003 "_name" 19 -1)
 (1005 "_int2" 21 -1)
 (1006 "_int2vector" 22 -1)
 (1007 "_int4" 23 -1)
 (1008 "_regproc" 24 -1)
 (1009 "_text" 25 -1)
 (1028 "_oid" 26 -1)
 (1010 "_tid" 27 -1)
 (1011 "_xid" 28 -1)
 (1012 "_cid" 29 -1)
 (1013 "_oidvector" 30 -1)
 (1014 "_bpchar" 1042 -1)
 (1015 "_varchar" 1043 -1)
 (1016 "_int8" 20 -1)
 (1017 "_point" 600 -1)
 (1018 "_lseg" 601 -1)
 (1019 "_path" 602 -1)
 (1020 "_box" 603 -1)
 (1021 "_float4" 700 -1)
 (1022 "_float8" 701 -1)
 (1023 "_abstime" 702 -1)
 (1024 "_reltime" 703 -1)
 (1025 "_tinterval" 704 -1)
 (1027 "_polygon" 604 -1)
 (1033 "aclitem" 0 8)
 (1034 "_aclitem" 1033 -1)
 (1040 "_macaddr" 829 -1)
 (1041 "_inet" 869 -1)
 (651 "_cidr" 650 -1)
 (1042 "bpchar" 0 -1)
 (1043 "varchar" 0 -1)
 (1082 "date" 0 4)
 (1083 "time" 0 8)
 (1182 "_date" 1082 -1)
 (1183 "_time" 1083 -1)
 (1184 "timestamp" 0 8)
 (1185 "_timestamp" 1184 -1)
 (1186 "interval" 0 12)
 (1187 "_interval" 1186 -1)
 (1231 "_numeric" 1700 -1)
 (1266 "timetz" 0 12)
 (1270 "_timetz" 1266 -1)
 (1560 "bit" 0 -1)
 (1561 "_bit" 1560 -1)
 (1562 "varbit" 0 -1)
 (1563 "_varbit" 1562 -1)
 (1700 "numeric" 0 -1)
 (18722 "symbol" 0 -1)
 (18725 "_symbol" 18722 -1))
|#


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defclass pgsql :super propertied-object
	:slots (connection socket-fd
		fields records notify types
		ntuples nfields	;number of tuples and fields
		type-hashtab
		type-conversion
		oid
		))

(defmethod pgsql
 (:init (&key (host "localhost") (port 0) (options 0) (tty 0)
		(dbname (cond ((unix:getenv "PGDATABASE"))
			      (t (unix:getenv "USER"))))
		(user (cond ((unix:getenv "PGUSER"))
			    (t (unix:getenv "USER"))))
		(pass 0))
    (setq connection
	  (setdblogin host port options tty dbname user pass))
    (cond ((zerop (pqstatus connection))
	   (if *debug*
	       (warning-message 4
		 ";; Postgres connection to ~a:~a dbname=~a user=~a~%"
		 (pqhost connection) (pqport connection)
		 (pqdb connection) (pquser connection))  ))
	  (t (error ";; Postgres connection failed due to ~a~%"
		(errorMessage connection))
	     (return-from :init nil))
	  )
    (send self :name (pqdb connection))
    (setq *db* connection)
    (setq type-hashtab (instance hash-table :init :size 100))
    (send self :make-type-hashtab)
    ;; (print connection)
    (setq socket-fd (socket connection)) 
    (send *top-selector* :add-port socket-fd 'pq-handler self)
    (send self :exec "set datestyle to 'ISO'")
    (setq type-conversion t)
    self)
 (:finish ()
    (finish connection)
    (send *top-selector* :remove-port socket-fd)
    (setq connection nil))
 (:dbname () (pqdb connection))
 (:user () (pquser connection))
 (:host () (pqhost connection))
 (:port () (pqport connection))
 (:password () (pqpass connection))
 (:type-conversion (flag) (setq type-conversion flag))
 (:types () types)
 (:oid () oid)
 (:make-type-hashtab ()
   (send type-hashtab :clear)
   (let ((type-list))
      (dolist (tp (send self :exec
	   "select oid,typname,typelem,typlen from pg_type where typtype='b'  order by oid"))
	  ;; typtype='b' means a basic type.
	  ;; typelen= -1 if array size is variable.
	  (let ((*package* *keyword-package*))
		 (push (mapcar #'read-from-string tp) type-list)))
      (dolist (tp (nreverse type-list))
	 (setf (gethash (car tp) type-hashtab) (cdr tp)))
      ))
 (:type-name (id)
   (let ((tp  (gethash id type-hashtab)))
      (cond ((null tp) ':char)
	    ((zerop (second tp))  (first tp))
	    ;; ((equal id 19) ':symbol)	;name
	    ;; I don't think it is a good idea to return the element type
	    ;; for an array type, but what else to do now?
	    ;; (t   (send self :type-name (second tp)))
	    (t  :array)
	    )
   ) )
 (:records () records)
 (:fields () fields)
 (:clear () (setq records nil))
 (:notify (&rest x) (setq notify x))
 (:sendquery (query &rest x)
    (setq records nil)
    (setq notify x)
    (sendquery connection query))
 (:consume ()
    ;; result data is available
    (consumeinput connection)
    (unless (zerop (isbusy connection))
	(error "db ~s is still busy after consumeInput" self))
    (send self :process-result (getResult connection))
    ;; data records are prepared
    (let ((func (car notify)) (args (cdr notify)))
       (if (functionp func)
	   (apply func records args)
	   (send* func (car args) records (cdr args))) )
    )
 (:getfields (result)
    (setq fields nil)
    (dotimes (i (nfields result)) (push (fname result i) fields))
    (nreverse fields))
 (:ntuples () ntuples)
 (:nfields () nfields)
 (:getvalues (result)
    (let ((fields) (r) (rec))
       (setq records nil)
       (setq ntuples (ntuples result))
       (setq nfields (nfields result))
       (setq types (instantiate vector nfields))
       (dotimes (i nfields)
	    (setf (aref types i)  (send self :type-name  (ftype result i))))
	;; (format t "tuples=~d fields=~d~%" ntuples nfields)
	;; (print types)
       (dotimes (j ntuples)
	  (setq rec nil)
	  (dotimes (i nfields)
		(setq r (getvalue result j i))
		(setq r
		    (if type-conversion
		        (case (aref types i)
			 ((:text :char)	;copy is necessary
				;because the result passed from postgress
				;is taken in libpq's memory.
				     (copy-seq r))
			 (:date (read-ISO-date r))
			 (:time (read-ISO-time r))
			 ((:datetime :timespan :timespan)
				     (read-ISO-datetime  r))
			 (:array (pgsql-field r)) ;; an array is read as a list
			 (t (pgsql-field  r)))
			(copy-seq r))
			 )
		(push r rec))
	  (push (nreverse rec) records))
       (send self :getfields result)
       (clear result)
       (setq result nil)	;result is still a foreign string
       (nreverse records))
       )
 (:process-result (stat)
    (case (resultstatus stat)
	(0  (warning-message 4 ";; empty query~%") nil)
	(1  
	    (if *debug* (warning-message 4 ";; pgsql OK~%"))
	    (setq oid (read-from-string 
		(oidStatus stat #|(make-foreign-string stat 92)|# )
		nil nil))
	    (clear stat)
	    oid )
	(2  (send self :getvalues (make-foreign-string stat 92)))
	((3 4) (warning-message 5 ";; copy in/out started~%") nil)
	(5 (warning-message 5 ";; db bad response~%") nil)
	(6 (error  ";; db non fatal error") )
	(7 (error  ";; db fatal error"))
	))
 (:exec (query) (send self :process-result (pqexec connection query)))
 )

;; large object
;; The following piece of code will make a copy of "file-1".
;; (setq oid (send db :lo-import "file-1"))
;; (send db :lo-export oid "file-2"))
;;
(defmethod pgsql
 (:lo-creat (&optional (mode (logior INV_READ INV_WRITE)))
     (lo_creat connection mode))
 (:lo-import (fname)
    (let ((loid))
        (pqexec connection "begin")
        (setq loid (lo_import connection fname))
        (pqexec connection "end")
 	loid))
 (:lo-export (loid fname)
        (pqexec connection "begin")
        (setq loid (lo_export connection loid fname))
        (pqexec connection "end")
 	loid)
 (:lo-open (oid &optional (mode (logior INV_READ INV_WRITE)))
     (lo_open connection oid mode))	;; returns 'fd'
 (:lo-write (fd bytes &optional (len (length bytes)))
     (lo_write connection fd bytes len))
 (:lo-read (fd buf len)
     (lo_read connection fd buf len))
 (:lo-lseek (fd offset &optional (whence SEEK_SET))
     (lo_lseek connection fd offset whence))
 (:lo-close (fd) (lo_close connection fd))
 (:lo-unlink (loid) (lo_unlink connection loid))
 (:lo-test (loid &optional (mode (logior INV_READ INV_WRITE)) &aux f)
     (pqexec connection "begin")
     (unwind-protect
	(setq f (>= (lo_open connection loid mode) 0))
	(pqexec connection "end"))
     f)
 ;;;;;;
 (:lo-put (datum &optional (oid nil))
    (let ((loid) (lofd))
       (pqexec connection "begin")
       (unwind-protect
	   (progn
	       (if (integerp oid)
		   (setq loid oid)
		   (setq loid (lo_creat connection INV_WRITE)))
	;;	(format t "loid=~d~%" loid)
	       (setq lofd (lo_open connection loid INV_WRITE))
	;;	(format t "open fd=~d~%" lofd)
	;;       (if (= lofd 0) (error "cannot open a large object"))
	       (lo_write connection lofd datum (length datum))
	       (lo_close connection lofd))
          (pqexec connection "end"))
       loid))
 (:lo-get (loid)
    (let ((lofd) (buff) (buffs) (rlen 4096))
       (pqexec connection "begin")
       (unwind-protect
	   (progn
	       (setq lofd (lo_open connection loid INV_READ))
	;;       (if (= lofd 0) (error "cannot open a large object"))
	;;	(format t "open fd=~d~%" lofd)
	       (while (= rlen 4096)
		  (setf buff (make-string 4096))
		  (setq rlen (lo_read connection lofd buff 4096))
	;;	(format t "lo_read: rlen=~d datum=~s~%" rlen buff)
		  (if (/= rlen 4096) (setf buff (subseq buff 0 rlen)))
		  (push buff buffs))
	       (lo_close connection lofd))
	  (pqexec connection "end"))
       (apply #'concatenate string (nreverse buffs))))
 )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; (table-fields db table) returns a list of descriptions of fields
;; of the table.  Each description consists of filed-number (1,2,3,...),
;; filed-name and datatype.
(defun table-fields (db table)
   (query db nil
	"select attnum, att.attname, typ.typname
	 from pg_attribute att,  pg_type typ
	 where att.attrelid=(
		select typrelid from pg_type where typname='~a') 
	 and typ.oid=att.atttypid  and attnum>0 order by attnum"
	table)
  )

;; (table-attributes db table) returns a list of table attributes, i.e.,
;; table-name, owner-user,
;; relation-kind, 
;;	'r' = ordinary table,
;;	'i' = index,
;;	'S' = sequence,
;;	'v' = view,
;;	's' = special,
;;	't' = secondary TOAST table 
;; number of fields,
;; flag whether the table has index,
;; flag whether the table has the primary key,
;; and access-list.

(defun table-attributes (db table)
   (car 
   (query db nil
	"select cls.relname, usr.usename, cls.relkind,
		cls.relnatts, cls.relhasindex, cls.relhaspkey, relacl
	 from pg_class cls, pg_user usr
	 where cls.relname='~a' and usr.usesysid=cls.relowner"
	table ))
  )

(defun table-pkey (db table)
  (caar
   (query db nil
	"select attname
	 from pg_attribute
	 where attrelid=(
		select typrelid from pg_type where typname='~a') 
	 and attnum=1
	 order by attnum"
	table)
  ))

;;;;;;;;;;;;;;;;;

(defun escape-quote (str)
   (with-output-to-string (out)
        (dotimes (i (length str))
           (if (eql (char str i) #\') (write-byte #\\ out))
           (write-byte (char str i) out))
        (get-output-stream-string out) ) )


(defun query (db handler &rest sql)
   (setq sql (apply #'format nil sql))
   (if *pgsql-debug* (print sql))
   (if handler
       (send db :sendQuery sql handler)
       (send db :exec sql)))

(defun tables (db)
   (mapcar #'car (query db nil "select * from pg_tables")))

(defun delimit-list (xlist delimiter &optional quotep)
   (let (rlist)
      (dolist (item xlist)
         (push (if quotep
		   (format nil "'~a'" item)
		   (format nil "~a" item))
		rlist)
         (push delimiter rlist))
      (apply #'concatenate string (nreverse (cdr rlist)))))

(defun where (expression)
   (cond ((stringp expression) (format nil "'~a'" expression))
	 ((atom expression) (format nil "~a" expression))
	 ((consp expression)
	  (format nil "(~a)"
	      (delimit-list (mapcar #'where (cdr expression))
		  (format nil " ~a " (first expression)))))
	))


(defun select (db fields table &key where limit limit-offset order-by)
   (query db nil
	"select ~a from ~a ~a ~a ~a"
	(if (consp fields) (delimit-list fields ",") fields)
	(if (consp table) (delimit-list table ",") table)
	(if (and where  (plusp (length where)))
	    (format nil "where ~a " 
		(if (consp where) (where where) where))
	    "")
	(if order-by
	    (format nil "order by ~a" order-by)
	    "")
	(if limit
	    (if limit-offset
		(format nil "limit ~a,~a" limit limit-offset)
		(format nil "limit ~a" limit))
	    "")
   )    )

;; UPDATE table SET column = expression [, ...]
;;     [ FROM fromlist ]
;;     [ WHERE condition ]

(defun update-record (db table field-values &key where)
   (let ((rlist))
      (setq rlist
	(delimit-list
	   (mapcar #'(lambda (fv)
		 (format nil "~a=~a" (car fv)
		     (if (stringp (second fv))
			 (format nil "'~a'" (second fv))
			 (second fv))) )
		 field-values)
	   ", "))
;;      (format t "update ~a set ~a where ~a" table rlist
;;		(where where))
      (send db :exec 
	(if where
	    (format nil "update ~a set ~a where ~a" table rlist
		(where where))
	    (format nil "update ~a set ~a" table rlist)))
      ))


;; INSERT INTO table [ ( column [, ...] ) ]
;;     { VALUES ( expression [, ...] ) | SELECT query }

(defun insert-record (db table field-values)
  (send db :exec (format nil 
	"INSERT INTO ~a (~a) VALUES (~a)"
	table
	(delimit-list (mapcar #'first field-values) ", ")
	(delimit-list (mapcar #'second field-values) ", " t)) )
   )

(defun insert-record2 (db table fields values)
  (send db :exec (format nil 
	"INSERT INTO ~a (~a) VALUES (~a)"
	table
	(delimit-list fields ", ")
	(delimit-list values ", " t)) )
   )


;; DELETE FROM table [ WHERE condition ]

(defun delete-record (db table &key where)
   (send db :exec
      (if where
	  (format nil
		"DELETE FROM ~a where ~a" table (where where))
	  (format nil "DELETE FROM ~a" ))))

;; (record-count db table) returns the number of rows (records)
;; in a table, i.e., the table size.

(defun record-count (db table)
  (caar (select db "count(*)" table)))

(provide :pgsql "@(#)$Id$")