pg-cvs
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- 76 discussions
Update of /project/pg/cvsroot/pg
In directory common-lisp.net:/tmp/cvs-serv13025
Modified Files:
pg-tests.lisp
Log Message:
added pbe test
Date: Mon Mar 8 09:37:43 2004
Author: pvaneynde
Index: pg/pg-tests.lisp
diff -u pg/pg-tests.lisp:1.2 pg/pg-tests.lisp:1.3
--- pg/pg-tests.lisp:1.2 Fri Mar 5 13:08:08 2004
+++ pg/pg-tests.lisp Mon Mar 8 09:37:43 2004
@@ -7,7 +7,7 @@
;;
;; These tests assume that a table named "test" is defined in the
;; system catalog, and that the user identified in
-;; CALL-WITH-TEST-CONNECTION has the rights to access that table.
+;; CALL-WITH-TEST-CONNECTION has the rights to access that table.
(defpackage :pg-tests
(:use :cl
@@ -279,6 +279,42 @@
(pg-exec conn "DROP TABLE pgmt")))
+(defun test-pbe ()
+ (with-test-connection (conn)
+ (when (pg-supports-pbe conn)
+ (format *debug-io* "~&Testing pbe...")
+ (let ((res nil)
+ (count 0)
+ (created nil))
+ (unwind-protect
+ (progn
+ (pg-exec conn "CREATE TABLE count_test(key int, val int)")
+ (setq created t)
+ (format *debug-io* "~&table created")
+ (pg-prepare conn "ct_insert"
+ "INSERT INTO count_test VALUES ($1, $2)"
+ '("int4" "int4"))
+ (loop :for i :from 1 :to 100
+ :do
+ (pg-bind conn
+ "ct_portal" "ct_insert"
+ `((:int32 ,i)
+ (:int32 ,(* i i))))
+ (pg-execute conn "ct_portal")
+ (pg-close-portal conn "ct_portal"))
+ (format *debug-io* "~&data inserted")
+ (setq res (pg-exec conn "SELECT count(val) FROM count_test"))
+ (assert (eql 100 (first (pg-result res :tuple 0))))
+ (setq res (pg-exec conn "SELECT sum(key) FROM count_test"))
+ (assert (eql 5050 (first (pg-result res :tuple 0))))
+ ;; this iterator does the equivalent of the sum(key) SQL statement
+ ;; above, but on the client side.
+ (pg-for-each conn "SELECT key FROM count_test"
+ (lambda (tuple) (incf count (first tuple))))
+ (assert (= 5050 count)))
+ (when created
+ (pg-exec conn "DROP TABLE count_test")))))))
+
(defun test ()
(with-test-connection (conn)
@@ -304,7 +340,8 @@
(test-notifications)
(test-lo)
(test-lo-read)
- #+cmu (test-lo-import))
+ #+cmu (test-lo-import)
+ (test-pbe))
;; EOF
1
0
Update of /project/pg/cvsroot/pg
In directory common-lisp.net:/tmp/cvs-serv12704
Modified Files:
parsers.lisp
Log Message:
added type-to-oid table and lookup-type function to aid in bpe operations
Date: Mon Mar 8 09:37:37 2004
Author: pvaneynde
Index: pg/parsers.lisp
diff -u pg/parsers.lisp:1.1 pg/parsers.lisp:1.2
--- pg/parsers.lisp:1.1 Fri Mar 5 13:08:08 2004
+++ pg/parsers.lisp Mon Mar 8 09:37:36 2004
@@ -59,7 +59,10 @@
(defvar *parsers* '())
-
+(defvar *type-to-oid*
+ (make-hash-table :test #'eq)
+ "Is a hashtable for turning a typename into a OID.
+Needed to define the type of objects in pg-prepare")
(defvar *type-parsers*
`(("bool" . ,'bool-parser)
@@ -96,6 +99,8 @@
;; FIXME switch to a specialized float parser
(defun float-parser (str)
+ (declare (type simple-string str))
+
(let ((*read-eval* nil))
(read-from-string str)))
@@ -103,12 +108,14 @@
(defun text-parser (str) str)
(defun bool-parser (str)
+ (declare (type simple-string str))
(cond ((string= "t" str) t)
((string= "f" str) nil)
(t (error 'protocol-error
:reason "Badly formed boolean from backend: ~s" str))))
(defun parse-timestamp (str)
+ (declare (type simple-string str))
(let* ((year (parse-integer (subseq str 0 4)))
(month (parse-integer (subseq str 5 7)))
(day (parse-integer (subseq str 8 10)))
@@ -172,8 +179,8 @@
;; which we convert to a CL universal time
(defun date-parser (str)
(let ((year (parse-integer (subseq str 0 4)))
- (month (parse-integer (subseq str 5 7)))
- (day (parse-integer (subseq str 8 10))))
+ (month (parse-integer (subseq str 5 7)))
+ (day (parse-integer (subseq str 8 10))))
(encode-universal-time 0 0 0 day month year)))
(defun initialize-parsers (connection)
@@ -185,14 +192,33 @@
(let* ((typname (first tuple))
(oid (parse-integer (second tuple)))
(type (assoc typname *type-parsers* :test #'string=)))
- (if (consp type)
- (push (cons oid (cdr type)) *parsers*))))
+ (cond
+ ((consp type)
+ (setf (gethash (intern typname :keyword) *type-to-oid*)
+ oid)
+ (push (cons oid (cdr type)) *parsers*))
+ (t
+ #+debug
+ (warn "Unknown postgresSQL type found: '~A' oid: '~A'"
+ typname
+ oid)))))
tuples)))
(defun parse (str oid)
+ (declare (type simple-string str))
(let ((parser (assoc oid *parsers* :test #'eql)))
(if (consp parser)
(funcall (cdr parser) str)
str)))
+
+(defun lookup-type (type)
+ "Given the name of a type, returns the oid of the type or NIL if
+not found"
+ (let ((type (etypecase type
+ (symbol
+ type)
+ (string
+ (intern type :keyword)))))
+ (gethash type *type-to-oid*)))
;; EOF
1
0
Update of /project/pg/cvsroot/pg
In directory common-lisp.net:/tmp/cvs-serv7425
Modified Files:
pg.lisp v3-protocol.lisp
Log Message:
pg.lisp:
- make print-object more robust
- added documentation to the defgenerics
- added some declarations
v3-protocol.lisp:
- make errors restartable as we hope to
sync again with the db
- return errors from read-packet because
sometimes it is the only clue we get
that there is no more output
- replaced arefs with the faster elt
- unified query followup into do-followup-query
- added pbe (prepare bind execute) support
Date: Mon Mar 8 09:37:31 2004
Author: pvaneynde
Index: pg/pg.lisp
diff -u pg/pg.lisp:1.2 pg/pg.lisp:1.3
--- pg/pg.lisp:1.2 Fri Mar 5 13:08:08 2004
+++ pg/pg.lisp Mon Mar 8 09:37:31 2004
@@ -40,7 +40,7 @@
;; Exceptions are Corman Common Lisp whose socket streams do not
;; support binary I/O.
;;
-;; See the README for API documentation.
+;; See the README for API documentation.
;; This code has been tested or reported to work with
;;
@@ -81,7 +81,7 @@
(port :initarg :port
:reader connection-failure-port)
(transport-error :initarg :transport-error
- :reader connection-failure-transport-error))
+ :reader connection-failure-transport-error))
(:report
(lambda (exc stream)
(declare (type stream stream))
@@ -90,7 +90,7 @@
Is the postmaster running and accepting TCP connections?~%"
(connection-failure-host exc)
(connection-failure-port exc)
- (connection-failure-transport-error exc)))))
+ (connection-failure-transport-error exc)))))
(define-condition authentication-failure (postgresql-error)
((reason :initarg :reason
@@ -162,21 +162,66 @@
:initform nil)))
(defmethod print-object ((self pgcon) stream)
- (print-unreadable-object (self stream :type nil)
- (with-slots (pid host port) self
- (format stream "PostgreSQL connection to backend pid ~d at ~a:~d"
- pid host port))))
+ (print-unreadable-object (self stream :type nil)
+ (with-slots (pid host port) self
+ (format stream "PostgreSQL connection to backend pid ~d at ~a:~d"
+ (when (slot-boundp self 'pid)
+ pid)
+ (when (slot-boundp self 'host)
+ host)
+ (when (slot-boundp self 'port)
+ port)))))
(defstruct pgresult connection status attributes tuples)
-(defgeneric pg-exec (connection &rest args))
-
-(defgeneric fn (connection fn integer-result &rest args))
-
-(defgeneric pg-disconnect (connection))
-
-
+(defgeneric pg-exec (connection &rest args)
+ (:documentation
+ "Execute the SQL command given by the concatenation of ARGS
+on the database to which we are connected via CONNECTION. Return
+a result structure which can be decoded using `pg-result'."))
+
+(defgeneric fn (connection fn integer-result &rest args)
+ (:documentation
+ "Execute one of the large-object functions (lo_open, lo_close etc).
+ Argument FN is either an integer, in which case it is the OID of an
+ element in the pg_proc table, and otherwise it is a string which we
+look up in the alist *lo-functions* to find the corresponding OID."))
+
+(defgeneric pg-disconnect (connection)
+ (:documentation
+ "Disconnects from the DB"))
+
+(defgeneric pg-supports-pbe (connection)
+ (:documentation
+ "Returns true if the connection supports pg-prepare/-bind and -execute")
+ (:method (connection)
+ (declare (ignore connection))
+ nil))
+
+(defgeneric pg-prepare (connection statement-name sql-statement &optional type-of-parameters)
+ (:documentation
+ "Prepares a sql-statement give a given statement-name (can be empty)
+and optionally declares the types of the parameters as a list of strings.
+You can define parameters to be filled in later by using $1 and so on."))
+
+(defgeneric pg-bind (connection portal statement-name list-of-types-and-values)
+ (:documentation
+ "Gives the values for the parameters defined in the statement-name. The types
+can be one of :char :byte :int16 :int32 or :cstring"))
+
+(defgeneric pg-execute (connection portal &optional maxinum-number-of-rows)
+ (:documentation
+ "Executes the portal defined previously and return (optionally) up to maximum-number-of-row.
+For an unlimited number of rows use 0"))
+
+(defgeneric pg-close-statement (connection statement-name)
+ (:documentation
+ "Closes a prepared statement"))
+
+(defgeneric pg-close-portal (connection portal)
+ (:documentation
+ "Closes a prepared statement"))
;; first attempt to connect to connect using the v3 protocol; if this
;; results in an ErrorResponse we close the connection and retry using
@@ -196,6 +241,7 @@
:port port
:password password)
(protocol-error (c)
+ (declare (ignore c))
(warn "reconnecting using protocol version 2")
(pg-connect/v2 dbname user
:host host
@@ -214,6 +260,7 @@
:tuple n -> return the nth component of the data
:oid -> return the OID (a unique identifier generated by PostgreSQL
for each row resulting from an insertion"
+ (declare (type pgresult result))
(cond ((eq :connection what) (pgresult-connection result))
((eq :status what) (pgresult-status result))
((eq :attributes what) (pgresult-attributes result))
@@ -238,6 +285,9 @@
;; read an integer in network byte order
(defun read-net-int (connection bytes)
+ (declare (type (integer 0) bytes)
+ (type pgcon connection))
+
(do ((i bytes (- i 1))
(stream (pgcon-stream connection))
(accum 0))
@@ -271,8 +321,8 @@
(let ((v (make-array howmany :element-type '(unsigned-byte 8)))
(s (pgcon-stream connection)))
(do ((continue-at (read-sequence v s :start 0 :end howmany)
- (read-sequence v s :start continue-at :end howmany)))
- ((= continue-at howmany))
+ (read-sequence v s :start continue-at :end howmany)))
+ ((= continue-at howmany))
)
v))
Index: pg/v3-protocol.lisp
diff -u pg/v3-protocol.lisp:1.1 pg/v3-protocol.lisp:1.2
--- pg/v3-protocol.lisp:1.1 Fri Mar 5 13:08:08 2004
+++ pg/v3-protocol.lisp Mon Mar 8 09:37:31 2004
@@ -15,46 +15,46 @@
((severity :initarg :severity
:reader error-response-severity)
(code :initarg :code
- :reader error-response-code)
+ :reader error-response-code)
(message :initarg :message
- :reader error-response-message)
+ :reader error-response-message)
(detail :initarg :detail
- :reader error-response-detail)
+ :reader error-response-detail)
(hint :initarg :hint
- :reader error-response-hint)
+ :reader error-response-hint)
(position :initarg :position
- :reader error-response-position)
+ :reader error-response-position)
(where :initarg :where
- :reader error-response-where)
+ :reader error-response-where)
(file :initarg :file
- :reader error-response-file)
+ :reader error-response-file)
(line :initarg :line
- :reader error-response-line)
+ :reader error-response-line)
(routine :initarg :routine
- :reader error-response-routine))
+ :reader error-response-routine))
(:report
(lambda (exc stream)
(format stream "PostgreSQL ~A: (~A) ~A, ~A. Hint: ~A File: ~A, line ~A/~A ~A -> ~A"
- (ignore-errors
- (error-response-severity exc))
- (ignore-errors
- (error-response-code exc))
- (ignore-errors
- (error-response-message exc))
- (ignore-errors
- (error-response-detail exc))
- (ignore-errors
- (error-response-hint exc))
- (ignore-errors
- (error-response-file exc))
- (ignore-errors
- (error-response-line exc))
- (ignore-errors
- (error-response-position exc))
- (ignore-errors
- (error-response-routine exc))
- (ignore-errors
- (error-response-where exc))))))
+ (ignore-errors
+ (error-response-severity exc))
+ (ignore-errors
+ (error-response-code exc))
+ (ignore-errors
+ (error-response-message exc))
+ (ignore-errors
+ (error-response-detail exc))
+ (ignore-errors
+ (error-response-hint exc))
+ (ignore-errors
+ (error-response-file exc))
+ (ignore-errors
+ (error-response-line exc))
+ (ignore-errors
+ (error-response-position exc))
+ (ignore-errors
+ (error-response-routine exc))
+ (ignore-errors
+ (error-response-where exc))))))
;; packets send/received are always:
@@ -68,24 +68,24 @@
(defclass pg-packet ()
((type :initarg :type
- :type base-char
- :reader pg-packet-type)
+ :type base-char
+ :reader pg-packet-type)
(length :initarg :length
- :type (integer 32))
+ :type (integer 32))
(data :initarg :data
- :type (array (unsigned-byte 8) *))
+ :type (array (unsigned-byte 8) *))
(position :initform 0
- :type integer)))
+ :type integer)))
(defmethod print-object ((object pg-packet) stream)
(print-unreadable-object (object stream :type t :identity t)
(format stream "type: ~A length: ~A position: ~A"
- (and (slot-boundp object 'type)
- (slot-value object 'type))
- (and (slot-boundp object 'length)
- (slot-value object 'length))
- (and (slot-boundp object 'position)
- (slot-value object 'position)))))
+ (and (slot-boundp object 'type)
+ (slot-value object 'type))
+ (and (slot-boundp object 'length)
+ (slot-value object 'length))
+ (and (slot-boundp object 'position)
+ (slot-value object 'position)))))
;; first some help functions:
@@ -98,8 +98,8 @@
(when (= 1 (ldb (byte 1 7) result))
;; negative
(setf result (-
- (1+ (logxor result
- #xFF)))))
+ (1+ (logxor result
+ #xFF)))))
result))
(defun %read-net-int16 (stream)
@@ -107,12 +107,12 @@
The signed integer is presumed to be in network order.
Returns the integer."
(let ((result (+ (* 256 (read-byte stream))
- (read-byte stream))))
+ (read-byte stream))))
(when (= 1 (ldb (byte 1 15) result))
;; negative
(setf result (-
- (1+ (logxor result
- #xFFFF)))))
+ (1+ (logxor result
+ #xFFFF)))))
result))
(defun %read-net-int32 (stream)
@@ -120,14 +120,14 @@
The signed integer is presumed to be in network order.
Returns the integer."
(let ((result (+ (* 256 256 256 (read-byte stream))
- (* 256 256 (read-byte stream))
- (* 256 (read-byte stream))
- (read-byte stream))))
+ (* 256 256 (read-byte stream))
+ (* 256 (read-byte stream))
+ (read-byte stream))))
(when (= 1 (ldb (byte 1 31) result))
;; negative
(setf result (-
- (1+ (logxor result
- #xFFFFFFFF)))))
+ (1+ (logxor result
+ #xFFFFFFFF)))))
result))
#-cmu
@@ -149,8 +149,8 @@
Returns the array of "
(let ((v (make-array howmany :element-type '(unsigned-byte 8))))
(do ((continue-at (read-sequence v stream :start 0 :end howmany)
- (read-sequence v stream :start continue-at :end howmany)))
- ((= continue-at howmany))
+ (read-sequence v stream :start continue-at :end howmany)))
+ ((= continue-at howmany))
)
v))
@@ -161,38 +161,41 @@
(defun read-and-generate-error-response (packet)
(let ((args nil))
(loop :for field-type = (read-from-packet packet :byte)
- :until (= field-type 0)
- :do
- (let ((message (read-from-packet packet :cstring)))
- (push message args)
- (push
- (ecase (code-char field-type)
- ((#\S) :severity)
- ((#\C) :code)
- ((#\M) :message)
- ((#\D) :detail)
- ((#\H) :hint)
- ((#\P) :position)
- ((#\W) :where)
- ((#\F) :file)
- ((#\L) :line)
- ((#\R) :routine))
- args)))
- (apply #'error
- 'error-response
- args)))
+ :until (= field-type 0)
+ :do
+ (let ((message (read-from-packet packet :cstring)))
+ (push message args)
+ (push
+ (ecase (code-char field-type)
+ ((#\S) :severity)
+ ((#\C) :code)
+ ((#\M) :message)
+ ((#\D) :detail)
+ ((#\H) :hint)
+ ((#\P) :position)
+ ((#\W) :where)
+ ((#\F) :file)
+ ((#\L) :line)
+ ((#\R) :routine))
+ args)))
+ ;; we are trying to recover from errors too:
+ (apply #'cerror
+ "Try to continue, should do a rollback"
+ 'error-response
+ args)))
(defun read-and-handle-notification-response (connection packet)
- (declare (type pg-packet packet))
-
+ (declare (type pg-packet packet)
+ (type pgcon-v3 connection))
+
(let* ((pid (read-from-packet packet :int32))
- (name-condition (read-from-packet packet :cstring))
- (additional-information (read-from-packet packet :cstring)))
+ (name-condition (read-from-packet packet :cstring))
+ (additional-information (read-from-packet packet :cstring)))
(setf (pgcon-pid connection) pid)
(format t "~&Got notice: ~S, ~S"
- name-condition
- additional-information)
+ name-condition
+ additional-information)
(push name-condition (pgcon-notices connection))))
@@ -201,10 +204,11 @@
(defun read-packet (connection)
"Reads a packet from the connection.
-Returns the packet, handles errors and notices automagically"
+Returns the packet, handles errors and notices automagically,
+but will still return them"
(let* ((stream (pgcon-stream connection))
- (type (%read-net-int8 stream))
- (length (%read-net-int32 stream)))
+ (type (%read-net-int8 stream))
+ (length (%read-net-int32 stream)))
;; detect a bogus protocol response from the backend, which
;; probably means that we're in PG-CONNECT/V3 but talking to an
;; old backend that only understands the V2 protocol. Heuristics
@@ -221,16 +225,16 @@
:length length
:data data)))
(case (pg-packet-type packet)
- (( #\E) ; error
+ (( #\E) ; error
(read-and-generate-error-response packet)
- ;; in case we handled it:
- (read-packet connection))
- (( #\N) ; Notice
- (handle-notice/v3 connection packet))
+ packet)
+ (( #\N) ; Notice
+ (handle-notice/v3 connection packet)
+ packet)
(t
;; return the packet
packet)))))
-
+
;; Not to get at the data:
(defgeneric read-from-packet (packet type)
@@ -238,70 +242,70 @@
"Reads an integer from the given PACKET with type TYPE")
(:method ((packet pg-packet) (type (eql :char)))
(with-slots (data position)
- packet
+ packet
(prog1
- (aref data position)
- (incf position))))
+ (elt data position)
+ (incf position))))
(:method ((packet pg-packet) (type (eql :byte)))
(with-slots (data position)
- packet
+ packet
- (let ((result (aref data position)))
- (incf position)
- (when (= 1 (ldb (byte 1 7) result))
- ;; negative
- (setf result (-
- (1+ (logxor result
- #xFF)))))
- result)))
+ (let ((result (elt data position)))
+ (incf position)
+ (when (= 1 (ldb (byte 1 7) result))
+ ;; negative
+ (setf result (-
+ (1+ (logxor result
+ #xFF)))))
+ result)))
(:method ((packet pg-packet) (type (eql :int16)))
(with-slots (data position)
- packet
+ packet
- (let ((result (+ (* 256 (aref data position))
- (aref data (1+ position)))))
- (incf position 2)
- (when (= 1 (ldb (byte 1 15) result))
- ;; negative
- (setf result (-
- (1+ (logxor result
- #xFFFF)))))
- result)))
+ (let ((result (+ (* 256 (elt data position))
+ (elt data (1+ position)))))
+ (incf position 2)
+ (when (= 1 (ldb (byte 1 15) result))
+ ;; negative
+ (setf result (-
+ (1+ (logxor result
+ #xFFFF)))))
+ result)))
(:method ((packet pg-packet) (type (eql :int32)))
(with-slots (data position)
- packet
+ packet
- (let ((result (+ (* 256 256 256 (aref data position))
- (* 256 256 (aref data (1+ position)))
- (* 256 (aref data (+ 2 position)))
- (aref data (+ 3 position)))))
-
- (incf position 4)
- (when (= 1 (ldb (byte 1 31) result))
- ;; negative
- (setf result (-
- (1+ (logxor result
- #xFFFFFFFF)))))
- result)))
+ (let ((result (+ (* 256 256 256 (elt data position))
+ (* 256 256 (elt data (1+ position)))
+ (* 256 (elt data (+ 2 position)))
+ (elt data (+ 3 position)))))
+
+ (incf position 4)
+ (when (= 1 (ldb (byte 1 31) result))
+ ;; negative
+ (setf result (-
+ (1+ (logxor result
+ #xFFFFFFFF)))))
+ result)))
(:method ((packet pg-packet) (type (eql :cstring)))
(with-slots (data position)
- packet
+ packet
(let* ((end (position 0 data :start position))
- ;; end is where the 0 byte is
- (result (unless (= end position)
- (make-array (- end position)
- :element-type 'base-char))))
- (when result
- (loop :for i :from position :below end
- :for j :from 0
- :do
- (setf (aref result j)
- (code-char
- (aref data i))))
- (setf position (1+ end))
- result)))))
+ ;; end is where the 0 byte is
+ (result (unless (= end position)
+ (make-array (- end position)
+ :element-type 'base-char))))
+ (when result
+ (loop :for i :from position :below end
+ :for j :from 0
+ :do
+ (setf (elt result j)
+ (code-char
+ (elt data i))))
+ (setf position (1+ end))
+ result)))))
(defgeneric read-string-from-packet (packet length)
(:documentation
@@ -311,19 +315,19 @@
(:method ((packet pg-packet) (length integer))
(when (<= length 0)
(error "length cannot be negative. is: ~S"
- length))
+ length))
(let ((result (make-array length
- :element-type 'base-char)))
+ :element-type 'base-char)))
(with-slots (data position)
- packet
- (loop :for i :from 0 :below length
- :do
- (setf (aref result i)
- (code-char
- (the (unsigned-byte 8)
- (aref data (+ i position))))))
- (incf position length)
- result))))
+ packet
+ (loop :for i :from 0 :below length
+ :do
+ (setf (elt result i)
+ (code-char
+ (the (unsigned-byte 8)
+ (elt data (+ i position))))))
+ (incf position length)
+ result))))
;; now sending data:
@@ -331,10 +335,10 @@
(defun %send-net-int (stream int bytes)
(let ((v (make-array bytes :element-type '(unsigned-byte 8))))
(loop for offset from (* 8 (1- bytes)) downto 0 by 8
- for data = (ldb (byte 8 offset) int)
- for i from 0
- do
- (setf (aref v i) data))
+ for data = (ldb (byte 8 offset) int)
+ for i from 0
+ do
+ (setf (elt v i) data))
#+debug
(format t "~&writing: ~S~%" v)
(write-sequence v stream)))
@@ -345,7 +349,7 @@
(v (make-array len :element-type '(unsigned-byte 8))))
;; convert the string to a vector of bytes
(dotimes (i len)
- (setf (aref v i) (char-code (aref str i))))
+ (setf (elt v i) (char-code (elt str i))))
(write-sequence v stream)
(write-byte 0 stream)))
@@ -360,57 +364,56 @@
of items with as first element one of :byte, :char
:int16 :int32 or :cstring and as second element the
value of the parameter"
- #+nil
(declare (type base-char code))
(let* ((length (+ 4
- (loop for (type value) in description
- sum (ecase type
- ((:byte :char) 1)
- ((:int16) 2)
- ((:int32) 4)
- ((:cstring)
- (+ 1
- (length value)))))))
- (data (make-array (- length 4)
- :element-type '(unsigned-byte 8)))
- (stream (pgcon-stream connection)))
+ (loop for (type value) in description
+ sum (ecase type
+ ((:byte :char) 1)
+ ((:int16) 2)
+ ((:int32) 4)
+ ((:cstring)
+ (+ 1
+ (length value)))))))
+ (data (make-array (- length 4)
+ :element-type '(unsigned-byte 8)))
+ (stream (pgcon-stream connection)))
(loop for (type value) in description
- with position = 0
- do
- (ecase type
- ((:byte)
- (check-type value (unsigned-byte 8))
- (setf (aref data position) value)
- (incf position))
- ((:char)
- (check-type value base-char)
- (setf (aref data position) (char-code value))
- (incf position))
- ((:int16)
- (check-type value (unsigned-byte 16))
- (setf (aref data position) (ldb (byte 8 8) value))
- (setf (aref data (+ 1 position)) (ldb (byte 8 0) value))
- (incf position 2))
- ((:int32)
- (check-type value (unsigned-byte 32))
-
- (setf (aref data position) (ldb (byte 8 24) value))
- (setf (aref data (+ 1 position)) (ldb (byte 8 16) value))
- (setf (aref data (+ 2 position)) (ldb (byte 8 8) value))
- (setf (aref data (+ 3 position)) (ldb (byte 8 0) value))
- (incf position 4))
- ((:cstring)
- (check-type value string)
-
- (loop for char across value
- do
- (setf (aref data position)
- (char-code char))
- (incf position))
- (setf (aref data position) 0)
- (incf position))))
+ with position = 0
+ do
+ (ecase type
+ ((:byte)
+ (check-type value (unsigned-byte 8))
+ (setf (elt data position) value)
+ (incf position))
+ ((:char)
+ (check-type value base-char)
+ (setf (elt data position) (char-code value))
+ (incf position))
+ ((:int16)
+ (check-type value (unsigned-byte 16))
+ (setf (elt data position) (ldb (byte 8 8) value))
+ (setf (elt data (+ 1 position)) (ldb (byte 8 0) value))
+ (incf position 2))
+ ((:int32)
+ (check-type value (unsigned-byte 32))
+
+ (setf (elt data position) (ldb (byte 8 24) value))
+ (setf (elt data (+ 1 position)) (ldb (byte 8 16) value))
+ (setf (elt data (+ 2 position)) (ldb (byte 8 8) value))
+ (setf (elt data (+ 3 position)) (ldb (byte 8 0) value))
+ (incf position 4))
+ ((:cstring)
+ (check-type value string)
+
+ (loop for char across value
+ do
+ (setf (elt data position)
+ (char-code char))
+ (incf position))
+ (setf (elt data position) 0)
+ (incf position))))
(%send-net-int stream (char-code code) 1)
(%send-net-int stream length 4 )
@@ -426,16 +429,16 @@
(let* ((stream (socket-connect port host))
(connection (make-instance 'pgcon-v3 :stream stream :host host :port port))
(user-packet-length (+ 4 ; length
- 4 ; protocol version
- (length "user")
- 1
- (length user)
- 1
- (length "database")
- 1
- (length dbname)
- 1
- 1)))
+ 4 ; protocol version
+ (length "user")
+ 1
+ (length user)
+ 1
+ (length "database")
+ 1
+ (length dbname)
+ 1
+ 1)))
;; send the startup packet
;; this is one of the only non-standard packets!
(%send-net-int stream user-packet-length 4)
@@ -453,138 +456,205 @@
:for packet = (read-packet connection)
:do
(case (pg-packet-type packet)
- ;; Authentication Request:
- (( #\R)
- (let* ((code (read-from-packet packet :int32)))
- (case code
- ((0) ;; AuthOK
- )
- ((1) ; AuthKerberos4
- (error 'authentication-failure
- :reason "Kerberos4 authentication not supported"))
- ((2) ; AuthKerberos5
- (error 'authentication-failure
- :reason "Kerberos5 authentication not supported"))
- ((3) ; AuthUnencryptedPassword
- (send-packet connection
- #\p
- `((:cstring ,password)))
- (%flush connection))
- ((4) ; AuthEncryptedPassword
- (let* ((salt (read-string-from-packet packet 2))
- (crypted (crypt password salt)))
- #+debug
- (format *debug-io* "Got salt of ~s~%" salt)
- (send-packet connection
- #\p
- `((:cstring ,crypted)))
- (%flush connection)))
- ((5) ; AuthMD5Password
- (error 'authentication-failure
- :reason "MD5 authentication not supported"))
- ((6) ; AuthSCMPassword
- (error 'authentication-failure
- :reason "SCM authentication not supported"))
- (t (error 'authentication-failure
- :reason "unknown authentication type")))))
- (( #\K) ; Cancelation
- (let* ((pid (read-from-packet packet :int32))
- (secret (read-from-packet packet :int32)))
- #+debug
- (format t "~&Got cancelation data")
-
- (setf (pgcon-pid connection) pid)
- (setf (pgcon-secret connection) secret)))
- (( #\S) ; Status
- (let* ((parameter (read-from-packet packet :cstring))
- (value (read-from-packet packet :cstring)))
+ ((#\R)
+ ;; Authentication Request:
+ (let* ((code (read-from-packet packet :int32)))
+ (case code
+ ((0) ;; AuthOK
+ )
+ ((1) ; AuthKerberos4
+ (error 'authentication-failure
+ :reason "Kerberos4 authentication not supported"))
+ ((2) ; AuthKerberos5
+ (error 'authentication-failure
+ :reason "Kerberos5 authentication not supported"))
+ ((3) ; AuthUnencryptedPassword
+ (send-packet connection
+ #\p
+ `((:cstring ,password)))
+ (%flush connection))
+ ((4) ; AuthEncryptedPassword
+ (let* ((salt (read-string-from-packet packet 2))
+ (crypted (crypt password salt)))
+ #+debug
+ (format *debug-io* "Got salt of ~s~%" salt)
+ (send-packet connection
+ #\p
+ `((:cstring ,crypted)))
+ (%flush connection)))
+ ((5) ; AuthMD5Password
+ (error 'authentication-failure
+ :reason "MD5 authentication not supported"))
+ ((6) ; AuthSCMPassword
+ (error 'authentication-failure
+ :reason "SCM authentication not supported"))
+ (t (error 'authentication-failure
+ :reason "unknown authentication type")))))
+ (( #\K)
+ ;; Cancelation
+ (let* ((pid (read-from-packet packet :int32))
+ (secret (read-from-packet packet :int32)))
+ #+debug
+ (format t "~&Got cancelation data")
+
+ (setf (pgcon-pid connection) pid)
+ (setf (pgcon-secret connection) secret)))
+ (( #\S)
+ ;; Status
+ (let* ((parameter (read-from-packet packet :cstring))
+ (value (read-from-packet packet :cstring)))
(push (cons parameter value) (pgcon-parameters connection))))
- ((#\Z) ; Ready for Query
- (let* ((status (read-from-packet packet :byte)))
- (unless (= status
- (char-code #\I))
- (warn "~&Got status ~S but wanted I~%"
- (code-char status)))
-
- (and (not *pg-disable-type-coercion*)
- (null *parsers*)
- (initialize-parsers connection))
- (when *pg-date-style*
- (setf (pg-date-style connection) *pg-date-style*))
- (when *pg-client-encoding*
- (setf (pg-client-encoding connection) *pg-client-encoding*))
- (return connection)))
+ ((#\Z)
+ ;; Ready for Query
+ (let* ((status (read-from-packet packet :byte)))
+ (unless (= status
+ (char-code #\I))
+ (warn "~&Got status ~S but wanted I~%"
+ (code-char status)))
+
+ (and (not *pg-disable-type-coercion*)
+ (null *parsers*)
+ (initialize-parsers connection))
+ (when *pg-date-style*
+ (setf (pg-date-style connection) *pg-date-style*))
+ (when *pg-client-encoding*
+ (setf (pg-client-encoding connection) *pg-client-encoding*))
+ (return connection)))
+ ((#\E)
+ ;; an error, we should abort.
+ (return nil))
+ ((#\N)
+ ;; We ignore Notices
+ t)
(t (error 'protocol-error
:reason "expected an authentication response"))))))
+(defun do-followup-query (connection)
+ "Does the followup of a query"
+
+ (let ((tuples '())
+ (attributes '())
+ (result (make-pgresult :connection connection)))
+
+ (%flush connection)
+
+ (loop
+ :for packet = (read-packet connection)
+ :with got-data-p = nil
+ :do
+ (when packet
+ (case (pg-packet-type packet)
+ ((#\S)
+ ;; Parameter status? not documented as return!
+ ;; XXX investigate
+ (let* ((parameter (read-from-packet packet :cstring))
+ (value (read-from-packet packet :cstring)))
+ ;;#+debug
+ (warn "~&Got unexpected parameter ~S = ~S"
+ parameter
+ value)))
+ ((#\A)
+ ;; NotificationResponse
+ ;; Not documented?
+ ;; XXX investigate
+ (read-and-handle-notification-response connection packet))
+ ((#\C)
+ ;; CommandComplete
+ (let ((status (read-from-packet packet :cstring)))
+ (setf (pgresult-status result) status)
+ (setf (pgresult-tuples result) (nreverse tuples))
+ (setf (pgresult-attributes result) attributes))
+ (setf got-data-p t))
+ ((#\G)
+ ;; CopyInResponse
+ (cerror "Just ignore it" "What to do with #\G?")
+ ;; The backend is ready to copy data from the frontend to a table;
+ ;; see Section 44.2.5 in http://www.postgresql.org/docs/7.4/interactive/protocol-flow.html
+ ;; for now we make it fail gracefully:
+ (send-packet connection
+ #\f
+ ;;CopyFail
+ '((:cstring "not implemented by pg.lisp yet")))
+ )
+ ((#\H)
+ ;; CopyOutResponse
+ (cerror "Just ignore it" "What to do with #\H?")
+ ;; The backend is ready to copy data from a table to the frontend;
+ ;; see Section 44.2.5.
+ ;; for now we make it fail gracefully (we cannot stop the transfer...
+ )
+ (( #\d
+ ;; CopyData
+ #\c
+ ;;CopyDone
+ )
+ t)
+ ((#\T)
+ ;; RowDescription (metadata for subsequent tuples), #\T
+ (and attributes (error "Cannot handle multiple result group"))
+ (setq attributes (read-attributes/v3 packet)))
+ ((#\D)
+ ;; AsciiRow (text data transfer), #\D
+ (setf got-data-p t)
+ (setf (pgcon-binary-p connection) nil)
+ (unless attributes
+ (error 'protocol-error :reason "Tuple received before metadata"))
+ (push (read-tuple/v3 packet attributes) tuples))
+ ((#\I)
+ ;; EmptyQueryResponse, #\I
+ ;; so no result.
+ (setf got-data-p t)
+ (setf (pgresult-status result) "SELECT")
+ (setf (pgresult-tuples result) nil)
+ (setf (pgresult-attributes result) nil))
+ ((#\Z)
+ ;; ReadyForQuery
+ ;;
+ ;; it might be a result from a previous
+ ;; query
+ (when got-data-p
+ (return result)))
+ ((#\s)
+ ;; PortalSuspend
+ ;; we're done in any case:
+ (return result))
+ ((#\2
+ ;; BindComplete
+ #\1
+ ;; ParseComplete
+ #\3
+ ;; CloseComplete
+ #\n
+ ;; NoData
+ )
+ ;; we ignore these messages
+ t)
+ ((#\E
+ ;; an error, we bravely try to recover...
+ #\N)
+ ;; and we ignore Notices
+ t)
+ (t
+ (warn "Got unexpected packet: ~S, resetting connection"
+ packet)
+ ;; sync
+ (send-packet connection
+ #\S
+ nil)
+ (%flush connection)))))))
+
(defmethod pg-exec ((connection pgcon-v3) &rest args)
"Execute the SQL command given by the concatenation of ARGS
on the database to which we are connected via CONNECTION. Return
a result structure which can be decoded using `pg-result'."
- (let ((sql (apply #'concatenate 'simple-string args))
- (tuples '())
- (attributes '())
- (result (make-pgresult :connection connection)))
+ (let ((sql (apply #'concatenate 'simple-string args)))
(when (> (length sql) +MAX_MESSAGE_LEN+)
(error "SQL statement too long: ~A" sql))
(send-packet connection #\Q `((:cstring ,sql)))
(%flush connection)
- (loop
- for packet = (read-packet connection)
- do
- (ecase (pg-packet-type packet)
- ((#\S)
- (let* ((parameter (read-from-packet packet :cstring))
- (value (read-from-packet packet :cstring)))
- (push (cons parameter value) (pgcon-parameters connection))))
- ((#\A)
- ;; NotificationResponse
- ;; Not documented?
- ;; XXX investigate
- (read-and-handle-notification-response connection packet))
- ((#\C)
- ;; CommandComplete
- (let ((status (read-from-packet packet :cstring)))
- (setf (pgresult-status result) status)
- (setf (pgresult-tuples result) (nreverse tuples))
- (setf (pgresult-attributes result) attributes)))
- ((#\G)
- ;; CopyInResponse
- (error "What to do with #\G?")
- ;; The backend is ready to copy data from the frontend to a table;
- ;; see Section 44.2.5.
- )
- ((#\H)
- ;; CopyOutResponse
- (error "What to do with #\H")
- ;; The backend is ready to copy data from a table to the frontend;
- ;; see Section 44.2.5.
- )
- ((#\T)
- ;; RowDescription (metadata for subsequent tuples), #\T
- (and attributes (error "Cannot handle multiple result group"))
- (setq attributes (read-attributes/v3 packet)))
- ((#\D)
- ;; AsciiRow (text data transfer), #\D
- (setf (pgcon-binary-p connection) nil)
- (unless attributes
- (error 'protocol-error :reason "Tuple received before metadata"))
- (push (read-tuple/v3 packet attributes) tuples))
- ((#\I)
- ;; EmptyQueryResponse, #\I
- ;; so no result.
- (setf (pgresult-status result) "SELECT")
- (setf (pgresult-tuples result) nil)
- (setf (pgresult-attributes result) nil))
- ((#\N) ; NotificationResponse
- ;; the notification has already been handled
- t)
- ((#\Z)
- ;; ReadyForQuery
- ;; we're done:
- (return result))))))
+ (do-followup-query connection)))
(defmethod pg-disconnect ((connection pgcon-v3))
@@ -601,41 +671,41 @@
(do ((i attribute-count (- i 1)))
((zerop i) (nreverse attributes))
(let* ((type-name (read-from-packet packet :cstring))
- (table-id (read-from-packet packet :int32))
- (column-id (read-from-packet packet :int16))
- (type-id (read-from-packet packet :int32))
- (type-len (read-from-packet packet :int16))
- (type-mod (read-from-packet packet :int32))
- (format-code (read-from-packet packet :int16)))
+ (table-id (read-from-packet packet :int32))
+ (column-id (read-from-packet packet :int16))
+ (type-id (read-from-packet packet :int32))
+ (type-len (read-from-packet packet :int16))
+ (type-mod (read-from-packet packet :int32))
+ (format-code (read-from-packet packet :int16)))
(declare (ignore type-mod format-code
- table-id column-id))
+ table-id column-id))
(push (list type-name type-id type-len) attributes)))))
(defun read-tuple/v3 (packet attributes)
(let* ((num-attributes (length attributes))
- (number (read-from-packet packet :int16))
+ (number (read-from-packet packet :int16))
(tuples '()))
(unless (= num-attributes
- number)
+ number)
(error "Should ~S not be equal to ~S"
- num-attributes
- number))
+ num-attributes
+ number))
(do ((i 0 (+ i 1))
(type-ids (mapcar #'second attributes) (cdr type-ids)))
((= i num-attributes) (nreverse tuples))
(let* ((length (read-from-packet packet :int32))
- (raw (unless (= length -1)
- (read-string-from-packet packet length))))
- (if raw
- (push (parse raw (car type-ids)) tuples)
- (push nil tuples))))))
+ (raw (unless (= length -1)
+ (read-string-from-packet packet length))))
+ (if raw
+ (push (parse raw (car type-ids)) tuples)
+ (push nil tuples))))))
;; Execute one of the large-object functions (lo_open, lo_close etc).
;; Argument FN is either an integer, in which case it is the OID of an
;; element in the pg_proc table, and otherwise it is a string which we
;; look up in the alist *lo-functions* to find the corresponding OID.
(defmethod fn ((connection pgcon-v3) fn integer-result &rest args)
- (or *lo-initialized* (lo-init connection))
+ (or *lo-initialized* (lo-init connection))
(let ((fnid (cond ((integerp fn) fn)
((not (stringp fn))
(error "Expecting a string or an integer: ~s" fn))
@@ -643,59 +713,73 @@
(cdr (assoc fn *lo-functions* :test #'string=)))
(t (error "Unknown builtin function ~s" fn)))))
(send-packet connection
- #\F
- `((:int32 ,fnid)
- (:int16 ,(length args))
- ,@(let ((result nil))
- (loop for arg in args
- do
- (cond
- ((integerp arg)
- (push `(:int16 1)
- result))
- ((stringp arg)
- (push `(:int16 0)
- result))
- (t (error 'protocol-error
- :reason (format nil "Unknown fastpath type ~s" arg)))))
- (nreverse result))
- (:int16 ,(length args))
- ,@(let ((result nil))
- (loop for arg in args
- do
- (cond
- ((integerp arg)
- (push '(:int32 4) result)
- (push `(:int32 ,arg) result))
- ((stringp arg)
- (push `(:int32 ,(1+ (length arg))) result)
- (push `(:cstring ,arg) result))
- (t (error 'protocol-error
- :reason (format nil "Unknown fastpath type ~s" arg)))))
- (nreverse result))
- (:int16 ,(if integer-result 1 0))))
+ #\F
+ `((:int32 ,fnid)
+ (:int16 ,(length args))
+ ,@(let ((result nil))
+ (loop for arg in args
+ do
+ (cond
+ ((integerp arg)
+ (push `(:int16 1)
+ result))
+ ((stringp arg)
+ (push `(:int16 0)
+ result))
+ (t (error 'protocol-error
+ :reason (format nil "Unknown fastpath type ~s" arg)))))
+ (nreverse result))
+ (:int16 ,(length args))
+ ,@(let ((result nil))
+ (loop for arg in args
+ do
+ (cond
+ ((integerp arg)
+ (push '(:int32 4) result)
+ (push `(:int32 ,arg) result))
+ ((stringp arg)
+ (push `(:int32 ,(1+ (length arg))) result)
+ (push `(:cstring ,arg) result))
+ (t (error 'protocol-error
+ :reason (format nil "Unknown fastpath type ~s" arg)))))
+ (nreverse result))
+ (:int16 ,(if integer-result 1 0))))
(%flush connection)
(loop :with result = nil
- :for packet = (read-packet connection)
- :do
- (ecase (pg-packet-type packet)
- ((#\V)
- (let* ((length (read-from-packet packet :int32))
- (data (unless (= length -1)
- (if integer-result
- (ecase length
- ((1)
- (read-from-packet packet :byte))
- ((2)
- (read-from-packet packet :int16))
- ((4)
- (read-from-packet packet :int32)))
- (read-string-from-packet packet length)))))
- (if data
- (setf result data)
- (return-from fn nil))))
- ((#\Z)
- (return-from fn result))))))
+ :for packet = (read-packet connection)
+ :do
+ (case (pg-packet-type packet)
+ ((#\V)
+ (let* ((length (read-from-packet packet :int32))
+ (data (unless (= length -1)
+ (if integer-result
+ (ecase length
+ ((1)
+ (read-from-packet packet :byte))
+ ((2)
+ (read-from-packet packet :int16))
+ ((4)
+ (read-from-packet packet :int32)))
+ (read-string-from-packet packet length)))))
+ (if data
+ (setf result data)
+ (return-from fn nil))))
+ ((#\Z)
+ (return-from fn result))
+ ((#\E)
+ ;; an error, we should abort.
+ (return nil))
+ ((#\N)
+ ;; We ignore Notices
+ t)
+ (t
+ (warn "Got unexpected packet: ~S, resetting connection"
+ packet)
+ ;; sync
+ (send-packet connection
+ #\S
+ nil)
+ (%flush connection))))))
@@ -722,8 +806,8 @@
(defun handle-notice/v3 (connection packet)
(loop :with notification = (make-instance 'backend-notification)
:for field-type = (read-from-packet packet :byte)
- :until (= field-type 0)
- :do (let ((message (read-from-packet packet :cstring))
+ :until (= field-type 0)
+ :do (let ((message (read-from-packet packet :cstring))
(slot (ecase (code-char field-type)
((#\S) 'severity)
((#\C) 'code)
@@ -739,5 +823,142 @@
:finally (push notification (pgcon-notices connection)))
packet)
+
+
+;; prepare/bind/execute functions
+
+(defmethod pg-supports-pbe ((connection pgcon-v3))
+ (declare (ignore connection))
+ t)
+
+(defmethod pg-prepare ((connection pgcon-v3) (statement-name string) (sql-statement string) &optional type-of-parameters)
+ (let ((types (when type-of-parameters
+ (loop :for type :in type-of-parameters
+ :for oid = (or (lookup-type type)
+ (error "type not found"))
+ :collect `(:int32 ,oid)))))
+
+ (cond
+ (types
+ (send-packet connection
+ #\P
+ `((:cstring ,statement-name)
+ (:cstring ,sql-statement)
+ (:int16 ,(length types))
+ ,@(when types
+ types))))
+ (t
+ (send-packet connection
+ #\P
+ `((:cstring ,statement-name)
+ (:cstring ,sql-statement)
+ (:int16 0)))))
+ t))
+
+(defmethod pg-bind ((connection pgcon-v3) (portal string) (statement-name string) list-of-types-and-values)
+ (let ((formats (when list-of-types-and-values
+ (loop :for (type value) :in list-of-types-and-values
+ :collect
+ (ecase type
+ ((:string) `(:int16 0))
+ ((:byte :int16 :int32 :char) `(:int16 1))))))
+ (data nil))
+
+ (when list-of-types-and-values
+ (loop :for (type value) :in list-of-types-and-values
+ :do
+ (ecase type
+ ((:int32)
+ (push '(:int32 4) data)
+ (push `(:int32 ,value) data))
+ ((:int16)
+ (push '(:int32 2) data)
+ (push `(:int16 ,value) data))
+ ((:byte)
+ (push '(:int32 1) data)
+ (push `(:int8 ,value) data))
+ ((:char)
+ (push '(:int32 1) data)
+ (push `(:int8 ,(char-code value)) data))
+ ((:string)
+ (push `(:int32 ,(1+ (length value))) data)
+ (push `(:cstring ,value) data))))
+
+ (setf data (nreverse data)))
+
+ (cond
+ (list-of-types-and-values
+ (send-packet connection
+ #\B
+ `((:cstring ,portal)
+ (:cstring ,statement-name)
+ (:int16 ,(length formats))
+ ,@formats
+ (:int16 ,(length formats))
+ ,@data
+ (:int16 0))))
+ (t
+ (send-packet connection
+ #\B
+ `((:cstring ,portal)
+ (:cstring ,statement-name)
+ (:int16 0)
+ (:int16 0)
+ (:int16 0)))))
+ t))
+
+(defmethod pg-execute ((connection pgcon-v3) (portal string) &optional (maxinum-number-of-rows 0))
+
+ ;; have it describe the result:
+ (send-packet connection
+ #\D
+ `((:char #\P)
+ (:cstring ,portal)))
+ ;; execute the query:
+ (send-packet connection
+ #\E
+ `((:cstring ,portal)
+ (:int32 ,maxinum-number-of-rows)))
+ ;; send all data:
+ (send-packet connection
+ #\S
+ nil)
+ (%flush connection)
+
+ (do-followup-query connection))
+
+(defun pg-close (connection name type)
+ (declare (type pgcon connection)
+ (type string name)
+ (type base-char type))
+
+ (send-packet connection
+ #\C
+ `((:char ,type)
+ (:cstring ,name)))
+ (%flush connection)
+ (loop :for packet = (read-packet connection)
+ :do
+ (case (pg-packet-type packet)
+ ((#\B #\Z)
+ ;; Close Complete
+ ;; or
+ ;; ReadyForQuery
+ (return))
+ (t
+ (warn "Got unexpected packet: ~S, resetting connection"
+ packet)
+ ;; sync
+ (send-packet connection
+ #\S
+ nil)
+ (%flush connection))))
+ t)
+
+(defmethod pg-close-statement ((connection pgcon-v3) (statement-name string))
+ (pg-close connection statement-name #\s))
+
+(defmethod pg-close-portal ((connection pgcon-v3) (portal string))
+ (pg-close connection portal #\P))
;; EOF
1
0
Update of /project/pg/cvsroot/pg
In directory common-lisp.net:/tmp/cvs-serv8996
Modified Files:
pg.asd
Log Message:
the crypt library actually is loaded in sysdep.lisp
Date: Sat Mar 6 17:59:39 2004
Author: pvaneynde
Index: pg/pg.asd
diff -u pg/pg.asd:1.2 pg/pg.asd:1.3
--- pg/pg.asd:1.2 Fri Mar 5 13:08:08 2004
+++ pg/pg.asd Sat Mar 6 17:59:39 2004
@@ -17,11 +17,11 @@
:author "Eric Marsden"
:version "0.21"
:components ((:file "defpackage")
- (:file "sysdep" :depends-on ("defpackage"))
+ (:pg-component "sysdep" :depends-on ("defpackage"))
(:file "meta-queries" :depends-on ("defpackage"))
(:file "parsers" :depends-on ("defpackage"))
(:file "utility" :depends-on ("defpackage"))
- (:pg-component "pg" :depends-on ("sysdep" "parsers"))
+ (:file "pg" :depends-on ("sysdep" "parsers"))
(:file "large-object" :depends-on ("pg"))
(:file "v2-protocol" :depends-on ("pg" "large-object" "utility"))
(:file "v3-protocol" :depends-on ("pg" "large-object" "utility"))))
1
0

[pg-cvs] CVS update: pg/CREDITS pg/TODO pg/large-object.lisp pg/meta-queries.lisp pg/parsers.lisp pg/utility.lisp pg/v2-protocol.lisp pg/v3-protocol.lisp pg/pg-tests.lisp pg/pg.asd pg/pg.lisp pg/sysdep.lisp
by Eric Marsden 05 Mar '04
by Eric Marsden 05 Mar '04
05 Mar '04
Update of /project/pg/cvsroot/pg
In directory common-lisp.net:/tmp/cvs-serv31521
Modified Files:
pg-tests.lisp pg.asd pg.lisp sysdep.lisp
Added Files:
CREDITS TODO large-object.lisp meta-queries.lisp parsers.lisp
utility.lisp v2-protocol.lisp v3-protocol.lisp
Log Message:
Integrate Peter Van Eynde's v3 protocol support:
- create PGCON-V2 and PGCON-V3 classes
- PG-CONNECT attempts to connect using v3 protocol, and falls back
to v2 protocol for older backends; return a PGCON-V2 or PGCON-V3
object
- PG-EXEC and FN and PG-DISCONNECT are generic functions that
dispatch on the connection type
- protocol code split into v2-protocol.lisp and v3-protocol.lisp
TBD: cleaning up the notification & error reporting support, and
factorizing more code between the two protocol versions.
Also split code out into multiple files:
- large-object support
- metainformation about databases
- parsing and type coercion support
- utility functions and macros
Date: Fri Mar 5 13:08:08 2004
Author: emarsden
Index: pg/pg-tests.lisp
diff -u pg/pg-tests.lisp:1.1.1.1 pg/pg-tests.lisp:1.2
--- pg/pg-tests.lisp:1.1.1.1 Wed Mar 3 08:11:50 2004
+++ pg/pg-tests.lisp Fri Mar 5 13:08:08 2004
@@ -1,4 +1,8 @@
-;; == testing ==============================================================
+;;; pg-tests.lisp -- incomplete test suite
+;;;
+;;; Author: Eric Marsden <emarsden(a)laas.fr>
+;;; Time-stamp: <2004-03-05 emarsden>
+;;
;;
;;
;; These tests assume that a table named "test" is defined in the
@@ -13,7 +17,7 @@
;; !!! CHANGE THE VALUES HERE !!!
(defun call-with-test-connection (function)
- (with-pg-connection (conn "test" "emarsden" :host "melbourne" :port 5433)
+ (with-pg-connection (conn "template1" "emarsden" :host nil :port 5432)
(funcall function conn)))
(defmacro with-test-connection ((conn) &body body)
@@ -301,5 +305,6 @@
(test-lo)
(test-lo-read)
#+cmu (test-lo-import))
+
;; EOF
Index: pg/pg.asd
diff -u pg/pg.asd:1.1.1.1 pg/pg.asd:1.2
--- pg/pg.asd:1.1.1.1 Wed Mar 3 08:11:50 2004
+++ pg/pg.asd Fri Mar 5 13:08:08 2004
@@ -15,8 +15,14 @@
(defsystem :pg
:name "Socket-level PostgreSQL interface"
:author "Eric Marsden"
- :version "0.19"
+ :version "0.21"
:components ((:file "defpackage")
(:file "sysdep" :depends-on ("defpackage"))
- (:pg-component "pg" :depends-on ("sysdep"))))
+ (:file "meta-queries" :depends-on ("defpackage"))
+ (:file "parsers" :depends-on ("defpackage"))
+ (:file "utility" :depends-on ("defpackage"))
+ (:pg-component "pg" :depends-on ("sysdep" "parsers"))
+ (:file "large-object" :depends-on ("pg"))
+ (:file "v2-protocol" :depends-on ("pg" "large-object" "utility"))
+ (:file "v3-protocol" :depends-on ("pg" "large-object" "utility"))))
Index: pg/pg.lisp
diff -u pg/pg.lisp:1.1.1.1 pg/pg.lisp:1.2
--- pg/pg.lisp:1.1.1.1 Wed Mar 3 08:11:50 2004
+++ pg/pg.lisp Fri Mar 5 13:08:08 2004
@@ -1,8 +1,8 @@
;;; pg.lisp -- socket level interface to the PostgreSQL RDBMS for Common Lisp
;;
;; Author: Eric Marsden <emarsden(a)laas.fr>
-;; Time-stamp: <2004-02-18 emarsden>
-;; Version: 0.20
+;; Time-stamp: <2004-03-05 emarsden>
+;; Version: 0.21
;;
;; Copyright (C) 1999,2000,2001,2002,2003 Eric Marsden
;;
@@ -69,28 +69,10 @@
;; a change in PostgreSQL timestamp format.
-;;; TODO ============================================================
-;;
-;; * add a mechanism for parsing user-defined types. The user should
-;; be able to define a parse function and a type-name; we query
-;; pg_type to get the type's OID and add the information to
-;; pg:*parsers*.
-;;
-;; * update to protocol version 3, as per
-;; http://developer.postgresql.org/docs/postgres/protocol-changes.html
-;; esp with respect to error responses
-
(declaim (optimize (speed 3) (safety 1)))
(in-package :postgresql)
-(eval-when (:compile-toplevel :load-toplevel :execute)
- #+allegro (require :socket)
- #+lispworks (require "comm")
- #+cormanlisp (require :sockets)
- #+sbcl (progn (require :asdf) (require :sb-bsd-sockets))
- #+(and mcl (not openmcl)) (require "OPENTRANSPORT"))
-
(define-condition postgresql-error (simple-error) ())
(define-condition connection-failure (postgresql-error)
@@ -102,6 +84,7 @@
:reader connection-failure-transport-error))
(:report
(lambda (exc stream)
+ (declare (type stream stream))
(format stream "Couldn't connect to PostgreSQL database at ~a:~a.
Connection attempt reported ~A.
Is the postmaster running and accepting TCP connections?~%"
@@ -114,6 +97,7 @@
:reader authentication-failure-reason))
(:report
(lambda (exc stream)
+ (declare (type stream stream))
(format stream "PostgreSQL authentication failure: ~a~%"
(authentication-failure-reason exc)))))
@@ -122,6 +106,7 @@
:reader protocol-error-reason))
(:report
(lambda (exc stream)
+ (declare (type stream stream))
(format stream "PostgreSQL protocol error: ~a~%"
(protocol-error-reason exc)))))
@@ -130,15 +115,12 @@
:reader backend-error-reason))
(:report
(lambda (exc stream)
+ (declare (type stream stream))
(format stream "PostgreSQL backend error: ~a~%"
(backend-error-reason exc)))))
(defconstant +NAMEDATALEN+ 32) ; postgres_ext.h
-(defconstant +PG_PROTOCOL_LATEST_MAJOR+ 2) ; libpq/pgcomm.h
-(defconstant +PG_PROTOCOL_63_MAJOR+ 1)
-(defconstant +PG_PROTOCOL_62_MAJOR+ 0)
-(defconstant +PG_PROTOCOL_LATEST_MINOR+ 0)
(defconstant +SM_DATABASE+ 64)
(defconstant +SM_USER+ 32)
(defconstant +SM_OPTIONS+ 64)
@@ -155,289 +137,70 @@
(defconstant +MAX_MESSAGE_LEN+ 8192) ; libpq-fe.h
-(defconstant +INV_ARCHIVE+ #x10000) ; fe-lobj.c
-(defconstant +INV_WRITE+ #x20000)
-(defconstant +INV_READ+ #x40000)
-(defconstant +LO_BUFSIZ+ 1024)
-
-;; alist of (oid . parser) pairs. This is built dynamically at
-;; initialization of the connection with the database (once generated,
-;; the information is shared between connections).
-(defvar *parsers* '())
-
(defvar *pg-client-encoding* "LATIN1"
"The encoding to use for text data, for example \"LATIN1\", \"UNICODE\", \"EUC_JP\".
See <http://www.postgresql.org/docs/7.3/static/multibyte.html>.")
(defvar *pg-date-style* "ISO")
-(defvar *pg-disable-type-coercion* nil
- "Non-nil disables the type coercion mechanism.
-The default is nil, which means that data recovered from the
-database is coerced to the corresponding Common Lisp type before
-being returned; for example numeric data is transformed to CL
-numbers, and booleans to booleans.
-
-The coercion mechanism requires an initialization query to the
-database, in order to build a table mapping type names to OIDs. This
-option is provided mainly in case you wish to avoid the overhead of
-this initial query. The overhead is only incurred once per session
-(not per connection to the backend).")
+(defclass pgcon ()
+ ((stream :accessor pgcon-stream
+ :initarg :stream
+ :initform nil)
+ (host :accessor pgcon-host
+ :initarg :host
+ :initform nil)
+ (port :accessor pgcon-port
+ :initarg :port
+ :initform 0)
+ (pid :accessor pgcon-pid)
+ (secret :accessor pgcon-secret)
+ (notices :accessor pgcon-notices
+ :initform (list))
+ (binary-p :accessor pgcon-binary-p
+ :initform nil)))
+(defmethod print-object ((self pgcon) stream)
+ (print-unreadable-object (self stream :type nil)
+ (with-slots (pid host port) self
+ (format stream "PostgreSQL connection to backend pid ~d at ~a:~d"
+ pid host port))))
-(defstruct (pgcon (:print-function print-pgcon))
- stream pid secret notices (binary-p nil) host port)
(defstruct pgresult connection status attributes tuples)
-(defun print-pgcon (self &optional (stream t) depth)
- (declare (ignore depth))
- (print-unreadable-object (self stream :type nil)
- (format stream "PostgreSQL connection to backend pid ~d at ~a:~d"
- (pgcon-pid self)
- (pgcon-host self)
- (pgcon-port self))))
-
-(defun pg-date-style (conn)
- (let ((res (pg-exec conn "SHOW datestyle")))
- (first (pg-result res :tuple 0))))
-
-(defun set-pg-date-style (conn new-date-style)
- (declare (type simple-string new-date-style))
- (pg-exec conn "SET datestyle TO " new-date-style))
-
-(defsetf pg-date-style set-pg-date-style)
-
-;; see http://www.postgresql.org/docs/7.3/static/multibyte.html
-(defun pg-client-encoding (conn)
- "Return a string identifying the client encoding."
- (let ((res (pg-exec conn "SHOW client_encoding")))
- (first (pg-result res :tuple 0))))
-
-(defun set-pg-client-encoding (conn new-encoding)
- "Set the client_encoding."
- (declare (type simple-string new-encoding))
- (pg-exec conn "SET client_encoding TO " new-encoding))
-
-(defsetf pg-client-encoding set-pg-client-encoding)
-
-
-(defmacro with-pg-connection ((con &rest open-args) &body body)
- "Bindspec is of the form (connection open-args), where OPEN-ARGS are
-as for PG-CONNECT. The database connection is bound to the variable
-CONNECTION. If the connection is unsuccessful, the forms are not
-evaluated. Otherwise, the BODY forms are executed, and upon
-termination, normal or otherwise, the database connection is closed."
- `(let ((,con (pg-connect ,@open-args)))
- (unwind-protect
- (progn ,@body)
- (when ,con (pg-disconnect ,con)))))
-
-#-old-version
-(defmacro with-pg-transaction (con &body body)
- "Execute BODY forms in a BEGIN..END block.
-If a PostgreSQL error occurs during execution of the forms, execute
-a ROLLBACK command.
-Large-object manipulations _must_ occur within a transaction, since
-the large object descriptors are only valid within the context of a
-transaction."
- `(progn
- (pg-exec ,con "BEGIN WORK")
- (handler-case (prog1 (progn ,@body) (pg-exec ,con "COMMIT WORK"))
- (error (e)
- (pg-exec ,con "ROLLBACK WORK")
- (error e)))))
-
-
-;;; this version thanks to Daniel Barlow. The old version would abort
-;;; the transaction before entering the debugger, which made
-;;; debugging difficult.
-(defmacro with-pg-transaction (con &body body)
- "Execute BODY forms in a BEGIN..END block.
-If a PostgreSQL error occurs during execution of the forms, execute
-a ROLLBACK command.
-Large-object manipulations _must_ occur within a transaction, since
-the large object descriptors are only valid within the context of a
-transaction."
- (let ((success (gensym "SUCCESS")))
- `(let (,success)
- (unwind-protect
- (prog2
- (pg-exec ,con "BEGIN WORK")
- (progn ,@body)
- (setf ,success t))
- (pg-exec ,con (if ,success "COMMIT WORK" "ROLLBACK WORK"))))))
-
-(defun pg-for-each (conn select-form callback)
- "Create a cursor for SELECT-FORM, and call CALLBACK for each result.
-Uses the PostgreSQL database connection CONN. SELECT-FORM must be an
-SQL SELECT statement. The cursor is created using an SQL DECLARE
-CURSOR command, then results are fetched successively until no results
-are left. The cursor is then closed.
-
-The work is performed within a transaction. The work can be
-interrupted before all tuples have been handled by THROWing to a tag
-called 'pg-finished."
- (let ((cursor (symbol-name (gensym "PGCURSOR"))))
- (catch 'pg-finished
- (with-pg-transaction conn
- (pg-exec conn "DECLARE " cursor " CURSOR FOR " select-form)
- (unwind-protect
- (loop :for res = (pg-result (pg-exec conn "FETCH 1 FROM " cursor) :tuples)
- :until (zerop (length res))
- :do (funcall callback (first res)))
- (pg-exec conn "CLOSE " cursor))))))
+(defgeneric pg-exec (connection &rest args))
+
+(defgeneric fn (connection fn integer-result &rest args))
+(defgeneric pg-disconnect (connection))
+
+
+
+;; first attempt to connect to connect using the v3 protocol; if this
+;; results in an ErrorResponse we close the connection and retry using
+;; the v2 protocol. This allows us to connect to PostgreSQL 7.4
+;; servers using the benefits of the new protocol, but still interact
+;; with older servers.
(defun pg-connect (dbname user &key (host "localhost") (port 5432) (password ""))
"Initiate a connection with the PostgreSQL backend.
Connect to the database DBNAME with the username USER,
on PORT of HOST, providing PASSWORD if necessary. Return a
connection to the database (as an opaque type). If HOST is nil, attempt
-to connect to the database using a Unix socket."
- (let* ((stream (socket-connect port host))
- (connection (make-pgcon :stream stream :host host :port port))
- (user-packet-length (+ +SM_USER+ +SM_OPTIONS+ +SM_UNUSED+ +SM_TTY+)))
- ;; send the startup packet
- (send-int connection +STARTUP_PACKET_SIZE+ 4)
- (send-int connection +PG_PROTOCOL_LATEST_MAJOR+ 2)
- (send-int connection +PG_PROTOCOL_LATEST_MINOR+ 2)
- (send-string connection dbname +SM_DATABASE+)
- (send-string connection user user-packet-length)
- (flush connection)
- #+cmu (ext:finalize connection (lambda () (pg-disconnect connection)))
- (loop
- (case (read-byte stream)
- ;; ErrorResponse
- ((69) (error 'authentication-failure
- :reason (read-cstring connection 4096)))
-
- ;; Authentication
- ((82)
- (case (read-net-int connection 4)
- ((0) ; AuthOK
- (and (not *pg-disable-type-coercion*)
- (null *parsers*)
- (initialize-parsers connection))
- (when *pg-date-style*
- (setf (pg-date-style connection) *pg-date-style*))
- (when *pg-client-encoding*
- (setf (pg-client-encoding connection) *pg-client-encoding*))
- (return connection))
- ((3) ; AuthUnencryptedPassword
- (send-int connection (+ 5 (length password)) 4)
- (send-string connection password)
- (send-int connection 0 1)
- (flush connection))
- ((4) ; AuthEncryptedPassword
- (let* ((salt (read-chars connection 2))
- (crypted (crypt password salt)))
- #+debug
- (format *debug-io* "Got salt of ~s~%" salt)
- (send-int connection (+ 5 (length crypted)) 4)
- (send-string connection crypted)
- (send-int connection 0 1)
- (flush connection)))
- ((1) ; AuthKerberos4
- (error 'authentication-failure
- :reason "Kerberos4 authentication not supported"))
- ((2) ; AuthKerberos5
- (error 'authentication-failure
- :reason "Kerberos5 authentication not supported"))
- (t (error 'authentication-failure
- :reason "unknown authentication type"))))
-
- (t (error 'protocol-error
- :reason "expected an authentication response"))))))
-
-(defun pg-exec (connection &rest args)
- "Execute the SQL command given by the concatenation of ARGS
-on the database to which we are connected via CONNECTION. Return
-a result structure which can be decoded using `pg-result'."
- (let ((sql (apply #'concatenate 'simple-string args))
- (stream (pgcon-stream connection))
- (tuples '())
- (attributes '())
- (result (make-pgresult :connection connection)))
- (when (> (length sql) +MAX_MESSAGE_LEN+)
- (error "SQL statement too long: ~A" sql))
- (write-byte 81 stream)
- (send-string connection sql)
- (write-byte 0 stream)
- (flush connection)
- (do ((b (read-byte stream nil :eof)
- (read-byte stream nil :eof)))
- ((eq b :eof) (error 'protocol-error :reason "unexpected EOF from backend"))
- (case b
- ;; asynchronous notify, #\A
- ((65)
- ;; read the pid
- (read-net-int connection 4)
- (handle-notice connection))
-
- ;; BinaryRow, #\B
- ((66)
- (setf (pgcon-binary-p connection) t)
- (unless attributes
- (error 'protocol-error :reason "Tuple received before metadata"))
- (push (read-tuple connection attributes) tuples))
-
- ;; CompletedResponse, #\C
- ((67)
- (let ((status (read-cstring connection +MAX_MESSAGE_LEN+)))
- (setf (pgresult-status result) status)
- (setf (pgresult-tuples result) (nreverse tuples))
- (setf (pgresult-attributes result) attributes)
- (return result)))
-
- ;; AsciiRow (text data transfer), #\D
- ((68)
- (setf (pgcon-binary-p connection) nil)
- (unless attributes
- (error 'protocol-error :reason "Tuple received before metadata"))
- (push (read-tuple connection attributes) tuples))
-
- ;; ErrorResponse, #\E
- ((69)
- (let ((msg (read-cstring connection +MAX_MESSAGE_LEN+)))
- (error 'backend-error :reason msg)))
-
- ;; #\G and #\H: start copy in, start copy out
-
- ;; EmptyQueryResponse, #\I
- ((73)
- (let ((c (read-byte stream)))
- (when (< 0 c)
- (error 'protocol-error :reason "Garbled data"))))
-
- ;; BackendKeyData, #\K
- ((75)
- (setf (pgcon-pid connection) (read-net-int connection 4))
- (setf (pgcon-secret connection) (read-net-int connection 4)))
-
- ;; NotificationResponse, #\N
- ((78)
- (setf (pgcon-pid connection) (read-net-int connection 4))
- (handle-notice connection))
-
- ;; CursorResponse, #\P
- ((80)
- (let ((str (read-cstring connection +MAX_MESSAGE_LEN+)))
- (declare (ignore str))
- ;; (format *debug-io* "Portal name ~a~%" str)
- ))
-
- ;; RowDescription (metadata for subsequent tuples), #\T
- ((84)
- (and attributes (error "Cannot handle multiple result group"))
- (setq attributes (read-attributes connection)))
-
- ;; ReadyForQuery
- ((90) t)
-
- (t
- (error 'protocol-error
- :reason (format nil "Unknown response type from backend ~d" b)))))))
+to connect to the database using a Unix socket.
+We first attempt to speak the PostgreSQL 7.4 protocol, and fall back to
+the older network protocol if necessary."
+ (handler-case (pg-connect/v3 dbname user
+ :host host
+ :port port
+ :password password)
+ (protocol-error (c)
+ (warn "reconnecting using protocol version 2")
+ (pg-connect/v2 dbname user
+ :host host
+ :port port
+ :password password))))
(defun pg-result (result what &rest args)
@@ -466,420 +229,9 @@
(error "Only INSERT commands generate an oid: ~s" status))))
(t (error "Unknown result request: ~s" what))))
-(defun pg-disconnect (connection)
- (write-byte 88 (pgcon-stream connection))
- (flush connection)
- (close (pgcon-stream connection))
- (values))
-
-
-
-;; Attribute information is as follows
-;; attribute-name (string)
-;; attribute-type as an oid from table pg_type
-;; attribute-size (in bytes?)
-(defun read-attributes (connection)
- (let ((attribute-count (read-net-int connection 2))
- (attributes '()))
- (do ((i attribute-count (- i 1)))
- ((zerop i) (nreverse attributes))
- (let ((type-name (read-cstring connection +MAX_MESSAGE_LEN+))
- (type-id (read-net-int connection 4))
- (type-len (read-net-int connection 2))
- ;; this doesn't exist in the 6.3 protocol !!
- (type-modifier (read-net-int connection 4)))
- (declare (ignore type-modifier))
- (push (list type-name type-id type-len) attributes)))))
-
-;; the bitmap is a string, which we interpret as a sequence of bytes
-(defun bitmap-ref (bitmap ref)
- (multiple-value-bind (char-ref bit-ref)
- (floor ref 8)
- (logand #b10000000 (ash (aref bitmap char-ref) bit-ref))))
-
-;; the server starts by sending a bitmap indicating which tuples are
-;; NULL. "A bit map with one bit for each field in the row. The 1st
-;; field corresponds to bit 7 (MSB) of the 1st byte, the 2nd field
-;; corresponds to bit 6 of the 1st byte, the 8th field corresponds to
-;; bit 0 (LSB) of the 1st byte, the 9th field corresponds to bit 7 of
-;; the 2nd byte, and so on. Each bit is set if the value of the
-;; corresponding field is not NULL. If the number of fields is not a
-;; multiple of 8, the remainder of the last byte in the bit map is
-;; wasted."
-(defun read-tuple (connection attributes)
- (let* ((num-attributes (length attributes))
- (num-bytes (ceiling (/ num-attributes 8)))
- (bitmap (read-bytes connection num-bytes))
- (correction (if (pgcon-binary-p connection) 0 -4))
- (tuples '()))
- (do ((i 0 (+ i 1))
- (type-ids (mapcar #'second attributes) (cdr type-ids)))
- ((= i num-attributes) (nreverse tuples))
- (cond ((zerop (bitmap-ref bitmap i))
- (push nil tuples))
- (t
- (let* ((len (+ (read-net-int connection 4) correction))
- (raw (read-chars connection (max 0 len)))
- (parsed (parse raw (car type-ids))))
- (push parsed tuples)))))))
-
-;; FIXME could signal a postgresql-notification condition
-(defun handle-notice (connection)
- (push (read-cstring connection +MAX_MESSAGE_LEN+)
- (pgcon-notices connection)))
-
-
-;; type coercion support ==============================================
-;;
-;; When returning data from a SELECT statement, PostgreSQL starts by
-;; sending some metadata describing the attributes. This information
-;; is read by `PG:READ-ATTRIBUTES', and consists of each attribute's
-;; name (as a string), its size (in bytes), and its type (as an oid
-;; which points to a row in the PostgreSQL system table pg_type). Each
-;; row in pg_type includes the type's name (as a string).
-;;
-;; We are able to parse a certain number of the PostgreSQL types (for
-;; example, numeric data is converted to a numeric Common Lisp type,
-;; dates are converted to the CL date representation, booleans to
-;; lisp booleans). However, there isn't a fixed mapping from a
-;; type to its OID which is guaranteed to be stable across database
-;; installations, so we need to build a table mapping OIDs to parser
-;; functions.
-;;
-;; This is done by the procedure `PG:INITIALIZE-PARSERS', which is run
-;; the first time a connection is initiated with the database from
-;; this invocation of CL, and which issues a SELECT statement to
-;; extract the required information from pg_type. This initialization
-;; imposes a slight overhead on the first request, which you can avoid
-;; by setting `*PG-DISABLE-TYPE-COERCION*' to non-nil if it bothers you.
-;; ====================================================================
-
-(defvar type-parsers
- `(("bool" . ,'bool-parser)
- ("char" . ,'text-parser)
- ("char2" . ,'text-parser)
- ("char4" . ,'text-parser)
- ("char8" . ,'text-parser)
- ("char16" . ,'text-parser)
- ("text" . ,'text-parser)
- ("varchar" . ,'text-parser)
- ("numeric" . ,'integer-parser)
- ("int2" . ,'integer-parser)
- ("int4" . ,'integer-parser)
- ("int8" . ,'integer-parser)
- ("oid" . ,'integer-parser)
- ("float4" . ,'float-parser)
- ("float8" . ,'float-parser)
- ("money" . ,'text-parser) ; "$12.34"
- ("abstime" . ,'timestamp-parser)
- ("date" . ,'date-parser)
- ("timestamp" . ,'timestamp-parser)
- ("timestamptz" . ,'timestamp-parser)
- ("datetime" . ,'timestamp-parser)
- ("time" . ,'text-parser) ; preparsed "15:32:45"
- ("timetz" . ,'text-parser)
- ("reltime" . ,'text-parser) ; don't know how to parse these
- ("timespan" . ,'interval-parser)
- ("interval" . ,'interval-parser)
- ("tinterval" . ,'interval-parser)))
-
-
-;; see `man pgbuiltin' for details on PostgreSQL builtin types
-(defun integer-parser (str) (parse-integer str))
-
-(defun float-parser (str) (read-from-string str))
-
-;; FIXME this may need support for charset decoding
-(defun text-parser (str) str)
-
-(defun bool-parser (str)
- (cond ((string= "t" str) t)
- ((string= "f" str) nil)
- (t (error "Badly formed boolean from backend: ~s" str))))
-
-(defun parse-timestamp (str)
- (let* ((year (parse-integer (subseq str 0 4)))
- (month (parse-integer (subseq str 5 7)))
- (day (parse-integer (subseq str 8 10)))
- (hours (parse-integer (subseq str 11 13)))
- (minutes (parse-integer (subseq str 14 16)))
- (seconds (parse-integer (subseq str 17 19)))
- (start-tz (if (eql #\+ (char str (- (length str) 3)))
- (- (length str) 3)))
- (tz (when start-tz
- (parse-integer (subseq str start-tz))))
- (milliseconds (if (eql (char str 19) #\.)
- (parse-integer (subseq str 20 start-tz)) 0)))
- (values year month day hours minutes seconds milliseconds tz)))
-
-;; format for abstime/timestamp etc with ISO output syntax is
-;;
-;; "1999-01-02 05:11:23.0345645+01"
-;;
-;; which we convert to a CL universal time. Thanks to James Anderson
-;; for a fix for timestamp format in PostgreSQL 7.3 (with or without
-;; tz, with or without milliseconds).
-(defun timestamp-parser (str)
- (multiple-value-bind (year month day hours minutes seconds)
- (parse-timestamp str)
- (encode-universal-time seconds minutes hours day month year)))
-
-(defun precise-timestamp-parser (str)
- (multiple-value-bind (year month day hours minutes seconds milliseconds)
- (parse-timestamp str)
- (+ (encode-universal-time seconds minutes hours day month year)
- (/ milliseconds 1000.0))))
-
-;; An interval is what you get when you subtract two timestamps. We
-;; convert to a number of seconds.
-(defun interval-parser (str)
- (let* ((hours (parse-integer (subseq str 0 2)))
- (minutes (parse-integer (subseq str 3 5)))
- (seconds (parse-integer (subseq str 6 8)))
- (milliseconds (parse-integer (subseq str 9))))
- (+ (/ milliseconds (expt 10.0 (- (length str) 9)))
- seconds
- (* 60 minutes)
- (* 60 60 hours))))
-
-
-;; format for abstime/timestamp etc with ISO output syntax is
-;;; "1999-01-02 00:00:00+01"
-;; which we convert to a CL universal time
-(defun isodate-parser (str)
- (let ((year (parse-integer (subseq str 0 4)))
- (month (parse-integer (subseq str 5 7)))
- (day (parse-integer (subseq str 8 10)))
- (hours (parse-integer (subseq str 11 13)))
- (minutes (parse-integer (subseq str 14 16)))
- (seconds (parse-integer (subseq str 17 19)))
- (tz (parse-integer (subseq str 19 22))))
- (encode-universal-time seconds minutes hours day month year tz)))
-
-;; format for date with ISO output syntax is
-;;; "1999-01-02"
-;; which we convert to a CL universal time
-(defun date-parser (str)
- (let ((year (parse-integer (subseq str 0 4)))
- (month (parse-integer (subseq str 5 7)))
- (day (parse-integer (subseq str 8 10))))
- (encode-universal-time 0 0 0 day month year)))
-
-(defun initialize-parsers (connection)
- (let* ((pgtypes (pg-exec connection "SELECT typname,oid FROM pg_type"))
- (tuples (pg-result pgtypes :tuples)))
- (setq *parsers* '())
- (map nil
- (lambda (tuple)
- (let* ((typname (first tuple))
- (oid (parse-integer (second tuple)))
- (type (assoc typname type-parsers :test #'string=)))
- (if (consp type)
- (push (cons oid (cdr type)) *parsers*))))
- tuples)))
-
-(defun parse (str oid)
- (let ((parser (assoc oid *parsers* :test #'eql)))
- (if (consp parser)
- (funcall (cdr parser) str)
- str)))
-
-;; large objects support ===============================================
-;;
-;; Sir Humphrey: Who is Large and to what does he object?
-;;
-;; Large objects are the PostgreSQL way of doing what most databases
-;; call BLOBs (binary large objects). In addition to being able to
-;; stream data to and from large objects, PostgreSQL's
-;; object-relational capabilities allow the user to provide functions
-;; which act on the objects.
-;;
-;; For example, the user can define a new type called "circle", and
-;; define a C or Tcl function called `circumference' which will act on
-;; circles. There is also an inheritance mechanism in PostgreSQL.
-;;
-;; The PostgreSQL large object interface is similar to the Unix file
-;; system, with open, read, write, lseek etc.
-;;
-;; Implementation note: the network protocol for large objects changed
-;; around version 6.5 to use network order for integers.
-;; =====================================================================
-
-(defvar *lo-initialized* nil)
-(defvar *lo-functions* '())
-
-(defun lo-init (connection)
- (let ((res (pg-exec connection
- "SELECT proname, oid from pg_proc WHERE "
- "proname = 'lo_open' OR "
- "proname = 'lo_close' OR "
- "proname = 'lo_creat' OR "
- "proname = 'lo_unlink' OR "
- "proname = 'lo_lseek' OR "
- "proname = 'lo_tell' OR "
- "proname = 'loread' OR "
- "proname = 'lowrite'")))
- (setq *lo-functions* '())
- (dolist (tuple (pg-result res :tuples))
- (push (cons (car tuple) (cadr tuple)) *lo-functions*))
- (unless (= 8 (length *lo-functions*))
- (error "Couldn't find OIDs for all the large object functions"))
- (setq *lo-initialized* t)))
-
-;; Execute one of the large-object functions (lo_open, lo_close etc).
-;; Argument FN is either an integer, in which case it is the OID of an
-;; element in the pg_proc table, and otherwise it is a string which we
-;; look up in the alist *lo-functions* to find the corresponding OID.
-(defun fn (connection fn integer-result &rest args)
- (or *lo-initialized* (lo-init connection))
- (let ((fnid (cond ((integerp fn) fn)
- ((not (stringp fn))
- (error "Expecting a string or an integer: ~s" fn))
- ((assoc fn *lo-functions* :test #'string=)
- (cdr (assoc fn *lo-functions* :test #'string=)))
- (t (error "Unknown builtin function ~s" fn)))))
- (send-int connection 70 1) ; function call
- (send-int connection 0 1)
- (send-int connection fnid 4)
- (send-int connection (length args) 4)
- (dolist (arg args)
- (cond ((integerp arg)
- (send-int connection 4 4)
- (send-int connection arg 4))
- ((stringp arg)
- (send-int connection (length arg) 4)
- (send-string connection arg))
- (t (error 'protocol-error
- :reason (format nil "Unknown fastpath type ~s" arg)))))
- (flush connection)
- (loop :with result = nil
- :with ready = nil
- :for b = (read-byte (pgcon-stream connection) nil :eof) :do
- (case b
- ;; FunctionResultResponse
- ((86)
- (let ((res (read-byte (pgcon-stream connection) nil :eof)))
- (cond ((= res 0) ; empty result
- (return-from fn nil))
- ((= res 71) ; nonempty result
- (let ((len (read-net-int connection 4)))
- (if integer-result
- (setq result (read-net-int connection len))
- (setq result (read-chars connection len)))))
- (t (error 'protocol-error :reason "wierd FunctionResultResponse")))))
-
- ;; end of FunctionResult
- ((48) (return-from fn result))
-
- ((69) (error 'backend-error :reason (read-cstring connection 4096)))
-
- ;; NoticeResponse
- ((78)
- (setf (pgcon-pid connection) (read-net-int connection 4))
- (handle-notice connection))
-
- ;; ReadyForQuery
- ((90) (setq ready t))
-
- (t (error 'protocol-error
- :reason (format nil "Unexpected byte ~s" b)))))))
-
-;; returns an OID
-(defun pglo-create (connection &optional (modestr "r"))
- (let* ((mode (cond ((integerp modestr) modestr)
- ((string= "r" modestr) +INV_READ+)
- ((string= "w" modestr) +INV_WRITE+)
- ((string= "rw" modestr) (logior +INV_READ+ +INV_WRITE+))
- (t (error "Bad mode ~s" modestr))))
- (oid (fn connection "lo_creat" t mode)))
- (unless (integerp oid)
- (error 'backend-error :reason "Didn't return an OID"))
- (when (zerop oid)
- (error 'backend-error :reason "Can't create large object"))
- oid))
-
-;; args = modestring (default "r", or "w" or "rw")
-;; returns a file descriptor for use in later lo-* procedures
-(defun pglo-open (connection oid &optional (modestr "r"))
- (let* ((mode (cond ((integerp modestr) modestr)
- ((string= "r" modestr) +INV_READ+)
- ((string= "w" modestr) +INV_WRITE+)
- ((string= "rw" modestr) (logior +INV_READ+ +INV_WRITE+))
- (t (error "Bad mode ~s" modestr))))
- (fd (fn connection "lo_open" t oid mode)))
- (assert (integerp fd))
- fd))
-
-(defun pglo-close (connection fd)
- (fn connection "lo_close" t fd))
-
-(defun pglo-read (connection fd bytes)
- (fn connection "loread" nil fd bytes))
-
-(defun pglo-write (connection fd buf)
- (fn connection "lowrite" t fd buf))
-
-(defun pglo-lseek (connection fd offset whence)
- (fn connection "lo_lseek" t fd offset whence))
-
-(defun pglo-tell (connection fd)
- (fn connection "lo_tell" t fd))
-
-(defun pglo-unlink (connection oid)
- (fn connection "lo_unlink" t oid))
-
-(defun pglo-import (connection filename)
- (let ((buf (make-string +LO_BUFSIZ+))
- (oid (pglo-create connection "rw")))
- (with-open-file (in filename :direction :input)
- (loop :with fdout = (pglo-open connection oid "w")
- :for bytes = (read-sequence buf in)
- :until (< bytes +LO_BUFSIZ+)
- :do (pglo-write connection fdout buf)
- :finally
- (pglo-write connection fdout (subseq buf 0 bytes))
- (pglo-close connection fdout)))
- oid))
-
-(defun pglo-export (connection oid filename)
- (with-open-file (out filename :direction :output)
- (loop :with fdin = (pglo-open connection oid "r")
- :for str = (pglo-read connection fdin +LO_BUFSIZ+)
- :until (zerop (length str))
- :do (write-sequence str out)
- :finally (pglo-close connection fdin))))
-
-
-;; DBMS metainformation ================================================
-;;
-;; Metainformation such as the list of databases present in the
-;; database management system, list of tables, attributes per table.
-;; This information is not available directly, but can be deduced by
-;; querying the system tables.
-;;
-;; Based on the queries issued by psql in response to user commands
-;; `\d' and `\d tablename'; see file pgsql/src/bin/psql/psql.c
-;; =====================================================================
-(defun pg-databases (conn)
- "Return a list of the databases available at this site."
- (let ((res (pg-exec conn "SELECT datname FROM pg_database")))
- (reduce #'append (pg-result res :tuples))))
-
-(defun pg-tables (conn)
- "Return a list of the tables present in this database."
- (let ((res (pg-exec conn "SELECT relname FROM pg_class, pg_user WHERE "
- "(relkind = 'r') AND relname !~ '^pg_' AND usesysid = relowner ORDER BY relname")))
- (reduce #'append (pg-result res :tuples))))
-
-(defun pg-columns (conn table)
- "Return a list of the columns present in TABLE."
- (let ((res (pg-exec conn (format nil "SELECT * FROM ~s WHERE 0 = 1" table))))
- (mapcar #'first (pg-result res :attributes))))
-
-(defun pg-backend-version (conn)
- "Return a string identifying the version and operating environment of the backend."
- (let ((res (pg-exec conn "SELECT version()")))
- (first (pg-result res :tuple 0))))
+
+
+
;; support routines ===================================================
@@ -934,7 +286,7 @@
(defun read-cstring (connection maxbytes)
"Read a null-terminated string from CONNECTION."
- (declare (type fixnum howmany))
+ (declare (type fixnum maxbytes))
(let ((stream (pgcon-stream connection))
(chars nil))
(do ((b (read-byte stream nil nil) (read-byte stream nil nil))
Index: pg/sysdep.lisp
diff -u pg/sysdep.lisp:1.1.1.1 pg/sysdep.lisp:1.2
--- pg/sysdep.lisp:1.1.1.1 Wed Mar 3 08:11:50 2004
+++ pg/sysdep.lisp Fri Mar 5 13:08:08 2004
@@ -1,6 +1,11 @@
-;;; system-dependent parts of pg-dot-lisp
+;;; sysdep.lisp -- system-dependent parts of pg-dot-lisp
+;;;
+;;; Author: Eric Marsden <emarsden(a)laas.fr>
+;;; Time-stamp: <2004-03-05 emarsden>
+;;
+;;
-(in-package :pg)
+(in-package :postgresql)
(eval-when (:compile-toplevel :load-toplevel :execute)
#+allegro (require :socket)
1
0
Update of /project/pg/cvsroot/pg
In directory common-lisp.net:/tmp/cvs-serv9233
Log Message:
initial import
Status:
Vendor Tag: clnet
Release Tags: start
N pg/pg.lisp
N pg/README
N pg/pg-tests.lisp
N pg/defpackage.lisp
N pg/pg.asd
N pg/sysdep.lisp
N pg/NEWS
N pg/cmucl-install-subsystem.lisp
No conflicts created by this import
Date: Wed Mar 3 08:11:50 2004
Author: emarsden
New module pg added
1
0