marijnh / Postmodern

A Common Lisp PostgreSQL programming interface
http://marijnhaverbeke.nl/postmodern
Other
400 stars 90 forks source link

Added postgres specific s-sql to postmodern. #331

Closed peoplestom closed 10 months ago

peoplestom commented 11 months ago

I want to call analyze in my lisp code and I'd prefer to use s-sql as it keeps the name transformations etc consistent.

sabracrolleton commented 11 months ago

I think you want this in package postmodern, not cl-postgres. (See first line of your s-sql-extensions.lisp file). Also confused about the references to postgres specific. All of postmodern is postgresql specific.

peoplestom commented 11 months ago

Yeah I should have properly reviewed it prior to opening the PR, sorry. It should be good to go now.

sabracrolleton commented 11 months ago

Does something like this accomplish what you want?

(defun analyze-option (option)
  "Option must be a list of 1 or 2 members with the first member being a keyword of :buffer-usage-limit,
 :verbose or :skip-locked. The second member (if it exists) and the first member is :verbose or :skip-locked 
should be on of :on, :off, :true, :false, 0, 1,t, nil, \"true\" or \"false\". The check for the string 
true or false is case insensitive. If the first member is :buffers-usage-limit, the second member should 
be either a number or a string containing a number and a size parameter (e.g. kb, MB, GB, TB). 
See https://www.postgresql.org/docs/current/sql-analyze.html for details."
  (destructuring-bind (name value)
      (if (= (length option) 2)
          option
        (list (car option) "TRUE"))
    (append
     (list (string-upcase (to-sql-name name nil t)))
     (list " ")
     (list (format nil "~:@(~a~)"
                   (cond  ((and (eq name :buffer-usage-limit)
                                (or (numberp value)
                                    (parse-integer value :junk-allowed t)))
                           (format nil "~a" value))
                          ((and (or (eq name :verbose)
                                    (eq name :skip-locked))
                                (or (eq value :off)
                                    (eq value :false)
                                    (eq value 0)
                                    (eq value nil)
                                    (equalp value "false")
                                    (equalp value "off")))
                           "FALSE")
                          ((and (or (eq name :verbose)
                                    (eq name :skip-locked))
                                (or (eq value :on)
                                    (eq value :true)
                                    (eq value 1)
                                    (eq value t)
                                    (equalp value "true")
                                    (equalp value "on")))
                           "TRUE")
                          (t  (error (format nil "Invalid Analyze Option name ~a value ~a  type ~a type-v ~a"
                                             name value (type-of name) (type-of value))))))))))

(defun sql-expand-analyze-option (elts &optional (sep ", "))
  "Expand a list of elements, adding a separator between them."
  (setf elts elts)
  (loop :for (elt . rest) :on elts
        :append  (analyze-option elt)
        :if rest :collect sep))

(def-sql-op :analyze (&rest args)
            (split-on-keywords ( (verbose - ?)(option * ?)(tables * ?))
                               args
                               `("ANALYZE "
                                 ,@(when verbose '("VERBOSE "))
                                 ,@(when option
                                     (append '("(")
                                             (sql-expand-analyze-option option)
                                             '(") ")))
                                 ,@(sql-expand-list tables))))
;;; Tests
(is (equal (sql (:analyze :option (:verbose t)  :tables (:t1 't1c1 't1c2) (:t2 't2c1 't2c)))
           "ANALYZE (VERBOSE TRUE) t1(t1c1, t1c2), t2(t2c1, t2c)"))
(is (equal (sql (:analyze :option (:verbose 1) (:skip-locked 0) (:buffer-usage-limit "70MB")  :tables (:t1 't1c1 't1c2) (:t2 't2c1 't2c)))
           "ANALYZE (VERBOSE TRUE, SKIP_LOCKED FALSE, BUFFER_USAGE_LIMIT 70MB) t1(t1c1, t1c2), t2(t2c1, t2c)"))
(is (equal (sql (:analyze :option (:verbose :off) (:skip-locked :on) (:buffer-usage-limit "70MB")  :tables (:t1 't1c1 't1c2) (:t2 't2c1 't2c)))
           "ANALYZE (VERBOSE FALSE, SKIP_LOCKED TRUE, BUFFER_USAGE_LIMIT 70MB) t1(t1c1, t1c2), t2(t2c1, t2c)"))
(is (equal (sql (:analyze :verbose :tables (:t1 't1c1 't1c2) (:t2 't2c1 't2c2 't2c3)))
           "ANALYZE VERBOSE t1(t1c1, t1c2), t2(t2c1, t2c2, t2c3)"))

(is (equal (sql (:analyze :verbose))
           "ANALYZE VERBOSE "))
(s (equal (sql (:analyze))
          "ANALYZE "))

(is (equal (sql (:analyze :option (:verbose t) (:skip-locked nil) (:buffer-usage-limit "70MB")  :tables (:t1 't1c1 't1c2) (:t2 't2c1 't2c)))
           "ANALYZE (VERBOSE TRUE, SKIP_LOCKED FALSE, BUFFER_USAGE_LIMIT 70MB) t1(t1c1, t1c2), t2(t2c1, t2c)"))

(is (equal (sql (:analyze :option (:verbose t) (:skip-locked nil) (:buffer-usage-limit "70MB")  :tables (:t1 't1c1 't1c2)))
           "ANALYZE (VERBOSE TRUE, SKIP_LOCKED FALSE, BUFFER_USAGE_LIMIT 70MB) t1(t1c1, t1c2)"))
(is (equal (sql (:analyze :option (:verbose t) (:skip-locked nil) (:buffer-usage-limit "70MB")  :tables (:t1 't1c1 't1c2)))
           "ANALYZE (VERBOSE TRUE, SKIP_LOCKED FALSE, BUFFER_USAGE_LIMIT 70MB) t1(t1c1, t1c2)"))
peoplestom commented 11 months ago

Yeah that works for me. Thanks for the help, I was stuck on how to support both the old verbose and the new options. All I changed was to ignore :verbose if we got :options.