From 44cd3f817f6f59ffe495db4cf2b9ea4637a57f75 Mon Sep 17 00:00:00 2001 From: "Kevin M. Rosenberg" Date: Sun, 30 May 2004 06:22:22 +0000 Subject: [PATCH] r9519: add prepared statements for postgresql --- ChangeLog | 13 +++++ clsql-tests.asd | 3 +- db-db2/db2-api.lisp | 45 ++++++++------- db-db2/db2-constants.lisp | 4 ++ db-db2/db2-sql.lisp | 52 +++++++++++++++++ db-mysql/mysql-sql.lisp | 4 ++ db-odbc/odbc-api.lisp | 2 +- db-odbc/odbc-constants.lisp | 3 +- db-postgresql/postgresql-sql.lisp | 3 + sql/db-interface.lisp | 38 +++++++++++++ sql/fdml.lisp | 45 +++++++++++---- sql/generic-odbc.lisp | 2 - sql/generic-postgresql.lisp | 94 ++++++++++++++++++++++++++++++- sql/package.lisp | 13 ++++- sql/transaction.lisp | 12 ++-- tests/test-basic.lisp | 10 ++++ tests/test-init.lisp | 5 +- tests/test-internal.lisp | 44 +++++++++++++++ 18 files changed, 341 insertions(+), 51 deletions(-) create mode 100644 tests/test-internal.lisp diff --git a/ChangeLog b/ChangeLog index 1850cc4..711551d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +29 May 2004 Kevin Rosenberg + * sql/package.lisp: Add API for prepared statments. + * sql/fdml.lisp: Change implicity flatp processing + for string map-query for greater CommonSQL conformance. + Add high-high API for prepared statements. Not complete. + * tests/test-basic.lisp: Add test for map-query and + single argument. + * sql/transactions.lisp: Change name/semantics of + autocommit to set-autocommit. + * sql/generic-postgresql.lisp: Add support for + prepared statements. + * tests/test-internal.lisp: New file + 27 May 2004 Kevin Rosenberg * Version 2.11.3 * sql/ooddl.lisp: Commit patch from Edi Weitz fixing return diff --git a/clsql-tests.asd b/clsql-tests.asd index 13a4c07..9d4df2f 100644 --- a/clsql-tests.asd +++ b/clsql-tests.asd @@ -33,8 +33,9 @@ (:file "utils") (:file "test-init") (:file "benchmarks") - (:file "test-time") + (:file "test-internal") (:file "test-basic") + (:file "test-time") (:file "test-connection") (:file "test-fddl") (:file "test-fdml") diff --git a/db-db2/db2-api.lisp b/db-db2/db2-api.lisp index 99e7245..880f360 100644 --- a/db-db2/db2-api.lisp +++ b/db-db2/db2-api.lisp @@ -52,32 +52,21 @@ :returning ,c-return))) (defun ,lisp-cli-fn (,@ll &key database nulls-ok) (let ((result (funcall %lisp-cli-fn ,@ll))) - #+ignore (case result - (#.+oci-success+ - +oci-success+) - (#.+oci-error+ - (handle-oci-error :database database :nulls-ok nulls-ok)) - (#.+oci-no-data+ - (error 'sql-database-error :message "OCI No Data Found")) - (#.+oci-success-with-info+ - (error 'sql-database-error :message "internal error: unexpected +oci-success-with-info")) - (#.+oci-no-data+ - (error 'sql-database-error :message "OCI No Data")) - (#.+oci-invalid-handle+ - (error 'sql-database-error :message "OCI Invalid Handle")) - (#.+oci-need-data+ - (error 'sql-database-error :message "OCI Need Data")) - (#.+oci-still-executing+ - (error 'sql-temporary-error :message "OCI Still Executing")) - (#.+oci-continue+ - (error 'sql-database-error :message "OCI Continue")) - (1804 - (error 'sql-database-error :message "Check CLI_HOME and NLS settings.")) + (#.SQL_SUCCESS + SQL_SUCCESS) + (#.SQL_SUCCESS_WITH_INFO + (format *standard-output* "sucess with info") + SQL_SUCCESS) + (#.SQL_ERROR + (error 'sql-database-error + :error-id result + :message + (format nil "DB2 error" result))) (t (error 'sql-database-error :message - (format nil "OCI unknown error, code=~A" result))))))))) + (format nil "DB2 unknown error, code=~A" result))))))))) (defmacro def-raw-cli-routine @@ -90,12 +79,22 @@ (funcall %lisp-cli-fn ,@ll))))) -(def-cli-routine ("SQLAllocHandle" sql-allocate-handle) +(def-cli-routine ("SQLAllocHandle" sql-alloc-handle) :int (fHandleType cli-smallint) (hInput cli-handle) (phOuput (* cli-handle))) +(def-cli-routine ("SQLConnect" sql-connect) + :int + (hDb cli-handle) + (server :cstring) + (server-len cli-smallint) + (user :cstring) + (user-len cli-smallint) + (password :cstring) + (passwd-len cli-smallint)) + ;;; CLI Functions needed ;;; SQLBindParameter diff --git a/db-db2/db2-constants.lisp b/db-db2/db2-constants.lisp index 01007a6..fad5a38 100644 --- a/db-db2/db2-constants.lisp +++ b/db-db2/db2-constants.lisp @@ -22,3 +22,7 @@ (defconstant SQL_HANDLE_STMT 3) (defconstant SQL_NTS -3) +(defconstant SQL_ERROR -1) +(defconstant SQL_SUCCESS 0) +(defconstant SQL_SUCCESS_WITH_INFO 1) + diff --git a/db-db2/db2-sql.lisp b/db-db2/db2-sql.lisp index 788d491..16aa216 100644 --- a/db-db2/db2-sql.lisp +++ b/db-db2/db2-sql.lisp @@ -18,3 +18,55 @@ (defmethod database-initialize-database-type ((database-type (eql :db2))) t) +(defclass db2-database (database) + ((henv :initform nil :allocation :class :initarg :henv :accessor henv) + (hdbc :initform nil :initarg :hdbc :accessor hdbc))) + + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :db2))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (dsn user password) connection-spec + (declare (ignore password)) + (concatenate 'string dsn "/" user))) + +(defmethod database-connect (connection-spec (database-type (eql :db2))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (server user password) connection-spec + (handler-case + (let ((db (make-instance 'db2-database + :name (database-name-from-spec connection-spec :db2) + :database-type :db2))) + (db2-connect db server user password) + db) + (error () ;; Init or Connect failed + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :message "Connection failed"))))) + + +;; API Functions + +(uffi:def-type handle-type cli-handle) +(uffi:def-type handle-ptr-type (* cli-handle)) + +(defmacro deref-vp (foreign-object) + `(the handle-type (uffi:deref-pointer (the handle-ptr-type ,foreign-object) cli-handle))) + +(defun db2-connect (db server user password) + (let ((henv (uffi:allocate-foreign-object cli-handle)) + (hdbc (uffi:allocate-foreign-object cli-handle))) + (sql-alloc-handle SQL_HANDLE_ENV SQL_NULL_HANDLE henv) + (setf (slot-value db 'henv) henv) + (setf (slot-value db 'hdbc) hdbc) + + (sql-alloc-handle SQL_HANDLE_DBC (deref-vp henv) hdbc) + (uffi:with-cstrings ((native-server server) + (native-user user) + (native-password password)) + (sql-connect (deref-vp hdbc) + native-server SQL_NTS + native-user SQL_NTS + native-password SQL_NTS))) + db) diff --git a/db-mysql/mysql-sql.lisp b/db-mysql/mysql-sql.lisp index 513c9ed..bb4940e 100644 --- a/db-mysql/mysql-sql.lisp +++ b/db-mysql/mysql-sql.lisp @@ -459,6 +459,10 @@ (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil)))) (and tuple (string-equal "YES" (second tuple))))) +(defmethod db-type-has-prepared-stmt? ((db-type (eql :mysql))) + #+mysql-client-v4.1 t + #-mysql-client-v4.1 nil) + (when (clsql-sys:database-type-library-loaded :mysql) (clsql-sys:initialize-database-type :database-type :mysql)) diff --git a/db-odbc/odbc-api.lisp b/db-odbc/odbc-api.lisp index b17af02..ab2f6ee 100644 --- a/db-odbc/odbc-api.lisp +++ b/db-odbc/odbc-api.lisp @@ -153,7 +153,7 @@ as possible second argument) to the desired representation of date/time/timestam () (SQLAllocEnv phenv) (deref-pointer phenv 'sql-handle))))) - (%set-attr-odbc-version henv $SQL_OV_ODBC2) + (%set-attr-odbc-version henv $SQL_OV_ODBC3) henv)) diff --git a/db-odbc/odbc-constants.lisp b/db-odbc/odbc-constants.lisp index e967729..c16e2a7 100644 --- a/db-odbc/odbc-constants.lisp +++ b/db-odbc/odbc-constants.lisp @@ -22,7 +22,7 @@ (defconstant $ODBC-LONG-TYPE :int) (defconstant $ODBC-ULONG-TYPE :unsigned-int) -(defconstant $ODBCVER #x0210) +;; (defconstant $ODBCVER #x0210) ;; generally useful constants (defconstant $SQL_SPEC_MAJOR 2) ;; Major version of specification @@ -802,7 +802,6 @@ (defconstant $SQL_POSITION 0) ;; 1.0 FALSE (defconstant $SQL_REFRESH 1) ;; 1.0 TRUE -; #if (ODBCVER >= #x0200)) (defconstant $SQL_UPDATE 2) (defconstant $SQL_DELETE 3) (defconstant $SQL_ADD 4) diff --git a/db-postgresql/postgresql-sql.lisp b/db-postgresql/postgresql-sql.lisp index d93b153..16d5d2f 100644 --- a/db-postgresql/postgresql-sql.lisp +++ b/db-postgresql/postgresql-sql.lisp @@ -158,6 +158,9 @@ :message (tidy-error-message (PQerrorMessage conn-ptr)))) (unwind-protect (case (PQresultStatus result) + ;; User gave a command rather than a query + (#.pgsql-exec-status-type#command-ok + nil) (#.pgsql-exec-status-type#empty-query nil) (#.pgsql-exec-status-type#tuples-ok diff --git a/sql/db-interface.lisp b/sql/db-interface.lisp index 939af1f..f48d00d 100644 --- a/sql/db-interface.lisp +++ b/sql/db-interface.lisp @@ -307,6 +307,12 @@ of TYPE_NAME (keyword) PRECISION SCALE NULLABLE.") t) (:documentation "T [default] if database can supports transactions.")) +(defgeneric db-type-has-prepared-stmt? (db-type) + (:method ((db-type t)) + nil) + (:documentation "T if database backend supports prepared statements.")) + + ;;; Large objects support (Marc Battyani) (defgeneric database-create-large-object (database) @@ -321,6 +327,38 @@ of TYPE_NAME (keyword) PRECISION SCALE NULLABLE.") (defgeneric database-delete-large-object (object-id database) (:documentation "Deletes the large object in the database")) +;; Prepared statements + +(defgeneric database-prepare (stmt types database result-types field-names) + (:method (stmt types (database t)) + (declare (ignore stmt types)) + (signal-no-database-error database)) + (:method (stmt types (database database)) + (declare (ignore stmt types)) + (error 'sql-database-error + :message + (format nil "DATABASE-PREPARE not implemented for ~S" database))) + (:documentation "Prepare a statement for later execution.")) + +(defgeneric database-bind-parameter (prepared-stmt position value) + (:method ((pstmt t) position value) + (declare (ignore position value)) + (error 'sql-database-error + :message + (format nil "database-bind-paremeter not implemented for ~S" pstmt))) + (:documentation "Bind a parameter for a prepared statement.")) + +(defgeneric database-run-prepared (prepared-stmt) + (:method ((pstmt t)) + (error 'sql-database-error + :message (format nil "database-run-prepared not specialized for ~S" pstmt))) + (:documentation "Execute a prepared statement.")) + +(defgeneric database-free-prepared (prepared-stmt) + (:method ((pstmt t)) + ;; nothing to do by default + nil) + (:documentation "Free the resources of a prepared statement.")) ;; Checks for closed database diff --git a/sql/fdml.lisp b/sql/fdml.lisp index 283f2dd..50ab024 100644 --- a/sql/fdml.lisp +++ b/sql/fdml.lisp @@ -415,10 +415,8 @@ computed for each field." (database-query-result-set query-expression database :full-set nil :result-types result-types) (let ((flatp (and (= columns 1) - (typecase query-expression - (string t) - (sql-query - (slot-value query-expression 'flatp)))))) + (typep query-expression 'sql-query) + (slot-value query-expression 'flatp)))) (when result-set (unwind-protect (do ((row (make-list columns))) @@ -434,10 +432,8 @@ computed for each field." (database-query-result-set query-expression database :full-set nil :result-types result-types) (let ((flatp (and (= columns 1) - (typecase query-expression - (string t) - (sql-query - (slot-value query-expression 'flatp)))))) + (typep query-expression 'sql-query) + (slot-value query-expression 'flatp)))) (when result-set (unwind-protect (let ((result (list nil))) @@ -456,10 +452,8 @@ computed for each field." (database-query-result-set query-expression database :full-set t :result-types result-types) (let ((flatp (and (= columns 1) - (typecase query-expression - (string t) - (sql-query - (slot-value query-expression 'flatp)))))) + (typep query-expression 'sql-query) + (slot-value query-expression 'flatp)))) (when result-set (unwind-protect (if rows @@ -583,3 +577,30 @@ computed for each field." (database-delete-large-object object-id database)) +;;; Prepared statements + +(defun prepare-sql (sql-stmt types &key (database *default-database*) (result-types :auto) field-names) + "Prepares a SQL statement for execution. TYPES contains a +list of UFFI primitive types corresponding to the input parameters. Returns a +prepared-statement object." + (unless (db-type-has-prepared-stmt? (database-type database)) + (error 'sql-user-error + :message + (format nil + "Database backend type ~:@(~A~) does not support prepared statements." + (database-type database)))) + + (database-prepare sql-stmt types database result-types field-names)) + +(defun bind-parameter (prepared-stmt position value) + "Sets the value of a parameter is in prepared statement." + (database-bind-parameter prepared-stmt position value) + value) + +(defun run-prepared-sql (prepared-stmt) + "Execute the prepared sql statment. All input parameters must be bound." + (database-run-prepared prepared-stmt)) + +(defun free-prepared-sql (prepared-stmt) + "Delete the objects associated with a prepared statement." + (database-free-prepared prepared-stmt)) diff --git a/sql/generic-odbc.lisp b/sql/generic-odbc.lisp index b83e8ca..d222550 100644 --- a/sql/generic-odbc.lisp +++ b/sql/generic-odbc.lisp @@ -178,8 +178,6 @@ (loop for row in rows collect (fourth row)))) - - (defmethod database-attribute-type ((attribute string) (table string) (database generic-odbc-database) &key (owner nil)) (declare (ignore owner)) diff --git a/sql/generic-postgresql.lisp b/sql/generic-postgresql.lisp index 55d3a90..b85da75 100644 --- a/sql/generic-postgresql.lisp +++ b/sql/generic-postgresql.lisp @@ -213,8 +213,92 @@ (sql-escape (string-downcase table))) database :auto nil)) - -;; Capabilities +;;; Prepared statements + +(defvar *next-prepared-id-num* 0) +(defun next-prepared-id () + (let ((num (incf *next-prepared-id-num*))) + (format nil "CLSQL_PS_~D" num))) + +(defclass postgresql-stmt () + ((database :initarg :database :reader database) + (id :initarg :id :reader id) + (bindings :initarg :bindings :reader bindings) + (field-names :initarg :field-names :accessor stmt-field-names) + (result-types :initarg :result-types :reader result-types))) + +(defun clsql-type->postgresql-type (type) + (case type + (:string "VARCHAR") + ((:int :integer) "INT4") + (:short "INT2") + ((:number :numeric :float) "NUMERIC") + (:bigint "INT8"))) + +(defun prepared-sql-to-postgresql-sql (sql) + ;; FIXME: Convert #\? to "$n". Don't convert within strings + (declare (simple-string sql)) + (with-output-to-string (out) + (do ((len (length sql)) + (param 0) + (in-str nil) + (pos 0 (1+ pos))) + ((= len pos)) + (declare (fixnum len param pos)) + (let ((c (schar sql pos))) + (declare (character c)) + (cond + ((or (char= c #\") (char= c #\')) + (setq in-str (not in-str)) + (write-char c out)) + ((and (char= c #\?) (not in-str)) + (write-char #\$ out) + (write-string (write-to-string (incf param)) out)) + (t + (write-char c out))))))) + +(defmethod database-prepare (sql-stmt types (database generic-postgresql-database) result-types field-names) + (let ((id (next-prepared-id))) + (database-execute-command + (format nil "PREPARE ~A (~{~A~^,~}) AS ~A" + id + (mapcar #'clsql-type->postgresql-type types) + (prepared-sql-to-postgresql-sql sql-stmt)) + database) + (make-instance 'postgresql-stmt + :id id + :database database + :result-types result-types + :field-names field-names + :bindings (make-list (length types))))) + +(defmethod database-bind-parameter ((stmt postgresql-stmt) position value) + (setf (nth (1- position) (bindings stmt)) value)) + +(defun binding-to-param (binding) + (typecase binding + (string + (concatenate 'string "'" (sql-escape-quotes binding) "'")) + (t + binding))) + +(defmethod database-run-prepared ((stmt postgresql-stmt)) + (with-slots (database id bindings field-names result-types) stmt + (let ((query (format nil "EXECUTE ~A (~{~A~^,~})" + id (mapcar #'binding-to-param bindings)))) + (cond + ((and field-names (not (consp field-names))) + (multiple-value-bind (res names) + (database-query query database result-types field-names) + (setf field-names names) + (values res names))) + (field-names + (values (nth-value 0 (database-query query database result-types nil)) + field-names)) + (t + (database-query query database result-types field-names)))))) + +;;; Capabilities (defmethod db-type-has-fancy-math? ((db-type (eql :postgresql))) t) @@ -222,3 +306,9 @@ (defmethod db-type-default-case ((db-type (eql :postgresql))) :lower) +(defmethod db-type-has-prepared-stmt? ((db-type (eql :postgresql))) + t) + +(defmethod db-type-has-prepared-stmt? ((db-type (eql :postgresql-socket))) + t) + diff --git a/sql/package.lisp b/sql/package.lisp index e757277..5f8cc4e 100644 --- a/sql/package.lisp +++ b/sql/package.lisp @@ -140,6 +140,7 @@ #:db-type-has-views? #:db-type-has-bigint? #:db-type-has-union? + #:db-type-has-prepared-stmt? #:db-type-has-subqueries? #:db-type-has-boolean-where? #:db-type-transaction-capable? @@ -164,6 +165,12 @@ #:read-large-object #:delete-large-object + ;; Prepared statments + #:database-prepare + #:database-run-prepared + #:database-bind-parameter + #:database-free-prepared + ;; accessors for database class #:name #:connection-spec @@ -301,6 +308,10 @@ #:loop ;; CLSQL Extensions #:for-each-row + #:prepare-sql + #:bind-parameter + #:run-prepared-sql + #:free-prepared-sql ;; Transaction handling (transaction.lisp) #:with-transaction @@ -318,7 +329,7 @@ #:database-commit-transaction #:transaction-level #:transaction - #:autocommit + #:set-autocommit ;; OODDL (ooddl.lisp) #:standard-db-object diff --git a/sql/transaction.lisp b/sql/transaction.lisp index d41d6ef..201d7c0 100644 --- a/sql/transaction.lisp +++ b/sql/transaction.lisp @@ -115,8 +115,10 @@ are called." transaction." (and database (transaction database) (= (transaction-level database) 1))) -(defun autocommit (&key (database *default-database*) (set :unspecified)) - "Returns whether autocommit is currently active." - (unless (eq set :unspecified) - (setf (database-autocommit database) set)) - (database-autocommit database)) +(defun set-autocommit (value &key (database *default-database*)) + "Sets autocommit on or off. Returns old value of of autocommit flag." + (let ((old-value (database-autocommit database))) + (setf (database-autocommit database) value) + (database-autocommit database) + old-value)) + diff --git a/tests/test-basic.lisp b/tests/test-basic.lisp index de3e719..24ef372 100644 --- a/tests/test-basic.lisp +++ b/tests/test-basic.lisp @@ -98,6 +98,7 @@ results))) ((t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t))) + (deftest :basic/map/2 (let ((results '()) (rows (map-query 'list #'identity "select * from TYPE_TABLE" @@ -133,6 +134,15 @@ results))) ((t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t))) + ;; confirm that a query on a single element returns a list of one element + (deftest :basic/map/4 + (let ((rows (map-query 'list #'identity "select t_int from TYPE_TABLE" + :result-types nil))) + (values + (consp (first rows)) + (length (first rows)))) + t 1) + (deftest :basic/do/1 (let ((results '())) (do-query ((int float str) "select * from TYPE_TABLE" :result-types nil) diff --git a/tests/test-init.lisp b/tests/test-init.lisp index 489b50b..cb1cec5 100644 --- a/tests/test-init.lisp +++ b/tests/test-init.lisp @@ -18,8 +18,9 @@ (defvar *report-stream* *standard-output* "Stream to send text report.") (defvar *sexp-report-stream* nil "Stream to send sexp report.") -(defvar *rt-connection*) +(defvar *rt-internal*) (defvar *rt-basic*) +(defvar *rt-connection*) (defvar *rt-fddl*) (defvar *rt-fdml*) (defvar *rt-ooddl*) @@ -552,7 +553,7 @@ (defun compute-tests-for-backend (db-type db-underlying-type) (let ((test-forms '()) (skip-tests '())) - (dolist (test-form (append *rt-connection* *rt-basic* *rt-fddl* *rt-fdml* + (dolist (test-form (append *rt-internal* *rt-connection* *rt-basic* *rt-fddl* *rt-fdml* *rt-ooddl* *rt-oodml* *rt-syntax*)) (let ((test (second test-form))) (cond diff --git a/tests/test-internal.lisp b/tests/test-internal.lisp new file mode 100644 index 0000000..a67cc87 --- /dev/null +++ b/tests/test-internal.lisp @@ -0,0 +1,44 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-internal.lisp +;;;; Purpose: Tests for internal clsql functions +;;;; Author: Kevin M. Rosenberg +;;;; Created: May 2004 +;;;; +;;;; $Id: test-basic.lisp 9450 2004-05-23 10:51:02Z kevin $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) + +(setq *rt-internal* + '( + (deftest :int/convert/1 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM BAR") + "SELECT FOO FROM BAR") + + (deftest :int/convert/2 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM BAR WHERE ID=?") + "SELECT FOO FROM BAR WHERE ID=$1") + + (deftest :int/convert/3 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM \"BAR\" WHERE ID=? AND CODE=?") + "SELECT FOO FROM \"BAR\" WHERE ID=$1 AND CODE=$2") + + (deftest :int/convert/4 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM BAR WHERE ID=\"Match?\" AND CODE=?") + "SELECT FOO FROM BAR WHERE ID=\"Match?\" AND CODE=$1") + + (deftest :int/convert/5 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT 'FOO' FROM BAR WHERE ID='Match?''?' AND CODE=?") + "SELECT 'FOO' FROM BAR WHERE ID='Match?''?' AND CODE=$1") + + )) + -- 2.34.1