Насколько я могу судить, вы пытаетесь сгенерировать в Лиспе некоторую строку, содержащую действительный SQL, которая сама содержит строку литерала SQL.
Прежде всего, это экземпляр антипаттерна, который я называю «язык в строке». Я продолжаю думать, что у меня есть большая диатриба об этом, чтобы указывать людям, но, похоже, у меня нет. Достаточно сказать, что это своего рода антитеза тому, что люди Lisp пытались достичь в течение более 60 лет, и именно поэтому у нас есть атаки с использованием SQL-инъекций и много других дерьмов, которые поражают нас. Но эта битва давно проиграна, и все, что мы можем сделать, - это попытаться не утонуть в море грязи и гниющих кусочков людей, которые теперь засоряют поле битвы.
Итак, для этого нужно уметь делать две вещи.
- Вы должны быть в состоянии генерировать литеральную строку SQL из последовательности символов (или из строки). Это означает, что вам нужно знать синтаксис литеральных строк SQL и, в частности, какие символы в них допустимы и как вы выражаете символы, которые не являются допустимыми.
- Вы должны иметь возможность интерполировать эту строку в строку CL.
Второе из них тривиально: это то, что делает директива format
~A
. Или, если вы хотите получить фантазию, вы можете использовать cl-interpol .
Во-первых, я не знаю синтаксиса строковых литералов SQL, но приведу пример, который предполагает следующие простые правила:
- буквенные строки разделяются
"
символами;
- символ
\
экранирует следующий символ, чтобы удалить его из какого-либо специального значения;
- разрешены все остальные символы (это почти наверняка неверно).
Ну, есть много способов сделать это, каждый из которых включает в себя прогулку по последовательности символов в поисках тех, кого нужно убежать. Вот что-то довольно ужасное и быстрое, что я написал. Ему нужен макрос с именем nlet
, который является конструкцией схемы с именем let, и он предполагает использование TRO в реализации (если ваша реализация этого не делает, получите тот, который делает).
(defmacro nlet (name bindings &body forms)
"named let"
(multiple-value-bind (vars vals) (values (mapcar (lambda (b)
(etypecase b
(symbol b)
(cons (first b))))
bindings)
(mapcar (lambda (b)
(etypecase b
(symbol 'nil)
(cons
(unless (null (cddr b))
(error "bad binding ~A" b))
(second b))))
bindings))
`(labels ((,name ,vars ,@forms))
(,name ,@vals))))
(defun ->sql-string (seq)
;; turn SEQ (a sequence of characters) into a string representing an
;; SQL literal string (perhaps)
(nlet loupe ((tail (coerce seq 'list))
(accum '()))
(if (null tail)
(coerce (cons #\" (nreverse (cons #\" accum))) 'string)
(destructuring-bind (first . rest) tail
(loupe rest
(case first
((#\\ #\")
(append (list first #\\) accum))
(otherwise
(cons first accum))))))))
Так что теперь:
> (->sql-string "foo")
"\"foo\""
> (->sql-string '(#\f #\\ #\o #\" #\o))
"\"f\\\\o\\\"o\""
Это делает уродливым принтер Lisp, но (см. Выше) мы можем видеть, какие строки на самом деле:
> (format t "~&select x from y where x.y = ~A;~%"
(->sql-string '(#\f #\\ #\o #\" #\o)))
select x from y where x.y = "f\\o\"o";
nil
И вы можете видеть, что литеральная строка SQL подчиняется правилам, которые я изложил выше.
Перед использованием чего-либо подобного проверьте, каковы правила , потому что, если вы их неправильно поняли, вы, возможно, открыты для атак с использованием SQL-инъекций.