branch: elpa/emacsql
commit 327b09b4b99ccb6b5605b804027a42fd73589929
Author: Christopher Wellons <[email protected]>
Commit: Christopher Wellons <[email protected]>
Add support for raw strings and raw parameters (#26, #28).
Strings quoted with ' are not printed when used. A new "r" parameter
type was also introduced to include unprinted string values in
queries.
---
README.md | 14 ++++++++++++++
emacsql-compiler.el | 25 +++++++++++++++++++------
tests/emacsql-compiler-tests.el | 14 ++++++++++++++
3 files changed, 47 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index c76ed67f1d..2edb978142 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,17 @@ does not "escape" `$tn` parameter symbols.
(emacsql db [... :where (= category 'hiking)])
```
+Quoting a string makes EmacSQL handle it as a "raw string." These raw
+strings are not printed when being assembled into a query. These are
+intended for use in special circumstances like filenames (`ATTACH`) or
+pattern matching (`LIKE`). It is vital that raw strings are not
+returned as results.
+
+```el
+(emacsql db [... :where (like name '"%foo%")])
+(emacsql db [:attach '"/path/to/foo.db" :as foo])
+```
+
Since template parameters include their type they never need to be
quoted.
@@ -261,6 +272,8 @@ one-indexed.
```el
(emacsql db [:select * :from $i1 :where (> salary $s2)] 'employees 50000)
+
+(emacsql db [:select * :from employees :where (like name $r1)] "%Smith%")
```
The letter before the number is the type.
@@ -268,6 +281,7 @@ The letter before the number is the type.
* `i` : identifier
* `s` : scalar
* `v` : vector (or multiple vectors)
+ * `r` : raw, unprinted strings
* `S` : schema
When combined with `:values`, the vector type can refer to lists of
diff --git a/emacsql-compiler.el b/emacsql-compiler.el
index 2f5b596f4e..3267d18118 100644
--- a/emacsql-compiler.el
+++ b/emacsql-compiler.el
@@ -83,6 +83,12 @@
((numberp value) (prin1-to-string value))
((emacsql-quote-scalar (prin1-to-string value))))))
+(defun emacsql-escape-raw (value)
+ "Escape VALUE for sending to SQLite."
+ (cond ((null value) "NULL")
+ ((stringp value) (emacsql-quote-scalar value))
+ ((error "Expected string or nil"))))
+
(defun emacsql-escape-vector (vector)
"Encode VECTOR into a SQL vector scalar."
(cl-typecase vector
@@ -174,28 +180,30 @@
"Return the index and type of THING, or nil if THING is not a parameter.
A parameter is a symbol that looks like $i1, $s2, $v3, etc. The
letter refers to the type: identifier (i), scalar (s),
-vector (v), schema (S)."
+vector (v), raw string (r), schema (S)."
(when (symbolp thing)
(let ((name (symbol-name thing)))
- (when (string-match-p "^\\$[isvS][0-9]+$" name)
+ (when (string-match-p "^\\$[isvrS][0-9]+$" name)
(cons (1- (read (substring name 2)))
(cl-ecase (aref name 1)
(?i :identifier)
(?s :scalar)
(?v :vector)
+ (?r :raw)
(?S :schema)))))))
(defmacro emacsql-with-params (prefix &rest body)
"Evaluate BODY, collecting parameters.
-Provided local functions: `param', `identifier', `scalar',
-`svector', `expr', `subsql', and `combine'. BODY should return a string,
-which will be combined with variable definitions."
+Provided local functions: `param', `identifier', `scalar', `raw',
+`svector', `expr', `subsql', and `combine'. BODY should return a
+string, which will be combined with variable definitions."
(declare (indent 1))
`(let ((emacsql--vars ()))
(cl-flet* ((combine (prepared) (emacsql--*combine prepared))
(param (thing) (emacsql--!param thing))
(identifier (thing) (emacsql--!param thing :identifier))
(scalar (thing) (emacsql--!param thing :scalar))
+ (raw (thing) (emacsql--!param thing :raw))
(svector (thing) (combine (emacsql--*vector thing)))
(expr (thing) (combine (emacsql--*expr thing)))
(subsql (thing)
@@ -218,6 +226,7 @@ Only use within `emacsql-with-params'!"
(:identifier (emacsql-escape-identifier thing))
(:scalar (emacsql-escape-scalar thing))
(:vector (emacsql-escape-vector thing))
+ (:raw (emacsql-escape-raw thing))
(:schema (emacsql-prepare-schema thing)))
(if (and (not (null thing))
(not (keywordp thing))
@@ -275,7 +284,10 @@ Only use within `emacsql-with-params'!"
((asc desc)
(format "%s %s" (recur 0) (upcase (symbol-name op))))
;; Special case quote
- ((quote) (scalar (nth 0 args)))
+ ((quote) (let ((arg (nth 0 args)))
+ (if (stringp arg)
+ (raw arg)
+ (scalar arg))))
;; Special case funcall
((funcall)
(format "%s(%s)" (recur 0)
@@ -365,6 +377,7 @@ Only use within `emacsql-with-params'!"
(:identifier (emacsql-escape-identifier thing))
(:scalar (emacsql-escape-scalar thing))
(:vector (emacsql-escape-vector thing))
+ (:raw (emacsql-escape-raw thing))
(:schema (emacsql-prepare-schema thing))
(otherwise
(emacsql-error "Invalid var type %S" kind))))))))
diff --git a/tests/emacsql-compiler-tests.el b/tests/emacsql-compiler-tests.el
index 5297245f3c..bed49c8889 100644
--- a/tests/emacsql-compiler-tests.el
+++ b/tests/emacsql-compiler-tests.el
@@ -33,6 +33,12 @@
(should (string= (emacsql-escape-vector '([1 2 3] [4 5 6]))
"(1, 2, 3), (4, 5, 6)")))
+(ert-deftest emacsql-escape-raw ()
+ (should (string= (emacsql-escape-raw "/var/emacsql") "'/var/emacsql'"))
+ (should (string= (emacsql-escape-raw "a b c") "'a b c'"))
+ (should (string= (emacsql-escape-raw "a 'b' c") "'a ''b'' c'"))
+ (should (string= (emacsql-escape-raw nil) "NULL")))
+
(ert-deftest emacsql-schema ()
(should (string= (emacsql-prepare-schema [a]) "a &NONE"))
(should (string= (emacsql-prepare-schema [a b c])
@@ -55,6 +61,7 @@
(should (equal (emacsql-param '$1) nil))
(should (equal (emacsql-param '$s5) '(4 . :scalar)))
(should (equal (emacsql-param '$v10) '(9 . :vector)))
+ (should (equal (emacsql-param '$r2) '(1 . :raw)))
(should (equal (emacsql-param '$a) nil))
(should (equal (emacsql-param '$i10) '(9 . :identifier))))
@@ -86,6 +93,13 @@
([:select p:name :from [(as [:select * :from people] p)]] '()
"SELECT p.name FROM (SELECT * FROM people) AS p;")))
+(ert-deftest emacsql-attach ()
+ (emacsql-tests-with-queries
+ ([:attach $r1 :as $i2] '("/var/foo.db" foo)
+ "ATTACH '/var/foo.db' AS foo;")
+ ([:detach $i1] '(foo)
+ "DETACH foo;")))
+
(ert-deftest emacsql-create-table ()
(emacsql-tests-with-queries
([:create-table foo ([a b c])] ()