PageRenderTime 33ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/cl-postgres/errors.lisp

http://github.com/marijnh/Postmodern
Lisp | 184 lines | 163 code | 13 blank | 8 comment | 3 complexity | 08c7034d299932648172d2ef6f9d8758 MD5 | raw file
  1. ;;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Base: 10; Package: CL-POSTGRES; -*-
  2. (in-package :cl-postgres)
  3. (defparameter *current-query* nil)
  4. (defparameter *query-log* nil)
  5. (defparameter *query-callback* 'log-query)
  6. (defun log-query (query time-units)
  7. (when *query-log*
  8. (format *query-log* "CL-POSTGRES query (~ams): ~a~%"
  9. (round (/ (* 1000 time-units)
  10. internal-time-units-per-second))
  11. query)))
  12. (defmacro with-query ((query) &body body)
  13. (let ((time-name (gensym)))
  14. `(let ((*current-query* ,query)
  15. (,time-name (if *query-callback* (get-internal-real-time) 0)))
  16. (multiple-value-prog1 (progn ,@body)
  17. (when *query-callback*
  18. (funcall *query-callback*
  19. *current-query*
  20. (- (get-internal-real-time) ,time-name)))))))
  21. ;;
  22. ;; See http://www.postgresql.org/docs/current/protocol-error-fields.html
  23. ;; and https://www.postgresql.org/docs/current/errcodes-appendix.html
  24. ;; for details, including documentation strings.
  25. ;;
  26. (define-condition database-error (error)
  27. ((error-code :initarg :code :initform nil :reader database-error-code
  28. :documentation "Code: the SQLSTATE code for the error (see Appendix A). Not localizable. Always present.")
  29. (message :initarg :message :accessor database-error-message
  30. :documentation "Message: the primary human-readable error message. This should be accurate but terse (typically one line). Always present.")
  31. (detail :initarg :detail :initform nil :reader database-error-detail
  32. :documentation "Detail: an optional secondary error message carrying more detail about the problem. Might run to multiple lines.")
  33. (hint :initarg :hint :initform nil :reader database-error-hint
  34. :documentation "Hint: an optional suggestion what to do about the problem.")
  35. (context :initarg :context :initform nil :reader database-error-context
  36. :documentation "Where: an indication of the context in which the error occurred. Presently this includes a call stack traceback of active procedural language functions and internally-generated queries. The trace is one entry per line, most recent first."
  37. )
  38. (query :initform *current-query* :reader database-error-query
  39. :documentation "Query that led to the error, if any.")
  40. (position :initarg :position :initform nil :reader database-error-position
  41. :documentation "Position: the field value is a decimal ASCII integer, indicating an error cursor position as an index into the original query string. The first character has index 1, and positions are measured in characters not bytes.")
  42. (cause :initarg :cause :initform nil :reader database-error-cause))
  43. (:report (lambda (err stream)
  44. (format stream "Database error~@[ ~A~]: ~A~@[~&DETAIL: ~A~]~@[~&HINT: ~A~]~@[~&CONTEXT: ~A~]~@[~&QUERY: ~A~]~@[~VT^~]"
  45. (database-error-code err)
  46. (database-error-message err)
  47. (database-error-detail err)
  48. (database-error-hint err)
  49. (database-error-context err)
  50. (database-error-query err)
  51. (database-error-position err))))
  52. (:documentation "This is the condition type that will be used to
  53. signal virtually all database-related errors \(though in some cases
  54. socket errors may be raised when a connection fails on the IP
  55. level)."))
  56. (defun database-error-constraint-name (err)
  57. "Given a database-error for an integrity violation, will attempt to
  58. extract the constraint name."
  59. (labels ((extract-quoted-part (string n)
  60. "Extracts the Nth quoted substring from STRING."
  61. (let* ((start-quote-inst (* 2 n))
  62. (start-quote-pos (position-nth #\" string start-quote-inst))
  63. (end-quote-pos (position #\" string :start (1+ start-quote-pos))))
  64. (subseq string (1+ start-quote-pos) end-quote-pos)))
  65. (position-nth (item seq n)
  66. "Finds the position of the zero-indexed Nth ITEM in SEQ."
  67. (loop :with pos = -1 :repeat (1+ n)
  68. :do (setf pos (position item seq :start (1+ pos)))
  69. :finally (return pos))))
  70. (let ((message (database-error-message err)))
  71. (typecase err
  72. (cl-postgres-error:not-null-violation (extract-quoted-part message 0))
  73. (cl-postgres-error:unique-violation (extract-quoted-part message 0))
  74. (cl-postgres-error:foreign-key-violation (extract-quoted-part message 1))
  75. (cl-postgres-error:check-violation (extract-quoted-part message 1))))))
  76. (defun database-error-extract-name (err)
  77. "Given a database-error, will extract the critical name from the error message."
  78. (labels ((extract-quoted-part (string n)
  79. "Extracts the Nth quoted substring from STRING."
  80. (let* ((start-quote-inst (* 2 n))
  81. (start-quote-pos (position-nth #\" string start-quote-inst))
  82. (end-quote-pos (position #\" string :start (1+ start-quote-pos))))
  83. (subseq string (1+ start-quote-pos) end-quote-pos)))
  84. (position-nth (item seq n)
  85. "Finds the position of the zero-indexed Nth ITEM in SEQ."
  86. (loop :with pos = -1 :repeat (1+ n)
  87. :do (setf pos (position item seq :start (1+ pos)))
  88. :finally (return pos))))
  89. (let* ((message (database-error-message err)))
  90. (typecase err
  91. (cl-postgres-error:invalid-sql-statement-name
  92. (extract-quoted-part message 0))
  93. (cl-postgres-error:duplicate-prepared-statement
  94. (extract-quoted-part message 0))))))
  95. (define-condition database-connection-error (database-error) ()
  96. (:documentation "Conditions of this type are signalled when an error
  97. occurs that breaks the connection socket. They offer a :reconnect
  98. restart."))
  99. (define-condition database-connection-lost (database-connection-error) ()
  100. (:documentation "Raised when a query is initiated on a disconnected
  101. connection object."))
  102. (define-condition database-socket-error (database-connection-error) ()
  103. (:documentation "Used to wrap stream-errors and socket-errors,
  104. giving them a database-connection-error superclass."))
  105. (defun wrap-socket-error (err)
  106. (make-instance 'database-socket-error
  107. :message (princ-to-string err)
  108. :cause err))
  109. (in-package :cl-postgres-error)
  110. (defparameter *error-table* (make-hash-table :test 'equal))
  111. (defmacro deferror (code typename &optional (superclass 'database-error))
  112. `(progn (define-condition ,typename (,superclass) ())
  113. (setf (gethash ,code *error-table*) ',typename)))
  114. ;; Connection Exceptions
  115. ;; https://www.postgresql.org/docs/current/errcodes-appendix.html
  116. (deferror "08000" connection-exception)
  117. (deferror "08003" connection-does-not-exist)
  118. (deferror "08006" connection-failure)
  119. (deferror "08001" sqlclient-unable-to-establish-sqlconnection)
  120. (deferror "08004" sqlserver-rejected-establishment-of-sqlconnection)
  121. (deferror "08007" transaction-resolution-unknown)
  122. (deferror "08P01" protocol-violation)
  123. (deferror "0A" feature-not-supported)
  124. (deferror "22" data-exception)
  125. (deferror "22012" db-division-by-zero data-exception)
  126. (deferror "22007" invalid-datetime-format data-exception)
  127. (deferror "22003" numeric-value-out-of-range data-exception)
  128. (deferror "22P01" floating-point-exception data-exception)
  129. (deferror "23" integrity-violation)
  130. (deferror "23001" restrict-violation integrity-violation)
  131. (deferror "23502" not-null-violation integrity-violation)
  132. (deferror "23503" foreign-key-violation integrity-violation)
  133. (deferror "23505" unique-violation integrity-violation)
  134. (deferror "23514" check-violation integrity-violation)
  135. (deferror "26000" invalid-sql-statement-name)
  136. (deferror "42" syntax-error-or-access-violation)
  137. (deferror "42501" insufficient-privilege syntax-error-or-access-violation)
  138. (deferror "40" transaction-rollback)
  139. (deferror "40001" serialization-failure transaction-rollback)
  140. (deferror "40002" transaction-integrity-constraint-violation transaction-rollback)
  141. (deferror "40003" statement-completion-unknown transaction-rollback)
  142. (deferror "40P01" deadlock-detected transaction-rollback)
  143. (deferror "42P01" undefined-table syntax-error-or-access-violation)
  144. (deferror "42601" columns-error syntax-error-or-access-violation)
  145. (deferror "42703" undefined-column syntax-error-or-access-violation)
  146. (deferror "42701" duplicate-column syntax-error-or-access-violation)
  147. (deferror "42P03" duplicate-cursor syntax-error-or-access-violation)
  148. (deferror "42P04" duplicate-database syntax-error-or-access-violation)
  149. (deferror "42723" duplicate-function syntax-error-or-access-violation)
  150. (deferror "42P05" duplicate-prepared-statement syntax-error-or-access-violation)
  151. (deferror "42P06" duplicate-schema syntax-error-or-access-violation)
  152. (deferror "42P07" duplicate-table syntax-error-or-access-violation)
  153. (deferror "42712" duplicate-alias syntax-error-or-access-violation)
  154. (deferror "42710" duplicate-object syntax-error-or-access-violation)
  155. (deferror "53" insufficient-resources)
  156. (deferror "54" program-limit-exceeded)
  157. (deferror "55" object-state-error)
  158. (deferror "55006" object-in-use object-state-error)
  159. (deferror "55P03" lock-not-available object-state-error)
  160. (deferror "57" operator-intervention)
  161. (deferror "57014" query-canceled operator-intervention)
  162. (define-condition server-shutdown (operator-intervention database-connection-error) ())
  163. (deferror "57P01" admin-shutdown server-shutdown)
  164. (deferror "57P02" crash-shutdown server-shutdown)
  165. (deferror "57P03" cannot-connect-now operator-intervention)
  166. (deferror "58" system-error)
  167. (deferror "XX" internal-error)
  168. (defun get-error-type (code)
  169. (or (gethash code *error-table*)
  170. (and code (gethash (subseq code 0 2) *error-table*))
  171. 'database-error))