mirror of
				https://github.com/psycopg/psycopg2.git
				synced 2025-11-04 09:47:30 +03:00 
			
		
		
		
	Merge branch 'devel'
This commit is contained in:
		
						commit
						ab685c2fc0
					
				
							
								
								
									
										17
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								NEWS
									
									
									
									
									
								
							| 
						 | 
					@ -1,3 +1,18 @@
 | 
				
			||||||
 | 
					What's new in psycopg 2.4.1
 | 
				
			||||||
 | 
					---------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - Use own parser for bytea output, not requiring anymore the libpq 9.0
 | 
				
			||||||
 | 
					    to parse the hex format.
 | 
				
			||||||
 | 
					  - Don't fail connection if the client encoding is a non-normalized
 | 
				
			||||||
 | 
					    variant. Issue reported by Peter Eisentraut.
 | 
				
			||||||
 | 
					  - Correctly detect an empty query sent to the backend (ticket #46).
 | 
				
			||||||
 | 
					  - Fixed a SystemError clobbering libpq errors raised without SQLSTATE.
 | 
				
			||||||
 | 
					    Bug vivisectioned by Eric Snow.
 | 
				
			||||||
 | 
					  - Fixed interaction between NamedTuple and server-side cursors.
 | 
				
			||||||
 | 
					  - Allow to specify --static-libpq on setup.py command line instead of
 | 
				
			||||||
 | 
					    just in 'setup.cfg'. Patch provided by Matthew Ryan (ticket #48).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What's new in psycopg 2.4
 | 
					What's new in psycopg 2.4
 | 
				
			||||||
-------------------------
 | 
					-------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,6 +178,8 @@ doc/html/advanced.html.
 | 
				
			||||||
  - Fixed TimestampFromTicks() and TimeFromTicks() for seconds >= 59.5.
 | 
					  - Fixed TimestampFromTicks() and TimeFromTicks() for seconds >= 59.5.
 | 
				
			||||||
  - Fixed spurious exception raised when calling C typecasters from Python
 | 
					  - Fixed spurious exception raised when calling C typecasters from Python
 | 
				
			||||||
    ones.
 | 
					    ones.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What's new in psycopg 2.0.14
 | 
					What's new in psycopg 2.0.14
 | 
				
			||||||
----------------------------
 | 
					----------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@
 | 
				
			||||||
# their work without bothering about the module dependencies.
 | 
					# their work without bothering about the module dependencies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALLOWED_PSYCOPG_VERSIONS = ('2.4-beta1', '2.4-beta2', '2.4')
 | 
					ALLOWED_PSYCOPG_VERSIONS = ('2.4-beta1', '2.4-beta2', '2.4', '2.4.1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,9 @@ Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!D
 | 
				
			||||||
Transferring binary data from PostgreSQL 9.0 doesn't work.
 | 
					Transferring binary data from PostgreSQL 9.0 doesn't work.
 | 
				
			||||||
    PostgreSQL 9.0 uses by default `the "hex" format`__ to transfer
 | 
					    PostgreSQL 9.0 uses by default `the "hex" format`__ to transfer
 | 
				
			||||||
    :sql:`bytea` data: the format can't be parsed by the libpq 8.4 and
 | 
					    :sql:`bytea` data: the format can't be parsed by the libpq 8.4 and
 | 
				
			||||||
    earlier. Three options to solve the problem are:
 | 
					    earlier. The problem is solved in Psycopg 2.4.1, that uses its own parser
 | 
				
			||||||
 | 
					    for the :sql:`bytea` format. For previous Psycopg releases, three options
 | 
				
			||||||
 | 
					    to solve the problem are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - set the bytea_output__ parameter to ``escape`` in the server;
 | 
					    - set the bytea_output__ parameter to ``escape`` in the server;
 | 
				
			||||||
    - execute the database command ``SET bytea_output TO escape;`` in the
 | 
					    - execute the database command ``SET bytea_output TO escape;`` in the
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,6 +271,10 @@ the SQL string that would be sent to the database.
 | 
				
			||||||
  .. versionchanged:: 2.4
 | 
					  .. versionchanged:: 2.4
 | 
				
			||||||
     only strings were supported before.
 | 
					     only strings were supported before.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .. versionchanged:: 2.4.1
 | 
				
			||||||
 | 
					     can parse the 'hex' format from 9.0 servers without relying on the
 | 
				
			||||||
 | 
					     version of the client library.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .. note::
 | 
					  .. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    In Python 2, if you have binary data in a `!str` object, you can pass them
 | 
					    In Python 2, if you have binary data in a `!str` object, you can pass them
 | 
				
			||||||
| 
						 | 
					@ -282,17 +286,14 @@ the SQL string that would be sent to the database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .. warning::
 | 
					  .. warning::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
     PostgreSQL 9 uses by default `a new "hex" format`__ to emit :sql:`bytea`
 | 
					     Since version 9.0 PostgreSQL uses by default `a new "hex" format`__ to
 | 
				
			||||||
     fields. Unfortunately this format can't be parsed by libpq versions
 | 
					     emit :sql:`bytea` fields. Starting from Psycopg 2.4.1 the format is
 | 
				
			||||||
     before 9.0. This means that using a library client with version lesser
 | 
					     correctly supported.  If you use a previous version you will need some
 | 
				
			||||||
     than 9.0 to talk with a server 9.0 or later you may have problems
 | 
					     extra care when receiving bytea from PostgreSQL: you must have at least
 | 
				
			||||||
     receiving :sql:`bytea` data. To work around this problem you can set the
 | 
					     the libpq 9.0 installed on the client or alternatively you can set the
 | 
				
			||||||
     `bytea_output`__ parameter to ``escape``, either in the server
 | 
					     `bytea_output`__ configutation parameter to ``escape``, either in the
 | 
				
			||||||
     configuration or in the client session using a query such as ``SET
 | 
					     server configuration file or in the client session (using a query such as
 | 
				
			||||||
     bytea_output TO escape;`` before trying to receive binary data.
 | 
					     ``SET bytea_output TO escape;``) before receiving binary data.
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
     Starting from Psycopg 2.4 this condition is detected and signaled with a
 | 
					 | 
				
			||||||
     `~psycopg2.InterfaceError`.
 | 
					 | 
				
			||||||
     
 | 
					     
 | 
				
			||||||
     .. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html
 | 
					     .. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html
 | 
				
			||||||
     .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
 | 
					     .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,6 @@ and classes untill a better place in the distribution is found.
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import codecs
 | 
					 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
import re as regex
 | 
					import re as regex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -291,21 +290,28 @@ class NamedTupleCursor(_cursor):
 | 
				
			||||||
            return nt(*t)
 | 
					            return nt(*t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def fetchmany(self, size=None):
 | 
					    def fetchmany(self, size=None):
 | 
				
			||||||
 | 
					        ts = _cursor.fetchmany(self, size)
 | 
				
			||||||
        nt = self.Record
 | 
					        nt = self.Record
 | 
				
			||||||
        if nt is None:
 | 
					        if nt is None:
 | 
				
			||||||
            nt = self.Record = self._make_nt()
 | 
					            nt = self.Record = self._make_nt()
 | 
				
			||||||
        ts = _cursor.fetchmany(self, size)
 | 
					 | 
				
			||||||
        return [nt(*t) for t in ts]
 | 
					        return [nt(*t) for t in ts]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def fetchall(self):
 | 
					    def fetchall(self):
 | 
				
			||||||
 | 
					        ts = _cursor.fetchall(self)
 | 
				
			||||||
        nt = self.Record
 | 
					        nt = self.Record
 | 
				
			||||||
        if nt is None:
 | 
					        if nt is None:
 | 
				
			||||||
            nt = self.Record = self._make_nt()
 | 
					            nt = self.Record = self._make_nt()
 | 
				
			||||||
        ts = _cursor.fetchall(self)
 | 
					 | 
				
			||||||
        return [nt(*t) for t in ts]
 | 
					        return [nt(*t) for t in ts]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __iter__(self):
 | 
					    def __iter__(self):
 | 
				
			||||||
        return iter(self.fetchall())
 | 
					        # Invoking _cursor.__iter__(self) goes to infinite recursion,
 | 
				
			||||||
 | 
					        # so we do pagination by hand
 | 
				
			||||||
 | 
					        while 1:
 | 
				
			||||||
 | 
					            recs = self.fetchmany(self.itersize)
 | 
				
			||||||
 | 
					            if not recs:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            for rec in recs:
 | 
				
			||||||
 | 
					                yield rec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        from collections import namedtuple
 | 
					        from collections import namedtuple
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -236,10 +236,45 @@ conn_get_standard_conforming_strings(PGconn *pgconn)
 | 
				
			||||||
    return equote;
 | 
					    return equote;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Remove irrelevant chars from encoding name and turn it uppercase.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Return a buffer allocated on Python heap,
 | 
				
			||||||
 | 
					 * NULL and set an exception on error.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static char *
 | 
				
			||||||
 | 
					clean_encoding_name(const char *enc)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const char *i = enc;
 | 
				
			||||||
 | 
					    char *rv, *j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* convert to upper case and remove '-' and '_' from string */
 | 
				
			||||||
 | 
					    if (!(j = rv = PyMem_Malloc(strlen(enc) + 1))) {
 | 
				
			||||||
 | 
					        PyErr_NoMemory();
 | 
				
			||||||
 | 
					        return NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (*i) {
 | 
				
			||||||
 | 
					        if (!isalnum(*i)) {
 | 
				
			||||||
 | 
					            ++i;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            *j++ = toupper(*i++);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    *j = '\0';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Dprintf("clean_encoding_name: %s -> %s", enc, rv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return rv;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Convert a PostgreSQL encoding to a Python codec.
 | 
					/* Convert a PostgreSQL encoding to a Python codec.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Return a new copy of the codec name allocated on the Python heap,
 | 
					 * Return a new copy of the codec name allocated on the Python heap,
 | 
				
			||||||
 * NULL with exception in case of error.
 | 
					 * NULL with exception in case of error.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 'enc' should be already normalized (uppercase, no - or _).
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
static char *
 | 
					static char *
 | 
				
			||||||
conn_encoding_to_codec(const char *enc)
 | 
					conn_encoding_to_codec(const char *enc)
 | 
				
			||||||
| 
						 | 
					@ -285,7 +320,7 @@ exit:
 | 
				
			||||||
static int
 | 
					static int
 | 
				
			||||||
conn_read_encoding(connectionObject *self, PGconn *pgconn)
 | 
					conn_read_encoding(connectionObject *self, PGconn *pgconn)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    char *enc = NULL, *codec = NULL, *j;
 | 
					    char *enc = NULL, *codec = NULL;
 | 
				
			||||||
    const char *tmp;
 | 
					    const char *tmp;
 | 
				
			||||||
    int rv = -1;
 | 
					    int rv = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -297,16 +332,10 @@ conn_read_encoding(connectionObject *self, PGconn *pgconn)
 | 
				
			||||||
        goto exit;
 | 
					        goto exit;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!(enc = PyMem_Malloc(strlen(tmp)+1))) {
 | 
					    if (!(enc = clean_encoding_name(tmp))) {
 | 
				
			||||||
        PyErr_NoMemory();
 | 
					 | 
				
			||||||
        goto exit;
 | 
					        goto exit;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* turn encoding in uppercase */
 | 
					 | 
				
			||||||
    j = enc;
 | 
					 | 
				
			||||||
    while (*tmp) { *j++ = toupper(*tmp++); }
 | 
					 | 
				
			||||||
    *j = '\0';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* Look for this encoding in Python codecs. */
 | 
					    /* Look for this encoding in Python codecs. */
 | 
				
			||||||
    if (!(codec = conn_encoding_to_codec(enc))) {
 | 
					    if (!(codec = conn_encoding_to_codec(enc))) {
 | 
				
			||||||
        goto exit;
 | 
					        goto exit;
 | 
				
			||||||
| 
						 | 
					@ -965,21 +994,23 @@ conn_set_client_encoding(connectionObject *self, const char *enc)
 | 
				
			||||||
    PGresult *pgres = NULL;
 | 
					    PGresult *pgres = NULL;
 | 
				
			||||||
    char *error = NULL;
 | 
					    char *error = NULL;
 | 
				
			||||||
    char query[48];
 | 
					    char query[48];
 | 
				
			||||||
    int res = 0;
 | 
					    int res = 1;
 | 
				
			||||||
    char *codec;
 | 
					    char *codec = NULL;
 | 
				
			||||||
 | 
					    char *clean_enc = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* If the current encoding is equal to the requested one we don't
 | 
					    /* If the current encoding is equal to the requested one we don't
 | 
				
			||||||
       issue any query to the backend */
 | 
					       issue any query to the backend */
 | 
				
			||||||
    if (strcmp(self->encoding, enc) == 0) return 0;
 | 
					    if (strcmp(self->encoding, enc) == 0) return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* We must know what python codec this encoding is. */
 | 
					    /* We must know what python codec this encoding is. */
 | 
				
			||||||
    if (!(codec = conn_encoding_to_codec(enc))) { return -1; }
 | 
					    if (!(clean_enc = clean_encoding_name(enc))) { goto exit; }
 | 
				
			||||||
 | 
					    if (!(codec = conn_encoding_to_codec(clean_enc))) { goto exit; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Py_BEGIN_ALLOW_THREADS;
 | 
					    Py_BEGIN_ALLOW_THREADS;
 | 
				
			||||||
    pthread_mutex_lock(&self->lock);
 | 
					    pthread_mutex_lock(&self->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* set encoding, no encoding string is longer than 24 bytes */
 | 
					    /* set encoding, no encoding string is longer than 24 bytes */
 | 
				
			||||||
    PyOS_snprintf(query, 47, "SET client_encoding = '%s'", enc);
 | 
					    PyOS_snprintf(query, 47, "SET client_encoding = '%s'", clean_enc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* abort the current transaction, to set the encoding ouside of
 | 
					    /* abort the current transaction, to set the encoding ouside of
 | 
				
			||||||
       transactions */
 | 
					       transactions */
 | 
				
			||||||
| 
						 | 
					@ -994,21 +1025,18 @@ conn_set_client_encoding(connectionObject *self, const char *enc)
 | 
				
			||||||
    /* no error, we can proceeed and store the new encoding */
 | 
					    /* no error, we can proceeed and store the new encoding */
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        char *tmp = self->encoding;
 | 
					        char *tmp = self->encoding;
 | 
				
			||||||
        self->encoding = NULL;
 | 
					        self->encoding = clean_enc;
 | 
				
			||||||
        PyMem_Free(tmp);
 | 
					        PyMem_Free(tmp);
 | 
				
			||||||
    }
 | 
					        clean_enc = NULL;
 | 
				
			||||||
    if (!(self->encoding = psycopg_strdup(enc, 0))) {
 | 
					 | 
				
			||||||
        res = 1;  /* don't call pq_complete_error below */
 | 
					 | 
				
			||||||
        goto endlock;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Store the python codec too. */
 | 
					    /* Store the python codec too. */
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        char *tmp = self->codec;
 | 
					        char *tmp = self->codec;
 | 
				
			||||||
        self->codec = NULL;
 | 
					        self->codec = codec;
 | 
				
			||||||
        PyMem_Free(tmp);
 | 
					        PyMem_Free(tmp);
 | 
				
			||||||
 | 
					        codec = NULL;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    self->codec = codec;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Dprintf("conn_set_client_encoding: set encoding to %s (codec: %s)",
 | 
					    Dprintf("conn_set_client_encoding: set encoding to %s (codec: %s)",
 | 
				
			||||||
            self->encoding, self->codec);
 | 
					            self->encoding, self->codec);
 | 
				
			||||||
| 
						 | 
					@ -1021,6 +1049,10 @@ endlock:
 | 
				
			||||||
    if (res < 0)
 | 
					    if (res < 0)
 | 
				
			||||||
        pq_complete_error(self, &pgres, &error);
 | 
					        pq_complete_error(self, &pgres, &error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exit:
 | 
				
			||||||
 | 
					    PyMem_Free(clean_enc);
 | 
				
			||||||
 | 
					    PyMem_Free(codec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return res;
 | 
					    return res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -423,36 +423,18 @@ static PyObject *
 | 
				
			||||||
psyco_conn_set_client_encoding(connectionObject *self, PyObject *args)
 | 
					psyco_conn_set_client_encoding(connectionObject *self, PyObject *args)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    const char *enc;
 | 
					    const char *enc;
 | 
				
			||||||
    char *buffer, *dest;
 | 
					 | 
				
			||||||
    PyObject *rv = NULL;
 | 
					    PyObject *rv = NULL;
 | 
				
			||||||
    Py_ssize_t len;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EXC_IF_CONN_CLOSED(self);
 | 
					    EXC_IF_CONN_CLOSED(self);
 | 
				
			||||||
    EXC_IF_CONN_ASYNC(self, set_client_encoding);
 | 
					    EXC_IF_CONN_ASYNC(self, set_client_encoding);
 | 
				
			||||||
    EXC_IF_TPC_PREPARED(self, set_client_encoding);
 | 
					    EXC_IF_TPC_PREPARED(self, set_client_encoding);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!PyArg_ParseTuple(args, "s#", &enc, &len)) return NULL;
 | 
					    if (!PyArg_ParseTuple(args, "s", &enc)) return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* convert to upper case and remove '-' and '_' from string */
 | 
					    if (conn_set_client_encoding(self, enc) == 0) {
 | 
				
			||||||
    if (!(dest = buffer = PyMem_Malloc(len+1))) {
 | 
					 | 
				
			||||||
        return PyErr_NoMemory();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while (*enc) {
 | 
					 | 
				
			||||||
        if (*enc == '_' || *enc == '-') {
 | 
					 | 
				
			||||||
            ++enc;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            *dest++ = toupper(*enc++);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    *dest = '\0';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (conn_set_client_encoding(self, buffer) == 0) {
 | 
					 | 
				
			||||||
        Py_INCREF(Py_None);
 | 
					        Py_INCREF(Py_None);
 | 
				
			||||||
        rv = Py_None;
 | 
					        rv = Py_None;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    PyMem_Free(buffer);
 | 
					 | 
				
			||||||
    return rv;
 | 
					    return rv;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -172,16 +172,19 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres)
 | 
				
			||||||
    if (pgres) {
 | 
					    if (pgres) {
 | 
				
			||||||
        err = PQresultErrorMessage(pgres);
 | 
					        err = PQresultErrorMessage(pgres);
 | 
				
			||||||
        if (err != NULL) {
 | 
					        if (err != NULL) {
 | 
				
			||||||
 | 
					            Dprintf("pq_raise: PQresultErrorMessage: err=%s", err);
 | 
				
			||||||
            code = PQresultErrorField(pgres, PG_DIAG_SQLSTATE);
 | 
					            code = PQresultErrorField(pgres, PG_DIAG_SQLSTATE);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (err == NULL)
 | 
					    if (err == NULL) {
 | 
				
			||||||
        err = PQerrorMessage(conn->pgconn);
 | 
					        err = PQerrorMessage(conn->pgconn);
 | 
				
			||||||
 | 
					        Dprintf("pq_raise: PQerrorMessage: err=%s", err);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* if the is no error message we probably called pq_raise without reason:
 | 
					    /* if the is no error message we probably called pq_raise without reason:
 | 
				
			||||||
       we need to set an exception anyway because the caller will probably
 | 
					       we need to set an exception anyway because the caller will probably
 | 
				
			||||||
       raise and a meaningful message is better than an empty one */
 | 
					       raise and a meaningful message is better than an empty one */
 | 
				
			||||||
    if (err == NULL) {
 | 
					    if (err == NULL || err[0] == '\0') {
 | 
				
			||||||
        PyErr_SetString(Error, "psycopg went psycotic without error set");
 | 
					        PyErr_SetString(Error, "psycopg went psycotic without error set");
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -191,9 +194,15 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres)
 | 
				
			||||||
    if (code != NULL) {
 | 
					    if (code != NULL) {
 | 
				
			||||||
        exc = exception_from_sqlstate(code);
 | 
					        exc = exception_from_sqlstate(code);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        /* Fallback if there is no exception code (reported happening e.g.
 | 
				
			||||||
 | 
					         * when the connection is closed). */
 | 
				
			||||||
 | 
					        exc = DatabaseError;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* try to remove the initial "ERROR: " part from the postgresql error */
 | 
					    /* try to remove the initial "ERROR: " part from the postgresql error */
 | 
				
			||||||
    err2 = strip_severity(err);
 | 
					    err2 = strip_severity(err);
 | 
				
			||||||
 | 
					    Dprintf("pq_raise: err2=%s", err2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    psyco_set_error(exc, curs, err2, err, code);
 | 
					    psyco_set_error(exc, curs, err2, err, code);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1355,6 +1364,13 @@ pq_fetch(cursorObject *curs)
 | 
				
			||||||
        /* don't clear curs->pgres, because it contains the results! */
 | 
					        /* don't clear curs->pgres, because it contains the results! */
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PGRES_EMPTY_QUERY:
 | 
				
			||||||
 | 
					        PyErr_SetString(ProgrammingError,
 | 
				
			||||||
 | 
					            "can't execute an empty query");
 | 
				
			||||||
 | 
					        IFCLEARPGRES(curs->pgres);
 | 
				
			||||||
 | 
					        ex = -1;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
        Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn);
 | 
					        Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn);
 | 
				
			||||||
        pq_raise(curs->conn, curs, NULL);
 | 
					        pq_raise(curs->conn, curs, NULL);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ chunk_dealloc(chunkObject *self)
 | 
				
			||||||
        FORMAT_CODE_PY_SSIZE_T,
 | 
					        FORMAT_CODE_PY_SSIZE_T,
 | 
				
			||||||
        self->base, self->len
 | 
					        self->base, self->len
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    PQfreemem(self->base);
 | 
					    PyMem_Free(self->base);
 | 
				
			||||||
    Py_TYPE(self)->tp_free((PyObject *)self);
 | 
					    Py_TYPE(self)->tp_free((PyObject *)self);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -127,95 +127,185 @@ PyTypeObject chunkType = {
 | 
				
			||||||
    chunk_doc                   /* tp_doc */
 | 
					    chunk_doc                   /* tp_doc */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static PyObject *
 | 
					
 | 
				
			||||||
 | 
					static char *psycopg_parse_hex(
 | 
				
			||||||
 | 
					        const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout);
 | 
				
			||||||
 | 
					static char *psycopg_parse_escape(
 | 
				
			||||||
 | 
					        const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* The function is not static and not hidden as we use ctypes to test it. */
 | 
				
			||||||
 | 
					PyObject *
 | 
				
			||||||
typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs)
 | 
					typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    chunkObject *chunk = NULL;
 | 
					    chunkObject *chunk = NULL;
 | 
				
			||||||
    PyObject *res = NULL;
 | 
					    PyObject *res = NULL;
 | 
				
			||||||
    char *str = NULL, *buffer = NULL;
 | 
					    char *buffer = NULL;
 | 
				
			||||||
    size_t len;
 | 
					    Py_ssize_t len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
 | 
					    if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* PQunescapeBytea absolutely wants a 0-terminated string and we don't
 | 
					    if (s[0] == '\\' && s[1] == 'x') {
 | 
				
			||||||
       want to copy the whole buffer, right? Wrong, but there isn't any other
 | 
					        /* This is a buffer escaped in hex format: libpq before 9.0 can't
 | 
				
			||||||
       way <g> */
 | 
					         * parse it and we can't detect reliably the libpq version at runtime.
 | 
				
			||||||
    if (s[l] != '\0') {
 | 
					         * So the only robust option is to parse it ourselves - luckily it's
 | 
				
			||||||
        if ((buffer = PyMem_Malloc(l+1)) == NULL) {
 | 
					         * an easy format.
 | 
				
			||||||
            PyErr_NoMemory();
 | 
					         */
 | 
				
			||||||
            goto fail;
 | 
					        if (NULL == (buffer = psycopg_parse_hex(s, l, &len))) {
 | 
				
			||||||
 | 
					            goto exit;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        /* Py_ssize_t->size_t cast is safe, as long as the Py_ssize_t is
 | 
					 | 
				
			||||||
         * >= 0: */
 | 
					 | 
				
			||||||
        assert (l >= 0);
 | 
					 | 
				
			||||||
        strncpy(buffer, s, (size_t) l);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        buffer[l] = '\0';
 | 
					 | 
				
			||||||
        s = buffer;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    str = (char*)PQunescapeBytea((unsigned char*)s, &len);
 | 
					    else {
 | 
				
			||||||
    Dprintf("typecast_BINARY_cast: unescaped " FORMAT_CODE_SIZE_T " bytes",
 | 
					        /* This is a buffer in the classic bytea format. So we can handle it
 | 
				
			||||||
      len);
 | 
					         * to the PQunescapeBytea to have it parsed, rignt? ...Wrong. We
 | 
				
			||||||
 | 
					         * could, but then we'd have to record whether buffer was allocated by
 | 
				
			||||||
    /* The type of the second parameter to PQunescapeBytea is size_t *, so it's
 | 
					         * Python or by the libpq to dispose it properly. Furthermore the
 | 
				
			||||||
     * possible (especially with Python < 2.5) to get a return value too large
 | 
					         * PQunescapeBytea interface is not the most brilliant as it wants a
 | 
				
			||||||
     * to fit into a Python container. */
 | 
					         * null-terminated string even if we have known its length thus
 | 
				
			||||||
    if (len > (size_t) PY_SSIZE_T_MAX) {
 | 
					         * requiring a useless memcpy and strlen.
 | 
				
			||||||
      PyErr_SetString(PyExc_IndexError, "PG buffer too large to fit in Python"
 | 
					         * So we'll just have our better integrated parser, let's finish this
 | 
				
			||||||
                                        " buffer.");
 | 
					         * story.
 | 
				
			||||||
      goto fail;
 | 
					         */
 | 
				
			||||||
    }
 | 
					        if (NULL == (buffer = psycopg_parse_escape(s, l, &len))) {
 | 
				
			||||||
 | 
					            goto exit;
 | 
				
			||||||
    /* Check the escaping was successful */
 | 
					        }
 | 
				
			||||||
    if (s[0] == '\\' && s[1] == 'x'     /* input encoded in hex format */
 | 
					 | 
				
			||||||
        && str[0] == 'x'                /* output resulted in an 'x' */
 | 
					 | 
				
			||||||
        && s[2] != '7' && s[3] != '8')  /* input wasn't really an x (0x78) */
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        PyErr_SetString(InterfaceError,
 | 
					 | 
				
			||||||
            "can't receive bytea data from server >= 9.0 with the current "
 | 
					 | 
				
			||||||
            "libpq client library: please update the libpq to at least 9.0 "
 | 
					 | 
				
			||||||
            "or set bytea_output to 'escape' in the server config "
 | 
					 | 
				
			||||||
            "or with a query");
 | 
					 | 
				
			||||||
        goto fail;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    chunk = (chunkObject *) PyObject_New(chunkObject, &chunkType);
 | 
					    chunk = (chunkObject *) PyObject_New(chunkObject, &chunkType);
 | 
				
			||||||
    if (chunk == NULL) goto fail;
 | 
					    if (chunk == NULL) goto exit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* **Transfer** ownership of str's memory to the chunkObject: */
 | 
					    /* **Transfer** ownership of buffer's memory to the chunkObject: */
 | 
				
			||||||
    chunk->base = str;
 | 
					    chunk->base = buffer;
 | 
				
			||||||
    str = NULL;
 | 
					    buffer = NULL;
 | 
				
			||||||
 | 
					    chunk->len = (Py_ssize_t)len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* size_t->Py_ssize_t cast was validated above: */
 | 
					 | 
				
			||||||
    chunk->len = (Py_ssize_t) len;
 | 
					 | 
				
			||||||
#if PY_MAJOR_VERSION < 3
 | 
					#if PY_MAJOR_VERSION < 3
 | 
				
			||||||
    if ((res = PyBuffer_FromObject((PyObject *)chunk, 0, chunk->len)) == NULL)
 | 
					    if ((res = PyBuffer_FromObject((PyObject *)chunk, 0, chunk->len)) == NULL)
 | 
				
			||||||
        goto fail;
 | 
					        goto exit;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
    if ((res = PyMemoryView_FromObject((PyObject*)chunk)) == NULL)
 | 
					    if ((res = PyMemoryView_FromObject((PyObject*)chunk)) == NULL)
 | 
				
			||||||
        goto fail;
 | 
					        goto exit;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    /* PyBuffer_FromObject() created a new reference.  We'll release our
 | 
					 | 
				
			||||||
     * reference held in 'chunk' in the 'cleanup' clause. */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    goto cleanup;
 | 
					exit:
 | 
				
			||||||
    fail:
 | 
					    Py_XDECREF((PyObject *)chunk);
 | 
				
			||||||
      assert (PyErr_Occurred());
 | 
					    PyMem_Free(buffer);
 | 
				
			||||||
      if (res != NULL) {
 | 
					 | 
				
			||||||
          Py_DECREF(res);
 | 
					 | 
				
			||||||
          res = NULL;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      /* Fall through to cleanup: */
 | 
					 | 
				
			||||||
    cleanup:
 | 
					 | 
				
			||||||
      if (chunk != NULL) {
 | 
					 | 
				
			||||||
          Py_DECREF((PyObject *) chunk);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (str != NULL) {
 | 
					 | 
				
			||||||
          /* str's mem was allocated by PQunescapeBytea; must use PQfreemem: */
 | 
					 | 
				
			||||||
          PQfreemem(str);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      /* We allocated buffer with PyMem_Malloc; must use PyMem_Free: */
 | 
					 | 
				
			||||||
      PyMem_Free(buffer);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return res;
 | 
					    return res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char hex_lut[128] = {
 | 
				
			||||||
 | 
					    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					     0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Parse a bytea output buffer encoded in 'hex' format.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * the format is described in
 | 
				
			||||||
 | 
					 * http://www.postgresql.org/docs/9.0/static/datatype-binary.html
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Parse the buffer in 'bufin', whose length is 'sizein'.
 | 
				
			||||||
 | 
					 * Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size.
 | 
				
			||||||
 | 
					 * In case of error set an exception and return NULL.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static char *
 | 
				
			||||||
 | 
					psycopg_parse_hex(const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    char *ret = NULL;
 | 
				
			||||||
 | 
					    const char *bufend = bufin + sizein;
 | 
				
			||||||
 | 
					    const char *pi = bufin + 2;     /* past the \x */
 | 
				
			||||||
 | 
					    char *bufout;
 | 
				
			||||||
 | 
					    char *po;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    po = bufout = PyMem_Malloc((sizein - 2) >> 1);   /* output size upper bound */
 | 
				
			||||||
 | 
					    if (NULL == bufout) {
 | 
				
			||||||
 | 
					        PyErr_NoMemory();
 | 
				
			||||||
 | 
					        goto exit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Implementation note: we call this function upon database response, not
 | 
				
			||||||
 | 
					     * user input (because we are parsing the output format of a buffer) so we
 | 
				
			||||||
 | 
					     * don't expect errors. On bad input we reserve the right to return a bad
 | 
				
			||||||
 | 
					     * output, not an error.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    while (pi < bufend) {
 | 
				
			||||||
 | 
					        char c;
 | 
				
			||||||
 | 
					        while (-1 == (c = hex_lut[*pi++ & '\x7f'])) {
 | 
				
			||||||
 | 
					            if (pi >= bufend) { goto endloop; }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        *po = c << 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while (-1 == (c = hex_lut[*pi++ & '\x7f'])) {
 | 
				
			||||||
 | 
					            if (pi >= bufend) { goto endloop; }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        *po++ |= c;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					endloop:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = bufout;
 | 
				
			||||||
 | 
					    *sizeout = po - bufout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exit:
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Parse a bytea output buffer encoded in 'escape' format.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * the format is described in
 | 
				
			||||||
 | 
					 * http://www.postgresql.org/docs/9.0/static/datatype-binary.html
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Parse the buffer in 'bufin', whose length is 'sizein'.
 | 
				
			||||||
 | 
					 * Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size.
 | 
				
			||||||
 | 
					 * In case of error set an exception and return NULL.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static char *
 | 
				
			||||||
 | 
					psycopg_parse_escape(const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    char *ret = NULL;
 | 
				
			||||||
 | 
					    const char *bufend = bufin + sizein;
 | 
				
			||||||
 | 
					    const char *pi = bufin;
 | 
				
			||||||
 | 
					    char *bufout;
 | 
				
			||||||
 | 
					    char *po;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    po = bufout = PyMem_Malloc(sizein);   /* output size upper bound */
 | 
				
			||||||
 | 
					    if (NULL == bufout) {
 | 
				
			||||||
 | 
					        PyErr_NoMemory();
 | 
				
			||||||
 | 
					        goto exit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (pi < bufend) {
 | 
				
			||||||
 | 
					        if (*pi != '\\') {
 | 
				
			||||||
 | 
					            /* Unescaped char */
 | 
				
			||||||
 | 
					            *po++ = *pi++;
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if ((pi[1] >= '0' && pi[1] <= '3') &&
 | 
				
			||||||
 | 
					            (pi[2] >= '0' && pi[2] <= '7') &&
 | 
				
			||||||
 | 
					            (pi[3] >= '0' && pi[3] <= '7'))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            /* Escaped octal value */
 | 
				
			||||||
 | 
					            *po++ = ((pi[1] - '0') << 6) |
 | 
				
			||||||
 | 
					                    ((pi[2] - '0') << 3) |
 | 
				
			||||||
 | 
					                    ((pi[3] - '0'));
 | 
				
			||||||
 | 
					            pi += 4;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            /* Escaped char */
 | 
				
			||||||
 | 
					            *po++ = pi[1];
 | 
				
			||||||
 | 
					            pi += 2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = bufout;
 | 
				
			||||||
 | 
					    *sizeout = po - bufout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exit:
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							| 
						 | 
					@ -79,7 +79,7 @@ except ImportError:
 | 
				
			||||||
# Take a look at http://www.python.org/dev/peps/pep-0386/
 | 
					# Take a look at http://www.python.org/dev/peps/pep-0386/
 | 
				
			||||||
# for a consistent versioning pattern.
 | 
					# for a consistent versioning pattern.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PSYCOPG_VERSION = '2.4'
 | 
					PSYCOPG_VERSION = '2.4.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
version_flags   = ['dt', 'dec']
 | 
					version_flags   = ['dt', 'dec']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,6 +133,7 @@ class psycopg_build_ext(build_ext):
 | 
				
			||||||
        self.mx_include_dir = None
 | 
					        self.mx_include_dir = None
 | 
				
			||||||
        self.use_pydatetime = 1
 | 
					        self.use_pydatetime = 1
 | 
				
			||||||
        self.have_ssl = have_ssl
 | 
					        self.have_ssl = have_ssl
 | 
				
			||||||
 | 
					        self.static_libpq = static_libpq
 | 
				
			||||||
        self.pg_config = None
 | 
					        self.pg_config = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_compiler(self):
 | 
					    def get_compiler(self):
 | 
				
			||||||
| 
						 | 
					@ -263,7 +264,7 @@ or with the pg_config option in 'setup.cfg'.
 | 
				
			||||||
            sys.exit(1)
 | 
					            sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.include_dirs.append(".")
 | 
					        self.include_dirs.append(".")
 | 
				
			||||||
        if static_libpq:
 | 
					        if self.static_libpq:
 | 
				
			||||||
            if not self.link_objects: self.link_objects = []
 | 
					            if not self.link_objects: self.link_objects = []
 | 
				
			||||||
            self.link_objects.append(
 | 
					            self.link_objects.append(
 | 
				
			||||||
                    os.path.join(self.get_pg_config("libdir"), "libpq.a"))
 | 
					                    os.path.join(self.get_pg_config("libdir"), "libpq.a"))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,17 +27,6 @@ import sys
 | 
				
			||||||
from testconfig import dsn
 | 
					from testconfig import dsn
 | 
				
			||||||
from testutils import unittest
 | 
					from testutils import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# If connection to test db fails, bail out early.
 | 
					 | 
				
			||||||
import psycopg2
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    cnn = psycopg2.connect(dsn)
 | 
					 | 
				
			||||||
except Exception, e:
 | 
					 | 
				
			||||||
    print "Failed connection to test db:", e.__class__.__name__, e
 | 
					 | 
				
			||||||
    print "Please set env vars 'PSYCOPG2_TESTDB*' to valid values."
 | 
					 | 
				
			||||||
    sys.exit(1)
 | 
					 | 
				
			||||||
else:
 | 
					 | 
				
			||||||
    cnn.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import bug_gc
 | 
					import bug_gc
 | 
				
			||||||
import bugX000
 | 
					import bugX000
 | 
				
			||||||
import extras_dictcursor
 | 
					import extras_dictcursor
 | 
				
			||||||
| 
						 | 
					@ -57,6 +46,17 @@ import test_green
 | 
				
			||||||
import test_cancel
 | 
					import test_cancel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_suite():
 | 
					def test_suite():
 | 
				
			||||||
 | 
					    # If connection to test db fails, bail out early.
 | 
				
			||||||
 | 
					    import psycopg2
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        cnn = psycopg2.connect(dsn)
 | 
				
			||||||
 | 
					    except Exception, e:
 | 
				
			||||||
 | 
					        print "Failed connection to test db:", e.__class__.__name__, e
 | 
				
			||||||
 | 
					        print "Please set env vars 'PSYCOPG2_TESTDB*' to valid values."
 | 
				
			||||||
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        cnn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suite = unittest.TestSuite()
 | 
					    suite = unittest.TestSuite()
 | 
				
			||||||
    suite.addTest(bug_gc.test_suite())
 | 
					    suite.addTest(bug_gc.test_suite())
 | 
				
			||||||
    suite.addTest(bugX000.test_suite())
 | 
					    suite.addTest(bugX000.test_suite())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,9 +14,11 @@
 | 
				
			||||||
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 | 
					# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 | 
				
			||||||
# License for more details.
 | 
					# License for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					from datetime import timedelta
 | 
				
			||||||
import psycopg2
 | 
					import psycopg2
 | 
				
			||||||
import psycopg2.extras
 | 
					import psycopg2.extras
 | 
				
			||||||
from testutils import unittest, skip_if_no_namedtuple
 | 
					from testutils import unittest, skip_before_postgres, skip_if_no_namedtuple
 | 
				
			||||||
from testconfig import dsn
 | 
					from testconfig import dsn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -261,6 +263,53 @@ class NamedTupleCursorTest(unittest.TestCase):
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            NamedTupleCursor._make_nt = f_orig
 | 
					            NamedTupleCursor._make_nt = f_orig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_if_no_namedtuple
 | 
				
			||||||
 | 
					    @skip_before_postgres(8, 0)
 | 
				
			||||||
 | 
					    def test_named(self):
 | 
				
			||||||
 | 
					        curs = self.conn.cursor('tmp')
 | 
				
			||||||
 | 
					        curs.execute("""select i from generate_series(0,9) i""")
 | 
				
			||||||
 | 
					        recs = []
 | 
				
			||||||
 | 
					        recs.extend(curs.fetchmany(5))
 | 
				
			||||||
 | 
					        recs.append(curs.fetchone())
 | 
				
			||||||
 | 
					        recs.extend(curs.fetchall())
 | 
				
			||||||
 | 
					        self.assertEqual(range(10), [t.i for t in recs])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_if_no_namedtuple
 | 
				
			||||||
 | 
					    def test_named_fetchone(self):
 | 
				
			||||||
 | 
					        curs = self.conn.cursor('tmp')
 | 
				
			||||||
 | 
					        curs.execute("""select 42 as i""")
 | 
				
			||||||
 | 
					        t = curs.fetchone()
 | 
				
			||||||
 | 
					        self.assertEqual(t.i, 42)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_if_no_namedtuple
 | 
				
			||||||
 | 
					    def test_named_fetchmany(self):
 | 
				
			||||||
 | 
					        curs = self.conn.cursor('tmp')
 | 
				
			||||||
 | 
					        curs.execute("""select 42 as i""")
 | 
				
			||||||
 | 
					        recs = curs.fetchmany(10)
 | 
				
			||||||
 | 
					        self.assertEqual(recs[0].i, 42)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_if_no_namedtuple
 | 
				
			||||||
 | 
					    def test_named_fetchall(self):
 | 
				
			||||||
 | 
					        curs = self.conn.cursor('tmp')
 | 
				
			||||||
 | 
					        curs.execute("""select 42 as i""")
 | 
				
			||||||
 | 
					        recs = curs.fetchall()
 | 
				
			||||||
 | 
					        self.assertEqual(recs[0].i, 42)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_if_no_namedtuple
 | 
				
			||||||
 | 
					    @skip_before_postgres(8, 2)
 | 
				
			||||||
 | 
					    def test_not_greedy(self):
 | 
				
			||||||
 | 
					        curs = self.conn.cursor('tmp')
 | 
				
			||||||
 | 
					        curs.itersize = 2
 | 
				
			||||||
 | 
					        curs.execute("""select clock_timestamp() as ts from generate_series(1,3)""")
 | 
				
			||||||
 | 
					        recs = []
 | 
				
			||||||
 | 
					        for t in curs:
 | 
				
			||||||
 | 
					            time.sleep(0.01)
 | 
				
			||||||
 | 
					            recs.append(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check that the dataset was not fetched in a single gulp
 | 
				
			||||||
 | 
					        self.assert_(recs[1].ts - recs[0].ts < timedelta(seconds=0.005))
 | 
				
			||||||
 | 
					        self.assert_(recs[2].ts - recs[1].ts > timedelta(seconds=0.0099))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_suite():
 | 
					def test_suite():
 | 
				
			||||||
    return unittest.TestLoader().loadTestsFromName(__name__)
 | 
					    return unittest.TestLoader().loadTestsFromName(__name__)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,7 @@
 | 
				
			||||||
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 | 
					# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 | 
				
			||||||
# License for more details.
 | 
					# License for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
from testutils import unittest, decorate_all_tests, skip_before_postgres
 | 
					from testutils import unittest, decorate_all_tests, skip_before_postgres
 | 
				
			||||||
| 
						 | 
					@ -141,6 +142,19 @@ class ConnectionTests(unittest.TestCase):
 | 
				
			||||||
        cur.execute("select 'foo'::text;")
 | 
					        cur.execute("select 'foo'::text;")
 | 
				
			||||||
        self.assertEqual(cur.fetchone()[0], u'foo')
 | 
					        self.assertEqual(cur.fetchone()[0], u'foo')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_connect_nonnormal_envvar(self):
 | 
				
			||||||
 | 
					        # We must perform encoding normalization at connection time
 | 
				
			||||||
 | 
					        self.conn.close()
 | 
				
			||||||
 | 
					        oldenc = os.environ.get('PGCLIENTENCODING')
 | 
				
			||||||
 | 
					        os.environ['PGCLIENTENCODING'] = 'utf-8'    # malformed spelling
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.conn = psycopg2.connect(dsn)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            if oldenc is not None:
 | 
				
			||||||
 | 
					                os.environ['PGCLIENTENCODING'] = oldenc
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                del os.environ['PGCLIENTENCODING']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_weakref(self):
 | 
					    def test_weakref(self):
 | 
				
			||||||
        from weakref import ref
 | 
					        from weakref import ref
 | 
				
			||||||
        conn = psycopg2.connect(dsn)
 | 
					        conn = psycopg2.connect(dsn)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,6 +37,12 @@ class CursorTests(unittest.TestCase):
 | 
				
			||||||
    def tearDown(self):
 | 
					    def tearDown(self):
 | 
				
			||||||
        self.conn.close()
 | 
					        self.conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_empty_query(self):
 | 
				
			||||||
 | 
					        cur = self.conn.cursor()
 | 
				
			||||||
 | 
					        self.assertRaises(psycopg2.ProgrammingError, cur.execute, "")
 | 
				
			||||||
 | 
					        self.assertRaises(psycopg2.ProgrammingError, cur.execute, " ")
 | 
				
			||||||
 | 
					        self.assertRaises(psycopg2.ProgrammingError, cur.execute, ";")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_executemany_propagate_exceptions(self):
 | 
					    def test_executemany_propagate_exceptions(self):
 | 
				
			||||||
        conn = self.conn
 | 
					        conn = self.conn
 | 
				
			||||||
        cur = conn.cursor()
 | 
					        cur = conn.cursor()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -140,24 +140,6 @@ def skip_if_no_namedtuple(f):
 | 
				
			||||||
    return skip_if_no_namedtuple_
 | 
					    return skip_if_no_namedtuple_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def skip_if_broken_hex_binary(f):
 | 
					 | 
				
			||||||
    """Decorator to detect libpq < 9.0 unable to parse bytea in hex format"""
 | 
					 | 
				
			||||||
    def cope_with_hex_binary_(self):
 | 
					 | 
				
			||||||
        from psycopg2 import InterfaceError
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            return f(self)
 | 
					 | 
				
			||||||
        except InterfaceError, e:
 | 
					 | 
				
			||||||
            if '9.0' in str(e) and self.conn.server_version >= 90000:
 | 
					 | 
				
			||||||
                return self.skipTest(
 | 
					 | 
				
			||||||
                    # FIXME: we are only assuming the libpq is older here,
 | 
					 | 
				
			||||||
                    # but we don't have a reliable way to detect the libpq
 | 
					 | 
				
			||||||
                    # version, not pre-9 at least.
 | 
					 | 
				
			||||||
                    "bytea broken with server >= 9.0, libpq < 9")
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return cope_with_hex_binary_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def skip_if_no_iobase(f):
 | 
					def skip_if_no_iobase(f):
 | 
				
			||||||
    """Skip a test if io.TextIOBase is not available."""
 | 
					    """Skip a test if io.TextIOBase is not available."""
 | 
				
			||||||
    def skip_if_no_iobase_(self):
 | 
					    def skip_if_no_iobase_(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ except:
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import testutils
 | 
					import testutils
 | 
				
			||||||
from testutils import unittest, skip_if_broken_hex_binary
 | 
					from testutils import unittest, decorate_all_tests
 | 
				
			||||||
from testconfig import dsn
 | 
					from testconfig import dsn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import psycopg2
 | 
					import psycopg2
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        s = self.execute("SELECT %s AS foo", (float("-inf"),))
 | 
					        s = self.execute("SELECT %s AS foo", (float("-inf"),))
 | 
				
			||||||
        self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s))      
 | 
					        self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s))      
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    def testBinary(self):
 | 
					    def testBinary(self):
 | 
				
			||||||
        if sys.version_info[0] < 3:
 | 
					        if sys.version_info[0] < 3:
 | 
				
			||||||
            s = ''.join([chr(x) for x in range(256)])
 | 
					            s = ''.join([chr(x) for x in range(256)])
 | 
				
			||||||
| 
						 | 
					@ -143,7 +142,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
            b = psycopg2.Binary(bytes([]))
 | 
					            b = psycopg2.Binary(bytes([]))
 | 
				
			||||||
            self.assertEqual(str(b), "''::bytea")
 | 
					            self.assertEqual(str(b), "''::bytea")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    def testBinaryRoundTrip(self):
 | 
					    def testBinaryRoundTrip(self):
 | 
				
			||||||
        # test to make sure buffers returned by psycopg2 are
 | 
					        # test to make sure buffers returned by psycopg2 are
 | 
				
			||||||
        # understood by execute:
 | 
					        # understood by execute:
 | 
				
			||||||
| 
						 | 
					@ -191,7 +189,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        s = self.execute("SELECT '{}'::text AS foo")
 | 
					        s = self.execute("SELECT '{}'::text AS foo")
 | 
				
			||||||
        self.failUnlessEqual(s, "{}")
 | 
					        self.failUnlessEqual(s, "{}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    @testutils.skip_from_python(3)
 | 
					    @testutils.skip_from_python(3)
 | 
				
			||||||
    def testTypeRoundtripBuffer(self):
 | 
					    def testTypeRoundtripBuffer(self):
 | 
				
			||||||
        o1 = buffer("".join(map(chr, range(256))))
 | 
					        o1 = buffer("".join(map(chr, range(256))))
 | 
				
			||||||
| 
						 | 
					@ -204,7 +201,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        self.assertEqual(type(o1), type(o2))
 | 
					        self.assertEqual(type(o1), type(o2))
 | 
				
			||||||
        self.assertEqual(str(o1), str(o2))
 | 
					        self.assertEqual(str(o1), str(o2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    @testutils.skip_from_python(3)
 | 
					    @testutils.skip_from_python(3)
 | 
				
			||||||
    def testTypeRoundtripBufferArray(self):
 | 
					    def testTypeRoundtripBufferArray(self):
 | 
				
			||||||
        o1 = buffer("".join(map(chr, range(256))))
 | 
					        o1 = buffer("".join(map(chr, range(256))))
 | 
				
			||||||
| 
						 | 
					@ -213,7 +209,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        self.assertEqual(type(o1[0]), type(o2[0]))
 | 
					        self.assertEqual(type(o1[0]), type(o2[0]))
 | 
				
			||||||
        self.assertEqual(str(o1[0]), str(o2[0]))
 | 
					        self.assertEqual(str(o1[0]), str(o2[0]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    @testutils.skip_before_python(3)
 | 
					    @testutils.skip_before_python(3)
 | 
				
			||||||
    def testTypeRoundtripBytes(self):
 | 
					    def testTypeRoundtripBytes(self):
 | 
				
			||||||
        o1 = bytes(range(256))
 | 
					        o1 = bytes(range(256))
 | 
				
			||||||
| 
						 | 
					@ -225,7 +220,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        o2 = self.execute("select %s;", (o1,))
 | 
					        o2 = self.execute("select %s;", (o1,))
 | 
				
			||||||
        self.assertEqual(memoryview, type(o2))
 | 
					        self.assertEqual(memoryview, type(o2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    @testutils.skip_before_python(3)
 | 
					    @testutils.skip_before_python(3)
 | 
				
			||||||
    def testTypeRoundtripBytesArray(self):
 | 
					    def testTypeRoundtripBytesArray(self):
 | 
				
			||||||
        o1 = bytes(range(256))
 | 
					        o1 = bytes(range(256))
 | 
				
			||||||
| 
						 | 
					@ -233,7 +227,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        o2 = self.execute("select %s;", (o1,))
 | 
					        o2 = self.execute("select %s;", (o1,))
 | 
				
			||||||
        self.assertEqual(memoryview, type(o2[0]))
 | 
					        self.assertEqual(memoryview, type(o2[0]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    @testutils.skip_before_python(2, 6)
 | 
					    @testutils.skip_before_python(2, 6)
 | 
				
			||||||
    def testAdaptBytearray(self):
 | 
					    def testAdaptBytearray(self):
 | 
				
			||||||
        o1 = bytearray(range(256))
 | 
					        o1 = bytearray(range(256))
 | 
				
			||||||
| 
						 | 
					@ -258,7 +251,6 @@ class TypesBasicTests(unittest.TestCase):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.assertEqual(memoryview, type(o2))
 | 
					            self.assertEqual(memoryview, type(o2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_if_broken_hex_binary
 | 
					 | 
				
			||||||
    @testutils.skip_before_python(2, 7)
 | 
					    @testutils.skip_before_python(2, 7)
 | 
				
			||||||
    def testAdaptMemoryview(self):
 | 
					    def testAdaptMemoryview(self):
 | 
				
			||||||
        o1 = memoryview(bytearray(range(256)))
 | 
					        o1 = memoryview(bytearray(range(256)))
 | 
				
			||||||
| 
						 | 
					@ -335,6 +327,104 @@ class AdaptSubclassTest(unittest.TestCase):
 | 
				
			||||||
           del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
 | 
					           del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ByteaParserTest(unittest.TestCase):
 | 
				
			||||||
 | 
					    """Unit test for our bytea format parser."""
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self._cast = self._import_cast()
 | 
				
			||||||
 | 
					        except Exception, e:
 | 
				
			||||||
 | 
					            self._cast = None
 | 
				
			||||||
 | 
					            self._exc = e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _import_cast(self):
 | 
				
			||||||
 | 
					        """Use ctypes to access the C function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Raise any sort of error: we just support this where ctypes works as
 | 
				
			||||||
 | 
					        expected.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        import ctypes
 | 
				
			||||||
 | 
					        lib = ctypes.cdll.LoadLibrary(psycopg2._psycopg.__file__)
 | 
				
			||||||
 | 
					        cast = lib.typecast_BINARY_cast
 | 
				
			||||||
 | 
					        cast.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.py_object]
 | 
				
			||||||
 | 
					        cast.restype = ctypes.py_object
 | 
				
			||||||
 | 
					        return cast
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cast(self, buffer):
 | 
				
			||||||
 | 
					        """Cast a buffer from the output format"""
 | 
				
			||||||
 | 
					        l = buffer and len(buffer) or 0
 | 
				
			||||||
 | 
					        rv = self._cast(buffer, l, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if rv is None:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if sys.version_info[0] < 3:
 | 
				
			||||||
 | 
					            return str(rv)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return rv.tobytes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_null(self):
 | 
				
			||||||
 | 
					        rv = self.cast(None)
 | 
				
			||||||
 | 
					        self.assertEqual(rv, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_blank(self):
 | 
				
			||||||
 | 
					        rv = self.cast(b(''))
 | 
				
			||||||
 | 
					        self.assertEqual(rv, b(''))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_blank_hex(self):
 | 
				
			||||||
 | 
					        # Reported as problematic in ticket #48
 | 
				
			||||||
 | 
					        rv = self.cast(b('\\x'))
 | 
				
			||||||
 | 
					        self.assertEqual(rv, b(''))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_full_hex(self, upper=False):
 | 
				
			||||||
 | 
					        buf = ''.join(("%02x" % i) for i in range(256))
 | 
				
			||||||
 | 
					        if upper: buf = buf.upper()
 | 
				
			||||||
 | 
					        buf = '\\x' + buf
 | 
				
			||||||
 | 
					        rv = self.cast(b(buf))
 | 
				
			||||||
 | 
					        if sys.version_info[0] < 3:
 | 
				
			||||||
 | 
					            self.assertEqual(rv, ''.join(map(chr, range(256))))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.assertEqual(rv, bytes(range(256)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_full_hex_upper(self):
 | 
				
			||||||
 | 
					        return self.test_full_hex(upper=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_full_escaped_octal(self):
 | 
				
			||||||
 | 
					        buf = ''.join(("\\%03o" % i) for i in range(256))
 | 
				
			||||||
 | 
					        rv = self.cast(b(buf))
 | 
				
			||||||
 | 
					        if sys.version_info[0] < 3:
 | 
				
			||||||
 | 
					            self.assertEqual(rv, ''.join(map(chr, range(256))))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.assertEqual(rv, bytes(range(256)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_escaped_mixed(self):
 | 
				
			||||||
 | 
					        import string
 | 
				
			||||||
 | 
					        buf = ''.join(("\\%03o" % i) for i in range(32))
 | 
				
			||||||
 | 
					        buf += string.ascii_letters
 | 
				
			||||||
 | 
					        buf += ''.join('\\' + c for c in string.ascii_letters)
 | 
				
			||||||
 | 
					        buf += '\\\\'
 | 
				
			||||||
 | 
					        rv = self.cast(b(buf))
 | 
				
			||||||
 | 
					        if sys.version_info[0] < 3:
 | 
				
			||||||
 | 
					            tgt = ''.join(map(chr, range(32))) \
 | 
				
			||||||
 | 
					                + string.ascii_letters * 2 + '\\'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            tgt = bytes(range(32)) + \
 | 
				
			||||||
 | 
					                (string.ascii_letters * 2 + '\\').encode('ascii')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(rv, tgt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def skip_if_cant_cast(f):
 | 
				
			||||||
 | 
					    def skip_if_cant_cast_(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        if self._cast is None:
 | 
				
			||||||
 | 
					            return self.skipTest("can't test bytea parser: %s - %s"
 | 
				
			||||||
 | 
					                % (self._exc.__class__.__name__, self._exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return f(self, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return skip_if_cant_cast_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					decorate_all_tests(ByteaParserTest, skip_if_cant_cast)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_suite():
 | 
					def test_suite():
 | 
				
			||||||
    return unittest.TestLoader().loadTestsFromName(__name__)
 | 
					    return unittest.TestLoader().loadTestsFromName(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user