branch: elpa/pg
commit 86b7d4525db7827b2afedeb7574946f10d48a778
Author: Eric Marsden <eric.mars...@risk-engineering.org>
Commit: Eric Marsden <eric.mars...@risk-engineering.org>

    Improve error handling for large object functionality
---
 pg-lo.el | 104 +++++++++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 81 insertions(+), 23 deletions(-)

diff --git a/pg-lo.el b/pg-lo.el
index 958bbd11c59..b8df0a01978 100644
--- a/pg-lo.el
+++ b/pg-lo.el
@@ -69,6 +69,12 @@
 ;; corresponding OID.
 (cl-defun pg-fn (con fn integer-result &rest args)
   (pg-connection-set-busy con t)
+  (when (pgcon-query-log con)
+    (let ((query (format "EXEC pg-fn %s %s %s" fn integer-result args)))
+      (with-current-buffer (pgcon-query-log con)
+        (insert query "\n"))
+      (when noninteractive
+        (message "%s" query))))
   (unless pg-lo-initialized
     (pg-lo-init con))
   (let ((fnid (cond ((integerp fn) fn)
@@ -150,10 +156,13 @@
                ;; end of FunctionResult
                (?0 nil)
 
-               (t (error "Unexpected character in pg-fn: %s" c))))))
+               (t
+                (let ((msg (format "Unexpected data from backend in pg-fn: %s" 
c)))
+                  (signal 'pg-protocol-error (list msg))))))))
 
-(defconst pg--INV_WRITE   131072)       ; fe-lobj.c
-(defconst pg--INV_READ    262144)
+;; These constants are defined in libpq-fs.h in the PostgreSQL source.
+(defconst pg--INV_WRITE   #x20000)
+(defconst pg--INV_READ    #x40000)
 
 ;; Note: recent versions of PostgreSQL support an alternative function 
lo_create
 ;; which takes the desired oid as a parameter.
@@ -171,17 +180,22 @@ ignored in PostgreSQL releases after v8.1."
                           (signal 'pg-user-error (list msg))))))
          (oid (pg-fn con "lo_creat" t mode)))
     (cond ((not (integerp oid))
-           (error "Returned value not an OID: %s" oid))
-          ((zerop oid)
-           (error "Can't create large object"))
+           (let ((msg (format "Returned value not an OID: %s" oid)))
+             (signal 'pg-operational-error (list msg))))
+          ((eql -1 oid)
+           (signal 'pg-operational-error (list "Can't create large object")))
           (t oid))))
 
 (defun pg-lo-open (con oid &optional mode)
   "Open the PostgreSQL large object designated by OID for reading.
-Uses PostgreSQL connection CON. The string MODE determines whether the
-object is opened for reading (`r'), or writing (`w'), or both (`rw').
-Returns a large object descriptor that can be used with functions
-`pg-lo-read', `pg-lo-write', `pg-lo-lseek', `pg-lo-tell' and `pg-lo-close'."
+The string MODE determines whether the object is opened for
+reading (`r'), or writing (`w'), or both (`rw'); it is ignored in
+PostgreSQL releases after v8.1. Returns a large object descriptor that
+can be used with functions `pg-lo-read', `pg-lo-write', `pg-lo-lseek',
+`pg-lo-tell' and `pg-lo-close'. Uses PostgreSQL connection CON."
+  (unless (> oid 0)
+    (let ((msg (format "%s is not a valid PostgreSQL OID" oid)))
+      (signal 'pg-user-error (list msg))))
   (let* ((modestr (or mode "r"))
          (mode (cond ((integerp modestr) modestr)
                      ((string= "r" modestr) pg--INV_READ)
@@ -191,18 +205,32 @@ Returns a large object descriptor that can be used with 
functions
                      (t (let ((msg (format "pg-lo-open: bad mode %s" mode)))
                           (signal 'pg-user-error (list msg))))))
          (fd (pg-fn con "lo_open" t oid mode)))
-    (unless (integerp fd)
-      (error "Couldn't open large object"))
-    fd))
+    (if (eql -1 fd)
+      (signal 'pg-operational-error (list "Couldn't open large object"))
+    fd)))
 
-(defsubst pg-lo-close (con fd)
+(defun pg-lo-close (con fd)
   "Closes the PostgreSQL large object designated by FD.
 Uses PostgreSQL connection CON."
-  (pg-fn con "lo_close" t fd))
+  (unless (>= fd 0)
+    (let ((msg (format "pg-lo-close: invalid FD %s" fd)))
+      (signal 'pg-user-error (list msg))))
+  (let ((ret (pg-fn con "lo_close" t fd)))
+    (if (eql -1 ret)
+        (signal 'pg-operational-error (list "lo_close function call failed"))
+      ret)))
 
 (defun pg-lo-read (con fd bytes)
-  "Read BYTES octets from large object designated by FD.
+  "Read BYTES octets from PostgreSQL large object designated by FD.
 Uses PostgreSQL connection CON."
+  (unless (>= bytes 0)
+    (signal 'pg-user-error (list "pg-lo-read: BYTES must be >= 0")))
+  ;; This error will be caught and reported by the PostgreSQL backend, but 
instead of being detected
+  ;; immediately when handling this function call request, it is detected 
after the next request is
+  ;; made. It's more practical to detect it immediately on the client side.
+  (unless (>= fd 0)
+    (let ((msg (format "pg-lo-read: invalid FD %s" fd)))
+      (signal 'pg-user-error (list msg))))
   (let* ((encoded (pg-fn con "loread" nil fd bytes))
          (hexdigits (substring encoded 2)))
     (unless (and (eql 92 (aref encoded 0))   ; \ character
@@ -211,10 +239,18 @@ Uses PostgreSQL connection CON."
               (list "Unexpected format for BYTEA binary string")))
     (decode-hex-string hexdigits)))
 
-(defsubst pg-lo-write (con fd buf)
+(defun pg-lo-write (con fd buf)
   "Write the contents of BUF to the large object designated by FD.
 Uses PostgreSQL connection CON."
-  (pg-fn con "lowrite" t fd buf))
+  (unless (>= fd 0)
+    (let ((msg (format "pg-lo-write: invalid FD %s" fd)))
+      (signal 'pg-user-error (list msg))))
+  (unless (stringp buf)
+    (signal 'pg-user-error (list "pg-lo-write: invalid BUF argument")))
+  (let ((ret (pg-fn con "lowrite" t fd buf)))
+    (if (eql -1 ret)
+        (signal 'pg-operational-error (list "lo_write function call failed"))
+      ret)))
 
 
 (defconst pg-SEEK_SET 0)
@@ -228,6 +264,9 @@ WHENCE can be `pg-SEEK_SET' (seek from object start),
 object end). OFFSET may be a large integer (int8 type in PostgreSQL;
 this function calls the PostgreSQL backend function `lo_lseek64'). Uses
 PostgreSQL connection CON."
+  (unless (>= fd 0)
+    (let ((msg (format "pg-lo-lseek: invalid FD %s" fd)))
+      (signal 'pg-user-error (list msg))))
   (let* ((res (pg-exec-prepared con "SELECT lo_lseek64($1, $2, $3)"
                                 `((,fd . "int4") (,offset . "int8") (,whence . 
"int4"))))
          (ret (cl-first (pg-result res :tuple 0))))
@@ -239,6 +278,9 @@ PostgreSQL connection CON."
   "Return the current file position in PostgreSQL large object FD.
 Uses PostgreSQL connection CON. Uses the PostgreSQL backend function
 `lo_tell64' to work with large objects."
+  (unless (>= fd 0)
+    (let ((msg (format "pg-lo-tell: invalid FD %s" fd)))
+      (signal 'pg-user-error (list msg))))
   (let* ((res (pg-exec-prepared con "SELECT lo_tell64($1)" `((,fd . "int4"))))
          (ret (cl-first (pg-result res :tuple 0))))
     (if (eql -1 ret)
@@ -250,6 +292,9 @@ Uses PostgreSQL connection CON. Uses the PostgreSQL backend 
function
 LEN may be a large integer (int8 type in PostgreSQL); this calls the
 PostgreSQL backend function `lo_truncate64'. Uses PostgreSQL connection
 CON."
+  (unless (>= fd 0)
+    (let ((msg (format "pg-lo-truncate: invalid FD %s" fd)))
+      (signal 'pg-user-error (list msg))))
   (let* ((res (pg-exec-prepared con "SELECT lo_truncate64($1, $2)"
                                 `((,fd . "int4") (,len . "int8"))))
          (ret (cl-first (pg-result res :tuple 0))))
@@ -257,20 +302,29 @@ CON."
       (signal 'pg-operational-error (list "lo_truncate64 function call 
failed"))))) 
 
 (defun pg-lo-unlink (con oid)
-  "Unlink the PostgreSQL large object identified by OID.
+  "Unlink (delete) the PostgreSQL large object identified by OID.
 Uses PostgreSQL connection CON."
-  (pg-fn con "lo_unlink" t oid))
+  (unless (> oid 0)
+    (let ((msg (format "%s is not a valid PostgreSQL OID" oid)))
+      (signal 'pg-user-error (list msg))))
+  (let ((ret (pg-fn con "lo_unlink" t oid)))
+    (if (eql -1 ret)
+        (signal 'pg-operational-error (list "lo_unlink function call failed"))
+      ret)))
 
 ;; FIXME should use unwind-protect here
 (defun pg-lo-import (con filename)
   "Import FILENAME as a PostgreSQL large object.
-Uses PostgreSQL connection CON. Returns the OID of the new object."
+Returns the OID of the new object. Note that FILENAME is a client-side
+file path, rather than a server-side path as used by the `lo_import'
+function in libpq. Uses PostgreSQL connection CON."
   (let* ((buf (get-buffer-create (format " *pg-%s" filename)))
          (oid (pg-lo-create con "rw"))
          (fdout (pg-lo-open con oid "w"))
-         (pos (point-min)))
+         (pos nil))
     (with-current-buffer buf
       (insert-file-contents-literally filename)
+      (setq pos (point-min))
       (while (< pos (point-max))
         (pg-lo-write con fdout
          (buffer-substring-no-properties pos (min (point-max) (cl-incf pos 
1024)))))
@@ -278,9 +332,13 @@ Uses PostgreSQL connection CON. Returns the OID of the new 
object."
     (kill-buffer buf)
     oid))
 
+;; Note that we are not using the lo_export fastpath function, in order to use 
a client-side output
+;; path, rather than an output path on the server where PostgreSQL is running.
 (defun pg-lo-export (con oid filename)
   "Export PostgreSQL large object identified by OID to FILENAME.
-Uses PostgreSQL connection CON."
+Note that FILENAME is a client-side file path, rather than a server-side
+path as used by the `lo_export' function in libpq. Uses PostgreSQL
+connection CON."
   (let* ((buf (get-buffer-create (format " *pg-%d" oid)))
          (fdin (pg-lo-open con oid "r")))
     (with-current-buffer buf

Reply via email to