r9519: add prepared statements for postgresql
authorKevin M. Rosenberg <kevin@rosenberg.net>
Sun, 30 May 2004 06:22:22 +0000 (06:22 +0000)
committerKevin M. Rosenberg <kevin@rosenberg.net>
Sun, 30 May 2004 06:22:22 +0000 (06:22 +0000)
18 files changed:
ChangeLog
clsql-tests.asd
db-db2/db2-api.lisp
db-db2/db2-constants.lisp
db-db2/db2-sql.lisp
db-mysql/mysql-sql.lisp
db-odbc/odbc-api.lisp
db-odbc/odbc-constants.lisp
db-postgresql/postgresql-sql.lisp
sql/db-interface.lisp
sql/fdml.lisp
sql/generic-odbc.lisp
sql/generic-postgresql.lisp
sql/package.lisp
sql/transaction.lisp
tests/test-basic.lisp
tests/test-init.lisp
tests/test-internal.lisp [new file with mode: 0644]

index 1850cc4ebe8a2ef4d9956fdc577c5c3a5dc62603..711551d939aaedbab8581261ab1e8c78dd41795c 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+29 May 2004 Kevin Rosenberg <kevin@rosenberg.net>
+       * 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 <kevin@rosenberg.net>
        * Version 2.11.3
        * sql/ooddl.lisp: Commit patch from Edi Weitz fixing return
index 13a4c07c855db37b5db46b0cc912e7bd16dd5bbc..9d4df2f67e4c223ce5c13b7b7fedfdf77915a0b6 100644 (file)
@@ -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")
index 99e7245aef95d85d4f2146d08634ae7fb5709985..880f360351a2605d796708c97d9b56f109fca7d8 100644 (file)
                             :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
         (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
index 01007a635f2ff94fbfd3b6419a958926cbc3320c..fad5a3801e4d32287b4222256f970cc2e7958fe7 100644 (file)
@@ -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)
+
index 788d491a76e54ff870fbac0267d11ccb4d566f98..16aa2165cf3613808749948aa2b7440843f2b237 100644 (file)
 (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)
index 513c9ed3602db3c95cf602a1ec7ad23201213ba5..bb4940e59d4cb29fe69cec1eb70ba7cac4e30d7f 100644 (file)
   (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))
 
index b17af024651a85554ea5d8ce8754fb7d83d7e4c0..ab2f6ee54a1e318dda8bb0c78daf0f4a03bf105b 100644 (file)
@@ -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))
 
 
index e967729e63559d6209b8e8ef20cb72b40cf56c6b..c16e2a76869201da13e5b15add9ea95147fdc129 100644 (file)
@@ -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 
 
 (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)
index d93b153d2f06106b016fa4cfab8516733b4165d8..16d5d2fc94a15adeff80016f7abbed465b9d0465 100644 (file)
                  :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
index 939af1fa68ff89356e7f35bc4d8a56ca258348cf..f48d00d7dad945d90f8d502f0b3336929262589b 100644 (file)
@@ -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
 
index 283f2dd95e866738e8031c755fcd3ac91f16e875..50ab024ec38129421c3fb784963e2b7ca0e6bd0c 100644 (file)
@@ -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))
index b83e8cacd854f9f370a238a8f3bf4fbd67f53a59..d22255070c7d2c6504a860c695cbc63d4c2db1d9 100644 (file)
     (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))
index 55d3a9082b8c6984781a58ed84cd3aef8662c979..b85da75fb073ea28016095ff97cd1bf1d2a47c97 100644 (file)
            (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)
 (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)
+
index e7572771e272b120f4c3ca4e20d2063de58761ea..5f8cc4e4b66e9139087fd37bb4ae5b34293621e0 100644 (file)
      #: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?
      #: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
         #:loop
          ;; CLSQL Extensions 
          #:for-each-row
+        #:prepare-sql
+        #:bind-parameter
+        #:run-prepared-sql
+        #:free-prepared-sql
 
          ;; Transaction handling (transaction.lisp) 
          #:with-transaction
          #:database-commit-transaction
          #:transaction-level
          #:transaction
-        #:autocommit
+        #:set-autocommit
 
         ;;  OODDL (ooddl.lisp) 
         #:standard-db-object               
index d41d6eff7c0f8a33d332a430af54ea617a382607..201d7c015b63b1b5663d5c92b955a84678c42c26 100644 (file)
@@ -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))
+
index de3e719fb3e08ad7750d34945a8d9b458954a163..24ef372fb63e7c8a583208a923c906c19a499ca2 100644 (file)
@@ -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" 
                 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)
index 489b50b280ca53cfe79f2d9be30a8e81f4c5b709..cb1cec578b3a793cfc4493845ffd1e25f0197c7f 100644 (file)
@@ -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*)
 (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 (file)
index 0000000..a67cc87
--- /dev/null
@@ -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")
+    
+    ))
+