patch to quote delimiter and null character in copy-* functions

Alejandro Dubrovsky <[email protected]>
Newsgroups gmane.comp.python.db.psycopg.devel
Message-ID <[email protected]>
This patch (attached) fixes the current lack of quoting on the delimiter
and null character passed in to the copy_from and copy_to functions.
This means that

cursor.copy_from(buffer, 'table', null='\\N') works

It also changes the COPY syntax to the current one (as of 7.3).  As a
side effect, a hack like:

cursor.copy_to(buffer, '(select * from table where a is NULL)')
now works.

The most contentious part of the patch would be that since there didn't
seem to be a readily-available quoting function, I created one
(cut-n-pasted from qstring_quote in adapter_qstring.c) in a new file
(utils.c) of dubious name (both the file and the function).  It also
uses caller-owned semantics.   I think that the use of qstring_escape
demands too much from the caller, so this one does more.

_______________________________________________
Psycopg mailing list
Psycopg-IAPFreCvJWPBWskQ1e/[email protected]
http://lists.initd.org/mailman/listinfo/psycopg
psycocopypatch.diff (text/x-patch, 6.9 KB)
=== modified file 'psycopg/adapter_qstring.c'
--- psycopg/adapter_qstring.c	2008-07-26 13:10:02 +0000
+++ psycopg/adapter_qstring.c	2008-09-16 05:08:57 +0000
@@ -40,7 +40,7 @@
 
 #ifndef PSYCOPG_OWN_QUOTING
 size_t
-qstring_escape(char *to, char *from, size_t len, PGconn *conn)
+qstring_escape(char *to, const char *from, size_t len, PGconn *conn)
 {
 #if PG_MAJOR_VERSION > 8 || \
  (PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION > 1) || \
@@ -54,7 +54,7 @@
 }
 #else
 size_t
-qstring_escape(char *to, char *from, size_t len, PGconn *conn)
+qstring_escape(char *to, const char *from, size_t len, PGconn *conn)
 {
     int i, j;
 

=== modified file 'psycopg/cursor_type.c'
--- psycopg/cursor_type.c	2008-07-21 05:41:54 +0000
+++ psycopg/cursor_type.c	2008-09-16 05:33:04 +0000
@@ -34,7 +34,9 @@
 #include "psycopg/typecast.h"
 #include "psycopg/microprotocols.h"
 #include "psycopg/microprotocols_proto.h"
+#include "psycopg/utils.h"
 #include "pgversion.h"
+#include <stdlib.h>
 
 extern PyObject *pyPsycopgTzFixedOffsetTimezone;
 
@@ -1193,6 +1195,7 @@
     Py_ssize_t bufsize = DEFAULT_COPYBUFF;
     PyObject *file, *columns = NULL, *res = NULL;
     char columnlist[DEFAULT_COPYBUFF];
+    char *quoted_delimiter;
 
     static char *kwlist[] = {"file", "table", "sep", "null", "size",
                              "columns", NULL};
@@ -1211,16 +1214,29 @@
 
     EXC_IF_CURS_CLOSED(self);
 
+    quoted_delimiter = psycopg_internal_escape_string(self->conn, sep);
+    if (NULL == quoted_delimiter) {
+        PyErr_SetString(PyExc_ValueError, "Failed to quote delimiter");
+        return NULL;
+    }
     if (null) {
+        char *quoted_null = psycopg_internal_escape_string(self->conn, null);
+        if (NULL == quoted_null) {
+            // Quoting failed.  Throw something
+            PyErr_SetString(PyExc_ValueError, "Failed to quote null-marker");
+            return NULL;
+        }
         PyOS_snprintf(query, DEFAULT_COPYBUFF-1,
-                      "COPY %s%s FROM stdin USING DELIMITERS '%s'"
-                      " WITH NULL AS '%s'", table_name, columnlist, sep, null);
+                      "COPY %s%s FROM stdin WITH DELIMITER AS %s"
+                      " NULL AS %s", table_name, columnlist, quoted_delimiter, quoted_null);
+        free(quoted_null);
     }
     else {
         PyOS_snprintf(query, DEFAULT_COPYBUFF-1,
-                      "COPY %s%s FROM stdin USING DELIMITERS '%s'",
-                      table_name, columnlist, sep);
+                      "COPY %s%s FROM stdin WITH DELIMITER AS %s",
+                      table_name, columnlist, quoted_delimiter);
     }
+    free(quoted_delimiter);
     Dprintf("psyco_curs_copy_from: query = %s", query);
 
     self->copysize = bufsize;
@@ -1265,6 +1281,7 @@
     const char *table_name;
     const char *sep = "\t", *null = NULL;
     PyObject *file, *columns = NULL, *res = NULL;
+    char *quoted_delimiter;
 
     static char *kwlist[] = {"file", "table", "sep", "null", "columns", NULL};
 
@@ -1278,17 +1295,29 @@
         return NULL;
 
     EXC_IF_CURS_CLOSED(self);
