emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/emacsql 896bb7ce52 035/427: Start adding SQL expanders.


From: ELPA Syncer
Subject: [nongnu] elpa/emacsql 896bb7ce52 035/427: Start adding SQL expanders.
Date: Tue, 13 Dec 2022 02:59:25 -0500 (EST)

branch: elpa/emacsql
commit 896bb7ce52c25b2253963579787102a8cc3ff9da
Author: Christopher Wellons <wellons@nullprogram.com>
Commit: Christopher Wellons <wellons@nullprogram.com>

    Start adding SQL expanders.
---
 emacsql.el | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)

diff --git a/emacsql.el b/emacsql.el
index 9badf8cd6e..07a2b97901 100644
--- a/emacsql.el
+++ b/emacsql.el
@@ -292,6 +292,91 @@ Each row must be a sequence of values to store into TABLE.
   (emacsql--check-error conn)
   (emacsql--parse conn))
 
+;; SQL Expansion Functions
+
+(defvar emacsql-expanders ()
+  "Alist of all expansion functions.")
+
+(defun emacsql-add-expander (keyword function)
+  "Register FUNCTION for KEYWORD as a SQL expander.
+FUNCTION should accept a single argument, the keyword's argument,
+and should return a list of (<string> [arg-pos] ...)."
+  (push (cons keyword function) emacsql-expanders)
+  :keyword)
+
+(defmacro emacsql-defexpander (keyword args &rest body)
+  "Define an expander for KEYWORD."
+  (declare (indent 2))
+  `(emacsql-add-expander ,keyword (lambda ,args ,@body)))
+
+(defun emacsql-expand (sql)
+  "Expand SQL into a SQL-consumable string, with variables."
+  (loop for (keyword argument) on (cl-coerce sql 'list) by #'cddr
+        for expander = (cdr (assoc keyword emacsql-expanders))
+        when expander collect (funcall expander argument) into parts
+        else do (error "Unrecognized keyword %s" keyword)
+        finally (return (cons (concat (mapconcat #'car parts " ") ";")
+                              (apply #'nconc (mapcar #'cdr parts))))))
+
+(defun emacsql-format (expansion &rest args)
+  "Fill in the variables EXPANSION with ARGS."
+  (cl-destructuring-bind (format . vars) expansion
+    (apply #'format format
+           (cl-loop for (i . kind) in vars collect
+                    (cl-ecase kind
+                      (:identifier (emacsql-escape (nth i args)))
+                      (:value (emacsql-escape-value (nth i args))))))))
+
+(defun emacsql-var (var)
+  "Return the index number of VAR, or nil if VAR is not a variable.
+A variable is a symbol that looks like $1, $2, $3, etc. A $ means $1."
+  (when (symbolp var)
+    (let ((name (symbol-name var)))
+      (when (eql (aref name 0) ?$)
+        (if (> (length name) 1)
+            (1- (read (substring name 1)))
+          0)))))
+
+(defun emacsql-escape-format (thing &optional kind)
+  "Escape THING for use as a `format' spec, pre-escaping for KIND.
+KIND should be :value or :identifier."
+  (replace-regexp-in-string
+   "%" "%%" (case kind
+              (:value (emacsql-escape-value thing))
+              (:identifier (emacsql-escape thing))
+              (otherwise thing))))
+
+(emacsql-defexpander :select (arg)
+  "Expands to the SELECT keyword."
+  (let ((vars ()))
+    (cons
+     (concat
+      "SELECT "
+      (cond
+       ((eq '* arg) "*")
+       ((emacsql-var arg)
+        (push (cons (emacsql-var arg) :identifier) vars)
+        "%s")
+       ((symbolp arg)
+        (emacsql-escape-format arg :identifier))
+       ((vectorp arg)
+        (mapconcat
+         #'identity
+         (cl-loop for name elements of arg
+                  when (emacsql-var name)
+                  collect (prog1 "%s" (push (cons it :identifier) vars))
+                  else when (symbolp name)
+                  collect (emacsql-escape-format name :identifier)
+                  else do (error "Unknown format %S" name))
+         ", "))))
+     (nreverse vars))))
+
+(emacsql-defexpander :from (table)
+  "Expands to the FROM keyword."
+  (if (emacsql-var table)
+      (list "FROM %s" (cons (emacsql-var table) :identifier))
+    (list (concat "FROM " (emacsql-escape-format table :identifier)))))
+
 (provide 'emacsql)
 
 ;;; emacsql.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]