branch: elpa/emacsql commit 327b09b4b99ccb6b5605b804027a42fd73589929 Author: Christopher Wellons <well...@nullprogram.com> Commit: Christopher Wellons <well...@nullprogram.com>
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])] ()