-
+    quoted_delimiter = psycopg_internal_escape_string(self->conn, sep);
+    if (NULL == quoted_delimiter) {
+        PyErr_SetString(PyExc_ValueError, "Failed to quote delimiter");
+        return NULL;
+    }
     if (null) {
+        char *quoted_null = psycopg_internal_escape_string(self->conn, null);
+        if (NULL == quoted_null) {
+            // Quoting failed.  Throw something
+            PyErr_SetString(PyExc_ValueError, "Failed to quote null-marker");
+            return NULL;
+        }
+
         PyOS_snprintf(query, DEFAULT_COPYBUFF-1,
-                      "COPY %s%s TO stdout USING DELIMITERS '%s'"
-                      " WITH NULL AS '%s'", table_name, columnlist, sep, null);
+                      "COPY %s%s TO stdout WITH DELIMITER AS %s"
+                      " NULL AS %s", table_name, columnlist, quoted_delimiter, quoted_null);
     }
     else {
         PyOS_snprintf(query, DEFAULT_COPYBUFF-1,
-                      "COPY %s%s TO stdout USING DELIMITERS '%s'",
-                      table_name, columnlist, sep);
+                      "COPY %s%s TO stdout WITH DELIMITER AS %s",
+                      table_name, columnlist, quoted_delimiter);
     }
+    free(quoted_delimiter);
 
     self->copysize = 0;
     self->copyfile = file;

=== modified file 'psycopg/psycopg.h'
--- psycopg/psycopg.h	2008-08-02 08:30:36 +0000
+++ psycopg/psycopg.h	2008-09-16 05:08:12 +0000
@@ -143,7 +143,7 @@
 HIDDEN void psyco_set_error(PyObject *exc, PyObject *curs,  const char *msg,
                             const char *pgerror, const char *pgcode);
 
-HIDDEN size_t qstring_escape(char *to, char *from, size_t len, PGconn *conn);
+HIDDEN size_t qstring_escape(char *to, const char *from, size_t len, PGconn *conn);
 
 /* Exceptions docstrings */
 #define Error_doc \

=== added file 'psycopg/utils.c'
--- psycopg/utils.c	1970-01-01 00:00:00 +0000
+++ psycopg/utils.c	2008-09-16 05:33:36 +0000
@@ -0,0 +1,43 @@
+/* utils.c - miscellaneous utility functions
+ *
+ */
+
+#include "psycopg/config.h"
+#include "psycopg/utils.h"
+#include "psycopg/psycopg.h"
+#include "psycopg/connection.h"
+#include "psycopg/pgtypes.h"
+#include "psycopg/pgversion.h"
+#include <string.h>
+#include <stdlib.h>
+
+char *psycopg_internal_escape_string(connectionObject *conn, const char *string)
+{
+    char *buffer;
+    size_t string_length;
+    int equote;         /* buffer offset if E'' quotes are needed */  
+
+    string_length = strlen(string);
+    
+    buffer = (char *) malloc((string_length * 2 + 4) * sizeof(char));
+    if (buffer == NULL) {
+        return NULL;
+    }
+
+    equote = (conn && (conn->equote)) ? 1 : 0;
+
+    { 
+        size_t qstring_length;
+
+        qstring_length = qstring_escape(buffer + equote + 1, string, string_length,
+                                        (conn ? conn->pgconn : NULL));
+
+        if (equote)
+            buffer[0] = 'E';
+        buffer[equote] = '\''; 
+        buffer[qstring_length + equote + 1] = '\'';
+        buffer[qstring_length + equote + 2] = 0;
+    }
+
+    return buffer;
+}

=== added file 'psycopg/utils.h'
--- psycopg/utils.h	1970-01-01 00:00:00 +0000
+++ psycopg/utils.h	2008-09-16 04:28:18 +0000
@@ -0,0 +1,16 @@
+/* utils.h - miscellaneous utility functions
+ *
+ */
+
+#ifndef PSYCOPG_UTILS_H
+#define PSYCOPG_UTILS_H 1
+
+#include "psycopg/config.h"
+#include "psycopg/connection.h"
+
+
+HIDDEN char *psycopg_internal_escape_string(connectionObject *conn, const char *string);
+
+#endif /* !defined(PSYCOPG_UTILS_H) */
+
+

=== modified file 'setup.py'
--- setup.py	2008-06-28 16:34:57 +0000
+++ setup.py	2008-09-16 05:06:52 +0000
@@ -330,7 +330,7 @@
     'connection_type.c', 'connection_int.c', 'cursor_type.c', 'cursor_int.c',
     'lobject_type.c', 'lobject_int.c',
     'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c',
-    'adapter_asis.c', 'adapter_list.c']
+    'adapter_asis.c', 'adapter_list.c', 'utils.c']
 
 parser = ConfigParser.ConfigParser()
 parser.read('setup.cfg')
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.