marijnh / Postmodern

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

Added postgres specific s-sql to postmodern. #331

Closed peoplestom closed 6 months ago

peoplestom commented 6 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 6 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 6 months ago

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

sabracrolleton commented 6 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 6 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.