Merge branch 'devel'

This commit is contained in:
Federico Di Gregorio 2011-06-13 18:53:48 +02:00
commit f8ff2ccc49
39 changed files with 1824 additions and 489 deletions

24
NEWS
View File

@ -1,3 +1,27 @@
What's new in psycopg 2.4.2
---------------------------
- Added 'set_session()' method and 'autocommit' property to the
connection. Added support for read-only sessions and, for PostgreSQL
9.1, for the "repeatable read" isolation level and the "deferrable"
transaction property.
- Psycopg doesn't execute queries at connection time to find the
default isolation level.
- Fixed bug with multithread code potentially causing loss of sync
with the server communication or lock of the client (ticket #55).
- Don't fail import if mx.DateTime module can't be found, even if its
support was built (ticket #53).
- Fixed escape for negative numbers prefixed by minus operator
(ticket #57).
- Fixed refcount issue during copy. Reported and fixed by Dave
Malcolm (ticket #58, Red Hat Bug 711095).
- Trying to execute concurrent operations on the same connection
through concurrent green thread results in an error instead of a
deadlock.
- Fixed detection of pg_config on Window. Report and fix, plus some
long needed setup.py cleanup by Steve Lacy: thanks!
What's new in psycopg 2.4.1 What's new in psycopg 2.4.1
--------------------------- ---------------------------

View File

@ -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', '2.4.1') ALLOWED_PSYCOPG_VERSIONS = ('2.4-beta1', '2.4-beta2', '2.4', '2.4.1', '2.4.2')
import sys import sys
import time import time

View File

@ -239,9 +239,8 @@ be sent from Python code simply executing a :sql:`NOTIFY` command in an
`~cursor.execute()` call. `~cursor.execute()` call.
Because of the way sessions interact with notifications (see |NOTIFY|_ Because of the way sessions interact with notifications (see |NOTIFY|_
documentation), you should keep the connection in :ref:`autocommit documentation), you should keep the connection in `~connection.autocommit`
<autocommit>` mode if you wish to receive or send notifications in a timely mode if you wish to receive or send notifications in a timely manner.
manner.
.. |LISTEN| replace:: :sql:`LISTEN` .. |LISTEN| replace:: :sql:`LISTEN`
.. _LISTEN: http://www.postgresql.org/docs/9.0/static/sql-listen.html .. _LISTEN: http://www.postgresql.org/docs/9.0/static/sql-listen.html
@ -373,12 +372,14 @@ When an asynchronous query is being executed, `connection.isexecuting()` returns
connection. connection.
There are several limitations in using asynchronous connections: the There are several limitations in using asynchronous connections: the
connection is always in :ref:`autocommit <autocommit>` mode and it is not connection is always in `~connection.autocommit` mode and it is not
possible to change it using `~connection.set_isolation_level()`. So a possible to change it. So a
transaction is not implicitly started at the first query and is not possible transaction is not implicitly started at the first query and is not possible
to use methods `~connection.commit()` and `~connection.rollback()`: you can to use methods `~connection.commit()` and `~connection.rollback()`: you can
manually control transactions using `~cursor.execute()` to send database manually control transactions using `~cursor.execute()` to send database
commands such as :sql:`BEGIN`, :sql:`COMMIT` and :sql:`ROLLBACK`. commands such as :sql:`BEGIN`, :sql:`COMMIT` and :sql:`ROLLBACK`. Similarly
`~connection.set_session()` can't be used but it is still possible to invoke the
:sql:`SET` command with the proper :sql:`default_transaction_...` parameter.
With asynchronous connections it is also not possible to use With asynchronous connections it is also not possible to use
`~connection.set_client_encoding()`, `~cursor.executemany()`, :ref:`large `~connection.set_client_encoding()`, `~cursor.executemany()`, :ref:`large
@ -431,11 +432,9 @@ SQLAlchemy_) to be used in coroutine-based programs.
.. warning:: .. warning::
Psycopg connections are not *green thread safe* and can't be used Psycopg connections are not *green thread safe* and can't be used
concurrently by different green threads. Each connection has a lock concurrently by different green threads. Trying to execute more than one
used to serialize requests from different cursors to the backend process. command at time using one cursor per thread will result in an error (or a
The lock is held for the duration of the command: if the control switched deadlock on versions before 2.4.2).
to a different thread and the latter tried to access the same connection,
the result would be a deadlock.
Therefore, programmers are advised to either avoid sharing connections Therefore, programmers are advised to either avoid sharing connections
between coroutines or to use a library-friendly lock to synchronize shared between coroutines or to use a library-friendly lock to synchronize shared

View File

@ -111,10 +111,10 @@ rst_epilog = """
.. _DBAPI: http://www.python.org/dev/peps/pep-0249/ .. _DBAPI: http://www.python.org/dev/peps/pep-0249/
.. _transaction isolation level: .. _transaction isolation level:
http://www.postgresql.org/docs/9.0/static/transaction-iso.html http://www.postgresql.org/docs/9.1/static/transaction-iso.html
.. _serializable isolation level: .. _serializable isolation level:
http://www.postgresql.org/docs/9.0/static/transaction-iso.html#XACT-SERIALIZABLE http://www.postgresql.org/docs/9.1/static/transaction-iso.html#XACT-SERIALIZABLE
.. _mx.DateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/ .. _mx.DateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/

View File

@ -327,11 +327,93 @@ The ``connection`` class
pair: Transaction; Autocommit pair: Transaction; Autocommit
pair: Transaction; Isolation level pair: Transaction; Isolation level
.. _autocommit: .. method:: set_session([isolation_level,] [readonly,] [deferrable,] [autocommit])
Set one or more parameters for the next transactions or statements in
the current session. See |SET TRANSACTION|_ for further details.
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
.. _SET TRANSACTION: http://www.postgresql.org/docs/9.1/static/sql-set-transaction.html
:param isolation_level: set the `isolation level`_ for the next
transactions/statements. The value can be one of the
:ref:`constants <isolation-level-constants>` defined in the
`~psycopg2.extensions` module or one of the literal values
``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
``SERIALIZABLE``.
:param readonly: if `!True`, set the connection to read only;
read/write if `!False`.
:param deferrable: if `!True`, set the connection to deferrable;
non deferrable if `!False`. Only available from PostgreSQL 9.1.
:param autocommit: switch the connection to autocommit mode: not a
PostgreSQL session setting but an alias for setting the
`autocommit` attribute.
The parameters *isolation_level*, *readonly* and *deferrable* also
accept the string ``DEFAULT`` as a value: the effect is to reset the
parameter to the server default.
.. _isolation level:
http://www.postgresql.org/docs/9.1/static/transaction-iso.html
The function must be invoked with no transaction in progress. At every
function invocation, only the specified parameters are changed.
The default for the values are defined by the server configuration:
see values for |default_transaction_isolation|__,
|default_transaction_read_only|__, |default_transaction_deferrable|__.
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
.. __: http://www.postgresql.org/docs/9.1/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION
.. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only`
.. __: http://www.postgresql.org/docs/9.1/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
.. __: http://www.postgresql.org/docs/9.1/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE
.. note::
There is currently no builtin method to read the current value for
the parameters: use :sql:`SHOW default_transaction_...` to read
the values from the backend.
.. versionadded:: 2.4.2
.. attribute:: autocommit
Read/write attribute: if `!True`, no transaction is handled by the
driver and every statement sent to the backend has immediate effect;
if `!False` a new transaction is started at the first command
execution: the methods `commit()` or `rollback()` must be manually
invoked to terminate the transaction.
The autocommit mode is useful to execute commands requiring to be run
outside a transaction, such as :sql:`CREATE DATABASE` or
:sql:`VACUUM`.
The default is `!False` (manual commit) as per DBAPI specification.
.. warning::
By default, any query execution, including a simple :sql:`SELECT`
will start a transaction: for long-running programs, if no further
action is taken, the session will remain "idle in transaction", a
condition non desiderable for several reasons (locks are held by
the session, tables bloat...). For long lived scripts, either
ensure to terminate a transaction as soon as possible or use an
autocommit connection.
.. versionadded:: 2.4.2
.. attribute:: isolation_level .. attribute:: isolation_level
.. method:: set_isolation_level(level) .. method:: set_isolation_level(level)
.. note::
From version 2.4.2, `set_session()` and `autocommit`, offer
finer control on the transaction characteristics.
Read or set the `transaction isolation level`_ for the current session. Read or set the `transaction isolation level`_ for the current session.
The level defines the different phenomena that can happen in the The level defines the different phenomena that can happen in the
database between concurrent transactions. database between concurrent transactions.

View File

@ -444,20 +444,23 @@ The ``cursor`` class
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard. The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
As such, its support is a Psycopg extension to the |DBAPI|. As such, its support is a Psycopg extension to the |DBAPI|.
.. method:: copy_from(file, table, sep='\\t', null='\\N', columns=None) .. method:: copy_from(file, table, sep='\\t', null='\\N', size=8192, columns=None)
Read data *from* the file-like object *file* appending them to Read data *from* the file-like object *file* appending them to
the table named *table*. *file* must have both the table named *table*. See :ref:`copy` for an overview.
`!read()` and `!readline()` method. See :ref:`copy` for an
overview.
The optional argument *sep* is the columns separator and :param file: file-like object to read data from. It must have both
*null* represents :sql:`NULL` values in the file. `!read()` and `!readline()` methods.
:param table: name of the table to copy data into.
:param sep: columns separator expected in the file. Defaults to a tab.
:param null: textual representation of :sql:`NULL` in the file.
:param size: size of the buffer used to read from the file.
:param columns: iterable with name of the columns to import.
The length and types should match the content of the file to read.
If not specified, it is assumed that the entire table matches the
file structure.
The *columns* argument is a sequence containing the name of the Example::
fields where the read data will be entered. Its length and column
type should match the content of the read file. If not specifies, it
is assumed that the entire table matches the file structure.
>>> f = StringIO("42\tfoo\n74\tbar\n") >>> f = StringIO("42\tfoo\n74\tbar\n")
>>> cur.copy_from(f, 'test', columns=('num', 'data')) >>> cur.copy_from(f, 'test', columns=('num', 'data'))
@ -476,14 +479,17 @@ The ``cursor`` class
.. method:: copy_to(file, table, sep='\\t', null='\\N', columns=None) .. method:: copy_to(file, table, sep='\\t', null='\\N', columns=None)
Write the content of the table named *table* *to* the file-like Write the content of the table named *table* *to* the file-like
object *file*. *file* must have a `!write()` method. object *file*. See :ref:`copy` for an overview.
See :ref:`copy` for an overview.
The optional argument *sep* is the columns separator and :param file: file-like object to write data into. It must have a
*null* represents :sql:`NULL` values in the file. `!write()` method.
:param table: name of the table to copy data from.
:param sep: columns separator expected in the file. Defaults to a tab.
:param null: textual representation of :sql:`NULL` in the file.
:param columns: iterable with name of the columns to export.
If not specified, export all the columns.
The *columns* argument is a sequence of field names: if not Example::
`!None` only the specified fields will be included in the dump.
>>> cur.copy_to(sys.stdout, 'test', sep="|") >>> cur.copy_to(sys.stdout, 'test', sep="|")
1|100|abc'def 1|100|abc'def
@ -499,17 +505,18 @@ The ``cursor`` class
from the backend. from the backend.
.. method:: copy_expert(sql, file [, size]) .. method:: copy_expert(sql, file, size=8192)
Submit a user-composed :sql:`COPY` statement. The method is useful to Submit a user-composed :sql:`COPY` statement. The method is useful to
handle all the parameters that PostgreSQL makes available (see handle all the parameters that PostgreSQL makes available (see
|COPY|__ command documentation). |COPY|__ command documentation).
*file* must be an open, readable file for :sql:`COPY FROM` or an :param sql: the :sql:`COPY` statement to execute.
open, writeable file for :sql:`COPY TO`. The optional *size* :param file: a file-like object; must be a readable file for
argument, when specified for a :sql:`COPY FROM` statement, will be :sql:`COPY FROM` or an writeable file for :sql:`COPY TO`.
passed to *file*\ 's read method to control the read buffer :param size: size of the read buffer to be used in :sql:`COPY FROM`.
size.
Example:
>>> cur.copy_expert("COPY test TO STDOUT WITH CSV HEADER", sys.stdout) >>> cur.copy_expert("COPY test TO STDOUT WITH CSV HEADER", sys.stdout)
id,num,data id,num,data

View File

@ -22,8 +22,8 @@ Why does `!psycopg2` leave database sessions "idle in transaction"?
call one of the transaction closing methods before leaving the connection call one of the transaction closing methods before leaving the connection
unused for a long time (which may also be a few seconds, depending on the unused for a long time (which may also be a few seconds, depending on the
concurrency level in your database). Alternatively you can use a concurrency level in your database). Alternatively you can use a
connection in :ref:`autocommit <autocommit>` mode to avoid a new connection in `~connection.autocommit` mode to avoid a new transaction to
transaction to be started at the first command. be started at the first command.
I receive the error *current transaction is aborted, commands ignored until end of transaction block* and can't do anything else! I receive the error *current transaction is aborted, commands ignored until end of transaction block* and can't do anything else!
There was a problem *in the previous* command to the database, which There was a problem *in the previous* command to the database, which

View File

@ -489,7 +489,7 @@ rounded to the nearest minute, with an error of up to 30 seconds.
versions use `psycopg2.extras.register_tstz_w_secs()`. versions use `psycopg2.extras.register_tstz_w_secs()`.
.. index:: Transaction, Begin, Commit, Rollback, Autocommit .. index:: Transaction, Begin, Commit, Rollback, Autocommit, Read only
.. _transactions-control: .. _transactions-control:
@ -503,7 +503,7 @@ The following database commands will be executed in the context of the same
transaction -- not only the commands issued by the first cursor, but the ones transaction -- not only the commands issued by the first cursor, but the ones
issued by all the cursors created by the same connection. Should any command issued by all the cursors created by the same connection. Should any command
fail, the transaction will be aborted and no further command will be executed fail, the transaction will be aborted and no further command will be executed
until a call to the `connection.rollback()` method. until a call to the `~connection.rollback()` method.
The connection is responsible to terminate its transaction, calling either the The connection is responsible to terminate its transaction, calling either the
`~connection.commit()` or `~connection.rollback()` method. Committed `~connection.commit()` or `~connection.rollback()` method. Committed
@ -516,9 +516,23 @@ It is possible to set the connection in *autocommit* mode: this way all the
commands executed will be immediately committed and no rollback is possible. A commands executed will be immediately committed and no rollback is possible. A
few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`...) require to be run few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`...) require to be run
outside any transaction: in order to be able to run these commands from outside any transaction: in order to be able to run these commands from
Psycopg, the session must be in autocommit mode. Read the documentation for Psycopg, the session must be in autocommit mode: you can use the
`connection.set_isolation_level()` to know how to change the commit mode. `~connection.autocommit` property (`~connection.set_isolation_level()` in
older versions).
.. warning::
By default even a simple :sql:`SELECT` will start a transaction: in
long-running programs, if no further action is taken, the session will
remain "idle in transaction", a condition non desiderable for several
reasons (locks are held by the session, tables bloat...). For long lived
scripts, either ensure to terminate a transaction as soon as possible or
use an autocommit connection.
A few other transaction properties can be set session-wide by the
`!connection`: for instance it is possible to have read-only transactions or
change the isolation level. See the `~connection.set_session()` method for all
the details.
.. index:: .. index::
@ -594,8 +608,8 @@ forking web deploy method such as FastCGI ensure to create the connections
.. __: http://www.postgresql.org/docs/9.0/static/libpq-connect.html#LIBPQ-CONNECT .. __: http://www.postgresql.org/docs/9.0/static/libpq-connect.html#LIBPQ-CONNECT
Connections shouldn't be shared either by different green threads: doing so Connections shouldn't be shared either by different green threads: see
may result in a deadlock. See :ref:`green-support` for further details. :ref:`green-support` for further details.

View File

@ -40,20 +40,16 @@ Homepage: http://initd.org/projects/psycopg2
# Import modules needed by _psycopg to allow tools like py2exe to do # Import modules needed by _psycopg to allow tools like py2exe to do
# their work without bothering about the module dependencies. # their work without bothering about the module dependencies.
#
# TODO: we should probably use the Warnings framework to signal a missing
# module instead of raising an exception (in case we're running a thin
# embedded Python or something even more devious.)
import sys, warnings import sys, warnings
if sys.version_info[0] >= 2 and sys.version_info[1] >= 3: if sys.version_info >= (2, 3):
try: try:
import datetime as _psycopg_needs_datetime import datetime as _psycopg_needs_datetime
except: except:
warnings.warn( warnings.warn(
"can't import datetime module probably needed by _psycopg", "can't import datetime module probably needed by _psycopg",
RuntimeWarning) RuntimeWarning)
if sys.version_info[0] >= 2 and sys.version_info[1] >= 4: if sys.version_info >= (2, 4):
try: try:
import decimal as _psycopg_needs_decimal import decimal as _psycopg_needs_decimal
except: except:

View File

@ -39,7 +39,7 @@ from psycopg2._psycopg import DECIMALARRAY, FLOATARRAY, INTEGERARRAY, INTERVALAR
from psycopg2._psycopg import LONGINTEGERARRAY, ROWIDARRAY, STRINGARRAY, TIMEARRAY from psycopg2._psycopg import LONGINTEGERARRAY, ROWIDARRAY, STRINGARRAY, TIMEARRAY
from psycopg2._psycopg import UNICODEARRAY from psycopg2._psycopg import UNICODEARRAY
from psycopg2._psycopg import Binary, Boolean, Float, QuotedString, AsIs from psycopg2._psycopg import Binary, Boolean, Int, Float, QuotedString, AsIs
try: try:
from psycopg2._psycopg import MXDATE, MXDATETIME, MXINTERVAL, MXTIME from psycopg2._psycopg import MXDATE, MXDATETIME, MXINTERVAL, MXTIME
from psycopg2._psycopg import MXDATEARRAY, MXDATETIMEARRAY, MXINTERVALARRAY, MXTIMEARRAY from psycopg2._psycopg import MXDATEARRAY, MXDATETIMEARRAY, MXINTERVALARRAY, MXTIMEARRAY
@ -68,13 +68,11 @@ except ImportError:
pass pass
"""Isolation level values.""" """Isolation level values."""
ISOLATION_LEVEL_AUTOCOMMIT = 0 ISOLATION_LEVEL_AUTOCOMMIT = 0
ISOLATION_LEVEL_READ_COMMITTED = 1 ISOLATION_LEVEL_READ_UNCOMMITTED = 1
ISOLATION_LEVEL_SERIALIZABLE = 2 ISOLATION_LEVEL_READ_COMMITTED = 2
ISOLATION_LEVEL_REPEATABLE_READ = 3
# PostgreSQL maps the the other standard values to already defined levels ISOLATION_LEVEL_SERIALIZABLE = 4
ISOLATION_LEVEL_REPEATABLE_READ = ISOLATION_LEVEL_SERIALIZABLE
ISOLATION_LEVEL_READ_UNCOMMITTED = ISOLATION_LEVEL_READ_COMMITTED
"""psycopg connection status values.""" """psycopg connection status values."""
STATUS_SETUP = 0 STATUS_SETUP = 0

View File

@ -45,11 +45,11 @@ typedef struct {
} pydatetimeObject; } pydatetimeObject;
HIDDEN int psyco_adapter_datetime_init(void);
/* functions exported to psycopgmodule.c */ /* functions exported to psycopgmodule.c */
#ifdef PSYCOPG_DEFAULT_PYDATETIME #ifdef PSYCOPG_DEFAULT_PYDATETIME
HIDDEN int psyco_adapter_datetime_init(void);
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args); HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
#define psyco_Date_doc \ #define psyco_Date_doc \
"Date(year, month, day) -> new date\n\n" \ "Date(year, month, day) -> new date\n\n" \

View File

@ -26,7 +26,6 @@
#define PSYCOPG_MODULE #define PSYCOPG_MODULE
#include "psycopg/psycopg.h" #include "psycopg/psycopg.h"
/* TODO: check if still compiles ok: I have no mx on this box */
#include "psycopg/adapter_mxdatetime.h" #include "psycopg/adapter_mxdatetime.h"
#include "psycopg/microprotocols_proto.h" #include "psycopg/microprotocols_proto.h"
@ -34,13 +33,16 @@
#include <string.h> #include <string.h>
/* Return 0 on success, -1 on failure, but don't set an exception */
int int
psyco_adapter_mxdatetime_init(void) psyco_adapter_mxdatetime_init(void)
{ {
Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime init"); Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime init");
if(mxDateTime_ImportModuleAndAPI()) { if (mxDateTime_ImportModuleAndAPI()) {
PyErr_SetString(PyExc_ImportError, "mx.DateTime initialization failed"); Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime initialization failed");
PyErr_Clear();
return -1; return -1;
} }
return 0; return 0;

View File

@ -41,8 +41,10 @@ pdecimal_getquoted(pdecimalObject *self, PyObject *args)
PyObject *check, *res = NULL; PyObject *check, *res = NULL;
check = PyObject_CallMethod(self->wrapped, "is_finite", NULL); check = PyObject_CallMethod(self->wrapped, "is_finite", NULL);
if (check == Py_True) { if (check == Py_True) {
res = PyObject_Str(self->wrapped); if (!(res = PyObject_Str(self->wrapped))) {
goto end; goto end;
}
goto output;
} }
else if (check) { else if (check) {
res = Bytes_FromString("'NaN'::numeric"); res = Bytes_FromString("'NaN'::numeric");
@ -70,16 +72,39 @@ pdecimal_getquoted(pdecimalObject *self, PyObject *args)
goto end; goto end;
} }
res = PyObject_Str(self->wrapped); /* wrapped is finite */
if (!(res = PyObject_Str(self->wrapped))) {
goto end;
}
/* res may be unicode and may suffer for issue #57 */
output:
#if PY_MAJOR_VERSION > 2 #if PY_MAJOR_VERSION > 2
/* unicode to bytes in Py3 */ /* unicode to bytes in Py3 */
if (res) { {
PyObject *tmp = PyUnicode_AsUTF8String(res); PyObject *tmp = PyUnicode_AsUTF8String(res);
Py_DECREF(res); Py_DECREF(res);
res = tmp; if (!(res = tmp)) {
goto end;
}
} }
#endif #endif
if ('-' == Bytes_AS_STRING(res)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */
PyObject *tmp;
if (!(tmp = Bytes_FromString(" "))) {
Py_DECREF(res);
res = NULL;
goto end;
}
Bytes_ConcatAndDel(&tmp, res);
if (!(res = tmp)) {
goto end;
}
}
end: end:
Py_XDECREF(check); Py_XDECREF(check);
return res; return res;

View File

@ -49,18 +49,37 @@ pfloat_getquoted(pfloatObject *self, PyObject *args)
rv = Bytes_FromString("'-Infinity'::float"); rv = Bytes_FromString("'-Infinity'::float");
} }
else { else {
rv = PyObject_Repr(self->wrapped); if (!(rv = PyObject_Repr(self->wrapped))) {
goto exit;
}
#if PY_MAJOR_VERSION > 2 #if PY_MAJOR_VERSION > 2
/* unicode to bytes in Py3 */ /* unicode to bytes in Py3 */
if (rv) { {
PyObject *tmp = PyUnicode_AsUTF8String(rv); PyObject *tmp = PyUnicode_AsUTF8String(rv);
Py_DECREF(rv); Py_DECREF(rv);
rv = tmp; if (!(rv = tmp)) {
goto exit;
}
} }
#endif #endif
if ('-' == Bytes_AS_STRING(rv)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */
PyObject *tmp;
if (!(tmp = Bytes_FromString(" "))) {
Py_DECREF(rv);
rv = NULL;
goto exit;
}
Bytes_ConcatAndDel(&tmp, rv);
if (!(rv = tmp)) {
goto exit;
}
}
} }
exit:
return rv; return rv;
} }

266
psycopg/adapter_pint.c Normal file
View File

@ -0,0 +1,266 @@
/* adapter_int.c - psycopg pint type wrapper implementation
*
* Copyright (C) 2011 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"
#include "psycopg/adapter_pint.h"
#include "psycopg/microprotocols_proto.h"
/** the Int object **/
static PyObject *
pint_getquoted(pintObject *self, PyObject *args)
{
PyObject *res;
if (!(res = PyObject_Str(self->wrapped))) {
goto exit;
}
#if PY_MAJOR_VERSION > 2
/* unicode to bytes in Py3 */
{
PyObject *tmp = PyUnicode_AsUTF8String(res);
Py_DECREF(res);
if (!(res = tmp)) {
goto exit;
}
}
#endif
if ('-' == Bytes_AS_STRING(res)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */
PyObject *tmp;
if (!(tmp = Bytes_FromString(" "))) {
Py_DECREF(res);
res = NULL;
goto exit;
}
Bytes_ConcatAndDel(&tmp, res);
if (!(res = tmp)) {
goto exit;
}
}
exit:
return res;
}
static PyObject *
pint_str(pintObject *self)
{
return psycopg_ensure_text(pint_getquoted(self, NULL));
}
static PyObject *
pint_conform(pintObject *self, PyObject *args)
{
PyObject *res, *proto;
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
if (proto == (PyObject*)&isqlquoteType)
res = (PyObject*)self;
else
res = Py_None;
Py_INCREF(res);
return res;
}
/** the int object */
/* object member list */
static struct PyMemberDef pintObject_members[] = {
{"adapted", T_OBJECT, offsetof(pintObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef pintObject_methods[] = {
{"getquoted", (PyCFunction)pint_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"__conform__", (PyCFunction)pint_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
pint_setup(pintObject *self, PyObject *obj)
{
Dprintf("pint_setup: init pint object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("pint_setup: good pint object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static int
pint_traverse(PyObject *obj, visitproc visit, void *arg)
{
pintObject *self = (pintObject *)obj;
Py_VISIT(self->wrapped);
return 0;
}
static void
pint_dealloc(PyObject* obj)
{
pintObject *self = (pintObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("pint_dealloc: deleted pint object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
pint_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *o;
if (!PyArg_ParseTuple(args, "O", &o))
return -1;
return pint_setup((pintObject *)obj, o);
}
static PyObject *
pint_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
static void
pint_del(PyObject* self)
{
PyObject_GC_Del(self);
}
static PyObject *
pint_repr(pintObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Int object at %p>",
self);
}
/* object type */
#define pintType_doc \
"Int(str) -> new Int adapter object"
PyTypeObject pintType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Int",
sizeof(pintObject),
0,
pint_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)pint_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
(reprfunc)pint_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
pintType_doc, /*tp_doc*/
pint_traverse, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
/* Attribute descriptor and subclassing stuff */
pintObject_methods, /*tp_methods*/
pintObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
pint_init, /*tp_init*/
0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/
pint_new, /*tp_new*/
(freefunc)pint_del, /*tp_free Low-level free-memory routine */
0, /*tp_is_gc For PyObject_IS_GC */
0, /*tp_bases*/
0, /*tp_mro method resolution order */
0, /*tp_cache*/
0, /*tp_subclasses*/
0 /*tp_weaklist*/
};
/** module-level functions **/
PyObject *
psyco_Int(PyObject *module, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&pintType, obj, NULL);
}

53
psycopg/adapter_pint.h Normal file
View File

@ -0,0 +1,53 @@
/* adapter_pint.h - definition for the psycopg int type wrapper
*
* Copyright (C) 2011 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#ifndef PSYCOPG_PINT_H
#define PSYCOPG_PINT_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject pintType;
typedef struct {
PyObject_HEAD
/* this is the real object we wrap */
PyObject *wrapped;
} pintObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_Int(PyObject *module, PyObject *args);
#define psyco_Int_doc \
"Int(obj) -> new int value"
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_PINT_H) */

View File

@ -27,7 +27,7 @@
#define PSYCOPG_CONFIG_H 1 #define PSYCOPG_CONFIG_H 1
/* GCC 4.0 and later have support for specifying symbol visibility */ /* GCC 4.0 and later have support for specifying symbol visibility */
#if __GNUC__ >= 4 #if __GNUC__ >= 4 && !defined(__MINGW32__)
# define HIDDEN __attribute__((visibility("hidden"))) # define HIDDEN __attribute__((visibility("hidden")))
#else #else
# define HIDDEN # define HIDDEN
@ -136,6 +136,8 @@ static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake)
* in libxml2 code */ * in libxml2 code */
#define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \ #define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \
: ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0)) : ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0))
#define strcasecmp(x, y) lstrcmpi(x, y)
#endif #endif
#endif #endif

View File

@ -59,14 +59,6 @@ extern "C" {
later change it, she must know what she's doing... these are the queries we later change it, she must know what she's doing... these are the queries we
need to issue */ need to issue */
#define psyco_datestyle "SET DATESTYLE TO 'ISO'" #define psyco_datestyle "SET DATESTYLE TO 'ISO'"
#define psyco_transaction_isolation "SHOW default_transaction_isolation"
/* possible values for isolation_level */
typedef enum {
ISOLATION_LEVEL_AUTOCOMMIT = 0,
ISOLATION_LEVEL_READ_COMMITTED = 1,
ISOLATION_LEVEL_SERIALIZABLE = 2,
} conn_isolation_level_t;
extern HIDDEN PyTypeObject connectionType; extern HIDDEN PyTypeObject connectionType;
@ -87,7 +79,6 @@ typedef struct {
long int closed; /* 1 means connection has been closed; long int closed; /* 1 means connection has been closed;
2 that something horrible happened */ 2 that something horrible happened */
long int isolation_level; /* isolation level for this connection */
long int mark; /* number of commits/rollbacks done so far */ long int mark; /* number of commits/rollbacks done so far */
int status; /* status of the connection */ int status; /* status of the connection */
XidObject *tpc_xid; /* Transaction ID in two-phase commit */ XidObject *tpc_xid; /* Transaction ID in two-phase commit */
@ -99,7 +90,10 @@ typedef struct {
PGconn *pgconn; /* the postgresql connection */ PGconn *pgconn; /* the postgresql connection */
PGcancel *cancel; /* the cancellation structure */ PGcancel *cancel; /* the cancellation structure */
PyObject *async_cursor; /* weakref to a cursor executing an asynchronous query */ /* Weakref to the object executing an asynchronous query. The object
* is a cursor for async connections, but it may be something else
* for a green connection. If NULL, the connection is idle. */
PyObject *async_cursor;
int async_status; /* asynchronous execution status */ int async_status; /* asynchronous execution status */
/* notice processing */ /* notice processing */
@ -117,12 +111,20 @@ typedef struct {
int equote; /* use E''-style quotes for escaped strings */ int equote; /* use E''-style quotes for escaped strings */
PyObject *weakreflist; /* list of weak references */ PyObject *weakreflist; /* list of weak references */
int autocommit;
} connectionObject; } connectionObject;
/* map isolation level values into a numeric const */
typedef struct {
char *name;
int value;
} IsolationLevel;
/* C-callable functions in connection_int.c and connection_ext.c */ /* C-callable functions in connection_int.c and connection_ext.c */
HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str); HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str);
HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn); HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn);
HIDDEN int conn_get_isolation_level(PGresult *pgres); HIDDEN int conn_get_isolation_level(connectionObject *self);
HIDDEN int conn_get_protocol_version(PGconn *pgconn); HIDDEN int conn_get_protocol_version(PGconn *pgconn);
HIDDEN int conn_get_server_version(PGconn *pgconn); HIDDEN int conn_get_server_version(PGconn *pgconn);
HIDDEN PGcancel *conn_get_cancel(PGconn *pgconn); HIDDEN PGcancel *conn_get_cancel(PGconn *pgconn);
@ -134,6 +136,10 @@ HIDDEN int conn_connect(connectionObject *self, long int async);
HIDDEN void conn_close(connectionObject *self); HIDDEN void conn_close(connectionObject *self);
HIDDEN int conn_commit(connectionObject *self); HIDDEN int conn_commit(connectionObject *self);
HIDDEN int conn_rollback(connectionObject *self); HIDDEN int conn_rollback(connectionObject *self);
HIDDEN int conn_set_session(connectionObject *self, const char *isolevel,
const char *readonly, const char *deferrable,
int autocommit);
HIDDEN int conn_set_autocommit(connectionObject *self, int value);
HIDDEN int conn_switch_isolation_level(connectionObject *self, int level); HIDDEN int conn_switch_isolation_level(connectionObject *self, int level);
HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc); HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc);
HIDDEN int conn_poll(connectionObject *self); HIDDEN int conn_poll(connectionObject *self);
@ -152,6 +158,13 @@ HIDDEN PyObject *conn_tpc_recover(connectionObject *self);
"in asynchronous mode"); \ "in asynchronous mode"); \
return NULL; } return NULL; }
#define EXC_IF_IN_TRANSACTION(self, cmd) \
if (self->status != CONN_STATUS_READY) { \
PyErr_Format(ProgrammingError, \
"%s cannot be used inside a transaction", #cmd); \
return NULL; \
}
#define EXC_IF_TPC_NOT_SUPPORTED(self) \ #define EXC_IF_TPC_NOT_SUPPORTED(self) \
if ((self)->server_version < 80100) { \ if ((self)->server_version < 80100) { \
PyErr_Format(NotSupportedError, \ PyErr_Format(NotSupportedError, \

View File

@ -34,6 +34,19 @@
#include <string.h> #include <string.h>
/* Mapping from isolation level name to value exposed by Python.
* Only used for backward compatibility by the isolation_level property */
const IsolationLevel conn_isolevels[] = {
{"", 0}, /* autocommit */
{"read uncommitted", 1},
{"read committed", 2},
{"repeatable read", 3},
{"serializable", 4},
{"default", -1},
{ NULL }
};
/* Return a new "string" from a char* from the database. /* Return a new "string" from a char* from the database.
* *
@ -82,6 +95,10 @@ conn_notice_callback(void *args, const char *message)
self->notice_pending = notice; self->notice_pending = notice;
} }
/* Expose the notices received as Python objects.
*
* The function should be called with the connection lock and the GIL.
*/
void void
conn_notice_process(connectionObject *self) conn_notice_process(connectionObject *self)
{ {
@ -92,10 +109,6 @@ conn_notice_process(connectionObject *self)
return; return;
} }
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
Py_BLOCK_THREADS;
notice = self->notice_pending; notice = self->notice_pending;
nnotices = PyList_GET_SIZE(self->notice_list); nnotices = PyList_GET_SIZE(self->notice_list);
@ -119,10 +132,6 @@ conn_notice_process(connectionObject *self)
0, nnotices - CONN_NOTICES_LIMIT); 0, nnotices - CONN_NOTICES_LIMIT);
} }
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
conn_notice_clean(self); conn_notice_clean(self);
} }
@ -130,8 +139,6 @@ void
conn_notice_clean(connectionObject *self) conn_notice_clean(connectionObject *self)
{ {
struct connectionObject_notice *tmp, *notice; struct connectionObject_notice *tmp, *notice;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
notice = self->notice_pending; notice = self->notice_pending;
@ -141,11 +148,8 @@ conn_notice_clean(connectionObject *self)
free((void*)tmp->message); free((void*)tmp->message);
free(tmp); free(tmp);
} }
self->notice_pending = NULL; self->notice_pending = NULL;
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
} }
@ -161,8 +165,6 @@ conn_notifies_process(connectionObject *self)
PyObject *notify = NULL; PyObject *notify = NULL;
PyObject *pid = NULL, *channel = NULL, *payload = NULL; PyObject *pid = NULL, *channel = NULL, *payload = NULL;
/* TODO: we are called without the lock! */
while ((pgn = PQnotifies(self->pgconn)) != NULL) { while ((pgn = PQnotifies(self->pgconn)) != NULL) {
Dprintf("conn_notifies_process: got NOTIFY from pid %d, msg = %s", Dprintf("conn_notifies_process: got NOTIFY from pid %d, msg = %s",
@ -358,26 +360,57 @@ exit:
return rv; return rv;
} }
int int
conn_get_isolation_level(PGresult *pgres) conn_get_isolation_level(connectionObject *self)
{ {
static const char lvl1a[] = "read uncommitted"; PGresult *pgres = NULL;
static const char lvl1b[] = "read committed"; char *error = NULL;
int rv; int rv = -1;
char *lname;
const IsolationLevel *level;
char *isolation_level = PQgetvalue(pgres, 0, 0); /* this may get called by async connections too: here's your result */
if (self->autocommit) {
return 0;
}
if ((strcmp(lvl1b, isolation_level) == 0) /* most likely */ Py_BEGIN_ALLOW_THREADS;
|| (strcmp(lvl1a, isolation_level) == 0)) pthread_mutex_lock(&self->lock);
rv = ISOLATION_LEVEL_READ_COMMITTED;
else /* if it's not one of the lower ones, it's SERIALIZABLE */
rv = ISOLATION_LEVEL_SERIALIZABLE;
CLEARPGRES(pgres); if (!(lname = pq_get_guc_locked(self, "default_transaction_isolation",
&pgres, &error, &_save))) {
goto endlock;
}
/* find the value for the requested isolation level */
level = conn_isolevels;
while ((++level)->name) {
if (0 == strcasecmp(level->name, lname)) {
rv = level->value;
break;
}
}
if (-1 == rv) {
error = malloc(256);
PyOS_snprintf(error, 256,
"unexpected isolation level: '%s'", lname);
}
free(lname);
endlock:
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
if (rv < 0) {
pq_complete_error(self, &pgres, &error);
}
return rv; return rv;
} }
int int
conn_get_protocol_version(PGconn *pgconn) conn_get_protocol_version(PGconn *pgconn)
{ {
@ -425,8 +458,8 @@ conn_is_datestyle_ok(PGconn *pgconn)
int int
conn_setup(connectionObject *self, PGconn *pgconn) conn_setup(connectionObject *self, PGconn *pgconn)
{ {
PGresult *pgres; PGresult *pgres = NULL;
int green; char *error = NULL;
self->equote = conn_get_standard_conforming_strings(pgconn); self->equote = conn_get_standard_conforming_strings(pgconn);
self->server_version = conn_get_server_version(pgconn); self->server_version = conn_get_server_version(pgconn);
@ -450,51 +483,24 @@ conn_setup(connectionObject *self, PGconn *pgconn)
pthread_mutex_lock(&self->lock); pthread_mutex_lock(&self->lock);
Py_BLOCK_THREADS; Py_BLOCK_THREADS;
green = psyco_green(); if (psyco_green() && (pq_set_non_blocking(self, 1, 1) != 0)) {
if (green && (pq_set_non_blocking(self, 1, 1) != 0)) {
return -1; return -1;
} }
if (!conn_is_datestyle_ok(self->pgconn)) { if (!conn_is_datestyle_ok(self->pgconn)) {
if (!green) { int res;
Py_UNBLOCK_THREADS; Py_UNBLOCK_THREADS;
Dprintf("conn_connect: exec query \"%s\";", psyco_datestyle); res = pq_set_guc_locked(self, "datestyle", "ISO",
pgres = PQexec(pgconn, psyco_datestyle); &pgres, &error, &_save);
Py_BLOCK_THREADS; Py_BLOCK_THREADS;
} else { if (res < 0) {
pgres = psyco_exec_green(self, psyco_datestyle); pq_complete_error(self, &pgres, &error);
}
if (pgres == NULL || PQresultStatus(pgres) != PGRES_COMMAND_OK ) {
PyErr_SetString(OperationalError, "can't set datestyle to ISO");
IFCLEARPGRES(pgres);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&self->lock);
Py_BLOCK_THREADS;
return -1; return -1;
} }
CLEARPGRES(pgres);
} }
if (!green) { /* for reset */
Py_UNBLOCK_THREADS; self->autocommit = 0;
pgres = PQexec(pgconn, psyco_transaction_isolation);
Py_BLOCK_THREADS;
} else {
pgres = psyco_exec_green(self, psyco_transaction_isolation);
}
if (pgres == NULL || PQresultStatus(pgres) != PGRES_TUPLES_OK) {
PyErr_SetString(OperationalError,
"can't fetch default_isolation_level");
IFCLEARPGRES(pgres);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&self->lock);
Py_BLOCK_THREADS;
return -1;
}
self->isolation_level = conn_get_isolation_level(pgres);
Py_UNBLOCK_THREADS; Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
@ -779,7 +785,7 @@ _conn_poll_setup_async(connectionObject *self)
* expected to manage the transactions himself, by sending * expected to manage the transactions himself, by sending
* (asynchronously) BEGIN and COMMIT statements. * (asynchronously) BEGIN and COMMIT statements.
*/ */
self->isolation_level = ISOLATION_LEVEL_AUTOCOMMIT; self->autocommit = 1;
/* If the datestyle is ISO or anything else good, /* If the datestyle is ISO or anything else good,
* we can skip the CONN_STATUS_DATESTYLE step. */ * we can skip the CONN_STATUS_DATESTYLE step. */
@ -857,7 +863,7 @@ conn_poll(connectionObject *self)
case CONN_STATUS_PREPARED: case CONN_STATUS_PREPARED:
res = _conn_poll_query(self); res = _conn_poll_query(self);
if (res == PSYCO_POLL_OK && self->async_cursor) { if (res == PSYCO_POLL_OK && self->async && self->async_cursor) {
/* An async query has just finished: parse the tuple in the /* An async query has just finished: parse the tuple in the
* target cursor. */ * target cursor. */
cursorObject *curs; cursorObject *curs;
@ -952,6 +958,77 @@ conn_rollback(connectionObject *self)
return res; return res;
} }
int
conn_set_session(connectionObject *self,
const char *isolevel, const char *readonly, const char *deferrable,
int autocommit)
{
PGresult *pgres = NULL;
char *error = NULL;
int res = -1;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
if (isolevel) {
Dprintf("conn_set_session: setting isolation to %s", isolevel);
if ((res = pq_set_guc_locked(self,
"default_transaction_isolation", isolevel,
&pgres, &error, &_save))) {
goto endlock;
}
}
if (readonly) {
Dprintf("conn_set_session: setting read only to %s", readonly);
if ((res = pq_set_guc_locked(self,
"default_transaction_read_only", readonly,
&pgres, &error, &_save))) {
goto endlock;
}
}
if (deferrable) {
Dprintf("conn_set_session: setting deferrable to %s", deferrable);
if ((res = pq_set_guc_locked(self,
"default_transaction_deferrable", deferrable,
&pgres, &error, &_save))) {
goto endlock;
}
}
if (self->autocommit != autocommit) {
Dprintf("conn_set_session: setting autocommit to %d", autocommit);
self->autocommit = autocommit;
}
res = 0;
endlock:
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
if (res < 0) {
pq_complete_error(self, &pgres, &error);
}
return res;
}
int
conn_set_autocommit(connectionObject *self, int value)
{
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
self->autocommit = value;
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
return 0;
}
/* conn_switch_isolation_level - switch isolation level on the connection */ /* conn_switch_isolation_level - switch isolation level on the connection */
int int
@ -959,33 +1036,80 @@ conn_switch_isolation_level(connectionObject *self, int level)
{ {
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
int res = 0; int curr_level;
int ret = -1;
/* use only supported levels on older PG versions */
if (self->server_version < 80000) {
if (level == 1 || level == 3) {
++level;
}
}
if (-1 == (curr_level = conn_get_isolation_level(self))) {
return -1;
}
if (curr_level == level) {
/* no need to change level */
return 0;
}
/* Emulate the previous semantic of set_isolation_level() using the
* functions currently available. */
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock); pthread_mutex_lock(&self->lock);
/* if the current isolation level is equal to the requested one don't switch */ /* terminate the current transaction if any */
if (self->isolation_level != level) { if ((ret = pq_abort_locked(self, &pgres, &error, &_save))) {
goto endlock;
/* if the current isolation level is > 0 we need to abort the current
transaction before changing; that all folks! */
if (self->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT) {
res = pq_abort_locked(self, &pgres, &error, &_save);
}
self->isolation_level = level;
Dprintf("conn_switch_isolation_level: switched to level %d", level);
} }
if (level == 0) {
if ((ret = pq_set_guc_locked(self,
"default_transaction_isolation", "default",
&pgres, &error, &_save))) {
goto endlock;
}
self->autocommit = 1;
}
else {
/* find the name of the requested level */
const IsolationLevel *isolevel = conn_isolevels;
while ((++isolevel)->name) {
if (level == isolevel->value) {
break;
}
}
if (!isolevel->name) {
ret = -1;
error = strdup("bad isolation level value");
goto endlock;
}
if ((ret = pq_set_guc_locked(self,
"default_transaction_isolation", isolevel->name,
&pgres, &error, &_save))) {
goto endlock;
}
self->autocommit = 0;
}
Dprintf("conn_switch_isolation_level: switched to level %d", level);
endlock:
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
if (res < 0) if (ret < 0) {
pq_complete_error(self, &pgres, &error); pq_complete_error(self, &pgres, &error);
}
return res; return ret;
} }
/* conn_set_client_encoding - switch client encoding on connection */ /* conn_set_client_encoding - switch client encoding on connection */
int int
@ -993,7 +1117,6 @@ conn_set_client_encoding(connectionObject *self, const char *enc)
{ {
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
char query[48];
int res = 1; int res = 1;
char *codec = NULL; char *codec = NULL;
char *clean_enc = NULL; char *clean_enc = NULL;
@ -1009,16 +1132,14 @@ conn_set_client_encoding(connectionObject *self, const char *enc)
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 */
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 */
if ((res = pq_abort_locked(self, &pgres, &error, &_save))) { if ((res = pq_abort_locked(self, &pgres, &error, &_save))) {
goto endlock; goto endlock;
} }
if ((res = pq_execute_command_locked(self, query, &pgres, &error, &_save))) { if ((res = pq_set_guc_locked(self, "client_encoding", clean_enc,
&pgres, &error, &_save))) {
goto endlock; goto endlock;
} }
@ -1042,7 +1163,6 @@ conn_set_client_encoding(connectionObject *self, const char *enc)
self->encoding, self->codec); self->encoding, self->codec);
endlock: endlock:
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;

View File

@ -187,6 +187,7 @@ psyco_conn_tpc_begin(connectionObject *self, PyObject *args)
EXC_IF_CONN_CLOSED(self); EXC_IF_CONN_CLOSED(self);
EXC_IF_CONN_ASYNC(self, tpc_begin); EXC_IF_CONN_ASYNC(self, tpc_begin);
EXC_IF_TPC_NOT_SUPPORTED(self); EXC_IF_TPC_NOT_SUPPORTED(self);
EXC_IF_IN_TRANSACTION(self, tpc_begin);
if (!PyArg_ParseTuple(args, "O", &oxid)) { if (!PyArg_ParseTuple(args, "O", &oxid)) {
goto exit; goto exit;
@ -196,15 +197,8 @@ psyco_conn_tpc_begin(connectionObject *self, PyObject *args)
goto exit; goto exit;
} }
/* check we are not in a transaction */
if (self->status != CONN_STATUS_READY) {
PyErr_SetString(ProgrammingError,
"tpc_begin must be called outside a transaction");
goto exit;
}
/* two phase commit and autocommit make no point */ /* two phase commit and autocommit make no point */
if (self->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) { if (self->autocommit) {
PyErr_SetString(ProgrammingError, PyErr_SetString(ProgrammingError,
"tpc_begin can't be called in autocommit mode"); "tpc_begin can't be called in autocommit mode");
goto exit; goto exit;
@ -384,6 +378,199 @@ psyco_conn_tpc_recover(connectionObject *self, PyObject *args)
#ifdef PSYCOPG_EXTENSIONS #ifdef PSYCOPG_EXTENSIONS
/* parse a python object into one of the possible isolation level values */
extern const IsolationLevel conn_isolevels[];
static const char *
_psyco_conn_parse_isolevel(connectionObject *self, PyObject *pyval)
{
const IsolationLevel *isolevel = NULL;
Py_INCREF(pyval); /* for ensure_bytes */
/* parse from one of the level constants */
if (PyInt_Check(pyval)) {
long level = PyInt_AsLong(pyval);
if (level == -1 && PyErr_Occurred()) { goto exit; }
if (level < 1 || level > 4) {
PyErr_SetString(PyExc_ValueError,
"isolation_level must be between 1 and 4");
goto exit;
}
isolevel = conn_isolevels + level;
}
/* parse from the string -- this includes "default" */
else {
isolevel = conn_isolevels;
while ((++isolevel)->name) {
if (!(pyval = psycopg_ensure_bytes(pyval))) {
goto exit;
}
if (0 == strcasecmp(isolevel->name, Bytes_AS_STRING(pyval))) {
break;
}
}
if (!isolevel->name) {
char msg[256];
snprintf(msg, sizeof(msg),
"bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
PyErr_SetString(PyExc_ValueError, msg);
}
}
/* use only supported levels on older PG versions */
if (isolevel && self->server_version < 80000) {
if (isolevel->value == 1 || isolevel->value == 3) {
++isolevel;
}
}
exit:
Py_XDECREF(pyval);
return isolevel ? isolevel->name : NULL;
}
/* convert True/False/"default" into a C string */
static const char *
_psyco_conn_parse_onoff(PyObject *pyval)
{
int istrue = PyObject_IsTrue(pyval);
if (-1 == istrue) { return NULL; }
if (istrue) {
int cmp;
PyObject *pydef;
if (!(pydef = Text_FromUTF8("default"))) { return NULL; }
cmp = PyObject_RichCompareBool(pyval, pydef, Py_EQ);
Py_DECREF(pydef);
if (-1 == cmp) { return NULL; }
return cmp ? "default" : "on";
}
else {
return "off";
}
}
/* set_session - set default transaction characteristics */
#define psyco_conn_set_session_doc \
"set_session(...) -- Set one or more parameters for the next transactions.\n\n" \
"Accepted arguments are 'isolation_level', 'readonly', 'deferrable', 'autocommit'."
static PyObject *
psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
{
PyObject *isolevel = Py_None;
PyObject *readonly = Py_None;
PyObject *deferrable = Py_None;
PyObject *autocommit = Py_None;
const char *c_isolevel = NULL;
const char *c_readonly = NULL;
const char *c_deferrable = NULL;
int c_autocommit = self->autocommit;
static char *kwlist[] =
{"isolation_level", "readonly", "deferrable", "autocommit", NULL};
EXC_IF_CONN_CLOSED(self);
EXC_IF_CONN_ASYNC(self, set_session);
EXC_IF_IN_TRANSACTION(self, set_session);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO", kwlist,
&isolevel, &readonly, &deferrable, &autocommit)) {
return NULL;
}
if (Py_None != isolevel) {
if (!(c_isolevel = _psyco_conn_parse_isolevel(self, isolevel))) {
return NULL;
}
}
if (Py_None != readonly) {
if (!(c_readonly = _psyco_conn_parse_onoff(readonly))) {
return NULL;
}
}
if (Py_None != deferrable) {
if (self->server_version < 90100) {
PyErr_SetString(ProgrammingError,
"the 'deferrable' setting is only available"
" from PostgreSQL 9.1");
return NULL;
}
if (!(c_deferrable = _psyco_conn_parse_onoff(deferrable))) {
return NULL;
}
}
if (Py_None != autocommit) {
c_autocommit = PyObject_IsTrue(autocommit);
if (-1 == c_autocommit) { return NULL; }
}
if (0 != conn_set_session(self,
c_isolevel, c_readonly, c_deferrable, c_autocommit)) {
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
#define psyco_conn_autocommit_doc \
"set or return the autocommit status."
static PyObject *
psyco_conn_autocommit_get(connectionObject *self)
{
PyObject *ret;
ret = self->autocommit ? Py_True : Py_False;
Py_INCREF(ret);
return ret;
}
static PyObject *
_psyco_conn_autocommit_set_checks(connectionObject *self)
{
/* wrapper to use the EXC_IF macros.
* return NULL in case of error, else whatever */
EXC_IF_CONN_CLOSED(self);
EXC_IF_CONN_ASYNC(self, autocommit);
EXC_IF_IN_TRANSACTION(self, autocommit);
return Py_None; /* borrowed */
}
static int
psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue)
{
int value;
if (!_psyco_conn_autocommit_set_checks(self)) { return -1; }
if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
if (0 != conn_set_autocommit(self, value)) { return -1; }
return 0;
}
/* isolation_level - return the current isolation level */
static PyObject *
psyco_conn_isolation_level_get(connectionObject *self)
{
int rv = conn_get_isolation_level(self);
if (-1 == rv) { return NULL; }
return PyInt_FromLong((long)rv);
}
/* set_isolation_level method - switch connection isolation level */ /* set_isolation_level method - switch connection isolation level */
#define psyco_conn_set_isolation_level_doc \ #define psyco_conn_set_isolation_level_doc \
@ -400,9 +587,9 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "i", &level)) return NULL; if (!PyArg_ParseTuple(args, "i", &level)) return NULL;
if (level < 0 || level > 2) { if (level < 0 || level > 4) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"isolation level must be between 0 and 2"); "isolation level must be between 0 and 4");
return NULL; return NULL;
} }
@ -717,6 +904,8 @@ static struct PyMethodDef connectionObject_methods[] = {
{"tpc_recover", (PyCFunction)psyco_conn_tpc_recover, {"tpc_recover", (PyCFunction)psyco_conn_tpc_recover,
METH_NOARGS, psyco_conn_tpc_recover_doc}, METH_NOARGS, psyco_conn_tpc_recover_doc},
#ifdef PSYCOPG_EXTENSIONS #ifdef PSYCOPG_EXTENSIONS
{"set_session", (PyCFunction)psyco_conn_set_session,
METH_VARARGS|METH_KEYWORDS, psyco_conn_set_session_doc},
{"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level, {"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level,
METH_VARARGS, psyco_conn_set_isolation_level_doc}, METH_VARARGS, psyco_conn_set_isolation_level_doc},
{"set_client_encoding", (PyCFunction)psyco_conn_set_client_encoding, {"set_client_encoding", (PyCFunction)psyco_conn_set_client_encoding,
@ -749,9 +938,6 @@ static struct PyMemberDef connectionObject_members[] = {
#ifdef PSYCOPG_EXTENSIONS #ifdef PSYCOPG_EXTENSIONS
{"closed", T_LONG, offsetof(connectionObject, closed), READONLY, {"closed", T_LONG, offsetof(connectionObject, closed), READONLY,
"True if the connection is closed."}, "True if the connection is closed."},
{"isolation_level", T_LONG,
offsetof(connectionObject, isolation_level), READONLY,
"The current isolation level."},
{"encoding", T_STRING, offsetof(connectionObject, encoding), READONLY, {"encoding", T_STRING, offsetof(connectionObject, encoding), READONLY,
"The current client encoding."}, "The current client encoding."},
{"notices", T_OBJECT, offsetof(connectionObject, notice_list), READONLY}, {"notices", T_OBJECT, offsetof(connectionObject, notice_list), READONLY},
@ -792,6 +978,16 @@ static struct PyGetSetDef connectionObject_getsets[] = {
EXCEPTION_GETTER(IntegrityError), EXCEPTION_GETTER(IntegrityError),
EXCEPTION_GETTER(DataError), EXCEPTION_GETTER(DataError),
EXCEPTION_GETTER(NotSupportedError), EXCEPTION_GETTER(NotSupportedError),
#ifdef PSYCOPG_EXTENSIONS
{ "autocommit",
(getter)psyco_conn_autocommit_get,
(setter)psyco_conn_autocommit_set,
psyco_conn_autocommit_doc },
{ "isolation_level",
(getter)psyco_conn_isolation_level_get,
(setter)NULL,
"The current isolation level." },
#endif
{NULL} {NULL}
}; };
#undef EXCEPTION_GETTER #undef EXCEPTION_GETTER

View File

@ -64,7 +64,7 @@ struct cursorObject {
PyObject *copyfile; /* file-like used during COPY TO/FROM ops */ PyObject *copyfile; /* file-like used during COPY TO/FROM ops */
Py_ssize_t copysize; /* size of the copy buffer during COPY TO/FROM ops */ Py_ssize_t copysize; /* size of the copy buffer during COPY TO/FROM ops */
#define DEFAULT_COPYSIZE 16384 #define DEFAULT_COPYSIZE 16384
#define DEFAULT_COPYBUFF 8132 #define DEFAULT_COPYBUFF 8192
PyObject *tuple_factory; /* factory for result tuples */ PyObject *tuple_factory; /* factory for result tuples */
PyObject *tzinfo_factory; /* factory for tzinfo objects */ PyObject *tzinfo_factory; /* factory for tzinfo objects */

View File

@ -456,7 +456,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs)
NULL, NULL); NULL, NULL);
return NULL; return NULL;
} }
if (self->conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) { if (self->conn->autocommit) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"can't use a named cursor outside of transactions", NULL, NULL); "can't use a named cursor outside of transactions", NULL, NULL);
return NULL; return NULL;
@ -739,7 +739,6 @@ psyco_curs_fetchone(cursorObject *self, PyObject *args)
PyObject *res; PyObject *res;
EXC_IF_CURS_CLOSED(self); EXC_IF_CURS_CLOSED(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchone);
if (_psyco_curs_prefetch(self) < 0) return NULL; if (_psyco_curs_prefetch(self) < 0) return NULL;
EXC_IF_NO_TUPLES(self); EXC_IF_NO_TUPLES(self);
@ -747,6 +746,7 @@ psyco_curs_fetchone(cursorObject *self, PyObject *args)
char buffer[128]; char buffer[128];
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchone);
EXC_IF_TPC_PREPARED(self->conn, fetchone); EXC_IF_TPC_PREPARED(self->conn, fetchone);
PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM \"%s\"", self->name); PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM \"%s\"", self->name);
if (pq_execute(self, buffer, 0) == -1) return NULL; if (pq_execute(self, buffer, 0) == -1) return NULL;
@ -853,7 +853,6 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords)
} }
EXC_IF_CURS_CLOSED(self); EXC_IF_CURS_CLOSED(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchmany);
if (_psyco_curs_prefetch(self) < 0) return NULL; if (_psyco_curs_prefetch(self) < 0) return NULL;
EXC_IF_NO_TUPLES(self); EXC_IF_NO_TUPLES(self);
@ -861,6 +860,7 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords)
char buffer[128]; char buffer[128];
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchmany);
EXC_IF_TPC_PREPARED(self->conn, fetchone); EXC_IF_TPC_PREPARED(self->conn, fetchone);
PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM \"%s\"", PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM \"%s\"",
(int)size, self->name); (int)size, self->name);
@ -924,7 +924,6 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args)
PyObject *list, *res; PyObject *list, *res;
EXC_IF_CURS_CLOSED(self); EXC_IF_CURS_CLOSED(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchall);
if (_psyco_curs_prefetch(self) < 0) return NULL; if (_psyco_curs_prefetch(self) < 0) return NULL;
EXC_IF_NO_TUPLES(self); EXC_IF_NO_TUPLES(self);
@ -932,6 +931,7 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args)
char buffer[128]; char buffer[128];
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchall);
EXC_IF_TPC_PREPARED(self->conn, fetchall); EXC_IF_TPC_PREPARED(self->conn, fetchall);
PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name); PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name);
if (pq_execute(self, buffer, 0) == -1) return NULL; if (pq_execute(self, buffer, 0) == -1) return NULL;
@ -1112,7 +1112,6 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
return NULL; return NULL;
EXC_IF_CURS_CLOSED(self); EXC_IF_CURS_CLOSED(self);
EXC_IF_ASYNC_IN_PROGRESS(self, scroll)
/* if the cursor is not named we have the full result set and we can do /* if the cursor is not named we have the full result set and we can do
our own calculations to scroll; else we just delegate the scrolling our own calculations to scroll; else we just delegate the scrolling
@ -1141,6 +1140,7 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
char buffer[128]; char buffer[128];
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, scroll)
EXC_IF_TPC_PREPARED(self->conn, scroll); EXC_IF_TPC_PREPARED(self->conn, scroll);
if (strcmp(mode, "absolute") == 0) { if (strcmp(mode, "absolute") == 0) {
@ -1213,15 +1213,19 @@ static int _psyco_curs_copy_columns(PyObject *columns, char *columnlist)
/* extension: copy_from - implements COPY FROM */ /* extension: copy_from - implements COPY FROM */
#define psyco_curs_copy_from_doc \ #define psyco_curs_copy_from_doc \
"copy_from(file, table, sep='\\t', null='\\N', columns=None) -- Copy table from file." "copy_from(file, table, sep='\\t', null='\\N', size=8192, columns=None) -- Copy table from file."
static int static int
_psyco_curs_has_read_check(PyObject* o, void* var) _psyco_curs_has_read_check(PyObject* o, void* var)
{ {
if (PyObject_HasAttrString(o, "readline") if (PyObject_HasAttrString(o, "readline")
&& PyObject_HasAttrString(o, "read")) { && PyObject_HasAttrString(o, "read")) {
/* It's OK to store a borrowed reference, because it is only held for /* This routine stores a borrowed reference. Although it is only held
* the duration of psyco_curs_copy_from. */ * for the duration of psyco_curs_copy_from, nested invocations of
* Py_BEGIN_ALLOW_THREADS could surrender control to another thread,
* which could invoke the garbage collector. We thus need an
* INCREF/DECREF pair if we store this pointer in a GC object, such as
* a cursorObject */
*((PyObject**)var) = o; *((PyObject**)var) = o;
return 1; return 1;
} }
@ -1311,6 +1315,7 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs)
Dprintf("psyco_curs_copy_from: query = %s", query); Dprintf("psyco_curs_copy_from: query = %s", query);
self->copysize = bufsize; self->copysize = bufsize;
Py_INCREF(file);
self->copyfile = file; self->copyfile = file;
if (pq_execute(self, query, 0) == 1) { if (pq_execute(self, query, 0) == 1) {
@ -1319,6 +1324,7 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs)
} }
self->copyfile = NULL; self->copyfile = NULL;
Py_DECREF(file);
exit: exit:
PyMem_Free(quoted_delimiter); PyMem_Free(quoted_delimiter);
@ -1337,8 +1343,6 @@ static int
_psyco_curs_has_write_check(PyObject* o, void* var) _psyco_curs_has_write_check(PyObject* o, void* var)
{ {
if (PyObject_HasAttrString(o, "write")) { if (PyObject_HasAttrString(o, "write")) {
/* It's OK to store a borrowed reference, because it is only held for
* the duration of psyco_curs_copy_to. */
*((PyObject**)var) = o; *((PyObject**)var) = o;
return 1; return 1;
} }
@ -1424,12 +1428,15 @@ psyco_curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs)
Dprintf("psyco_curs_copy_to: query = %s", query); Dprintf("psyco_curs_copy_to: query = %s", query);
self->copysize = 0; self->copysize = 0;
Py_INCREF(file);
self->copyfile = file; self->copyfile = file;
if (pq_execute(self, query, 0) == 1) { if (pq_execute(self, query, 0) == 1) {
res = Py_None; res = Py_None;
Py_INCREF(Py_None); Py_INCREF(Py_None);
} }
Py_DECREF(file);
self->copyfile = NULL; self->copyfile = NULL;
exit: exit:
@ -1447,7 +1454,7 @@ exit:
*/ */
#define psyco_curs_copy_expert_doc \ #define psyco_curs_copy_expert_doc \
"copy_expert(sql, file, size=None) -- Submit a user-composed COPY statement.\n" \ "copy_expert(sql, file, size=8192) -- Submit a user-composed COPY statement.\n" \
"`file` must be an open, readable file for COPY FROM or an open, writeable\n" \ "`file` must be an open, readable file for COPY FROM or an open, writeable\n" \
"file for COPY TO. The optional `size` argument, when specified for a COPY\n" \ "file for COPY TO. The optional `size` argument, when specified for a COPY\n" \
"FROM statement, will be passed to file's read method to control the read\n" \ "FROM statement, will be passed to file's read method to control the read\n" \
@ -1471,18 +1478,18 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs)
EXC_IF_TPC_PREPARED(self->conn, copy_expert); EXC_IF_TPC_PREPARED(self->conn, copy_expert);
sql = _psyco_curs_validate_sql_basic(self, sql); sql = _psyco_curs_validate_sql_basic(self, sql);
/* Any failure from here forward should 'goto fail' rather than /* Any failure from here forward should 'goto exit' rather than
'return NULL' directly. */ 'return NULL' directly. */
if (sql == NULL) { goto fail; } if (sql == NULL) { goto exit; }
/* This validation of file is rather weak, in that it doesn't enforce the /* This validation of file is rather weak, in that it doesn't enforce the
assocation between "COPY FROM" -> "read" and "COPY TO" -> "write". assocation between "COPY FROM" -> "read" and "COPY TO" -> "write".
However, the error handling in _pq_copy_[in|out] must be able to handle However, the error handling in _pq_copy_[in|out] must be able to handle
the case where the attempt to call file.read|write fails, so no harm the case where the attempt to call file.read|write fails, so no harm
done. */ done. */
if ( !PyObject_HasAttrString(file, "read") if ( !PyObject_HasAttrString(file, "read")
&& !PyObject_HasAttrString(file, "write") && !PyObject_HasAttrString(file, "write")
) )
@ -1490,26 +1497,23 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs)
PyErr_SetString(PyExc_TypeError, "file must be a readable file-like" PyErr_SetString(PyExc_TypeError, "file must be a readable file-like"
" object for COPY FROM; a writeable file-like object for COPY TO." " object for COPY FROM; a writeable file-like object for COPY TO."
); );
goto fail; goto exit;
} }
self->copysize = bufsize; self->copysize = bufsize;
Py_INCREF(file);
self->copyfile = file; self->copyfile = file;
/* At this point, the SQL statement must be str, not unicode */ /* At this point, the SQL statement must be str, not unicode */
if (pq_execute(self, Bytes_AS_STRING(sql), 0) != 1) { goto fail; } if (pq_execute(self, Bytes_AS_STRING(sql), 0) == 1) {
res = Py_None;
res = Py_None; Py_INCREF(res);
Py_INCREF(res);
goto cleanup;
fail:
if (res != NULL) {
Py_DECREF(res);
res = NULL;
} }
/* Fall through to cleanup */
cleanup:
self->copyfile = NULL; self->copyfile = NULL;
Py_DECREF(file);
exit:
Py_XDECREF(sql); Py_XDECREF(sql);
return res; return res;

View File

@ -152,6 +152,20 @@ psyco_exec_green(connectionObject *conn, const char *command)
{ {
PGresult *result = NULL; PGresult *result = NULL;
/* Check that there is a single concurrently executing query */
if (conn->async_cursor) {
PyErr_SetString(ProgrammingError,
"a single async query can be executed on the same connection");
goto end;
}
/* we don't care about which cursor is executing the query, and
* it may also be that no cursor is involved at all and this is
* an internal query. So just store anything in the async_cursor,
* respecting the code expecting it to be a weakref */
if (!(conn->async_cursor = PyWeakref_NewRef((PyObject*)conn, NULL))) {
goto end;
}
/* Send the query asynchronously */ /* Send the query asynchronously */
if (0 == pq_send_query(conn, command)) { if (0 == pq_send_query(conn, command)) {
goto end; goto end;
@ -173,6 +187,7 @@ psyco_exec_green(connectionObject *conn, const char *command)
end: end:
conn->async_status = ASYNC_DONE; conn->async_status = ASYNC_DONE;
Py_CLEAR(conn->async_cursor);
return result; return result;
} }

View File

@ -76,7 +76,7 @@ HIDDEN int lobject_close(lobjectObject *self);
return NULL; } return NULL; }
#define EXC_IF_LOBJ_LEVEL0(self) \ #define EXC_IF_LOBJ_LEVEL0(self) \
if (self->conn->isolation_level == 0) { \ if (self->conn->autocommit) { \
psyco_set_error(ProgrammingError, NULL, \ psyco_set_error(ProgrammingError, NULL, \
"can't use a lobject outside of transactions", NULL, NULL); \ "can't use a lobject outside of transactions", NULL, NULL); \
return NULL; \ return NULL; \

View File

@ -252,7 +252,7 @@ lobject_close_locked(lobjectObject *self, char **error)
break; break;
} }
if (self->conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT || if (self->conn->autocommit ||
self->conn->mark != self->mark || self->conn->mark != self->mark ||
self->fd == -1) self->fd == -1)
return 0; return 0;

View File

@ -51,7 +51,7 @@ psyco_lobj_close(lobjectObject *self, PyObject *args)
closing the current transaction is equivalent to close all the closing the current transaction is equivalent to close all the
opened large objects */ opened large objects */
if (!lobject_is_closed(self) if (!lobject_is_closed(self)
&& self->conn->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT && !self->conn->autocommit
&& self->conn->mark == self->mark) && self->conn->mark == self->mark)
{ {
Dprintf("psyco_lobj_close: closing lobject at %p", self); Dprintf("psyco_lobj_close: closing lobject at %p", self);
@ -331,7 +331,7 @@ lobject_setup(lobjectObject *self, connectionObject *conn,
{ {
Dprintf("lobject_setup: init lobject object at %p", self); Dprintf("lobject_setup: init lobject object at %p", self);
if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) { if (conn->autocommit) {
psyco_set_error(ProgrammingError, NULL, psyco_set_error(ProgrammingError, NULL,
"can't use a lobject outside of transactions", NULL, NULL); "can't use a lobject outside of transactions", NULL, NULL);
return -1; return -1;

View File

@ -343,12 +343,12 @@ pq_execute_command_locked(connectionObject *conn, const char *query,
*tstate = PyEval_SaveThread(); *tstate = PyEval_SaveThread();
} }
if (*pgres == NULL) { if (*pgres == NULL) {
const char *msg;
Dprintf("pq_execute_command_locked: PQexec returned NULL"); Dprintf("pq_execute_command_locked: PQexec returned NULL");
msg = PQerrorMessage(conn->pgconn); if (!PyErr_Occurred()) {
if (msg) const char *msg;
*error = strdup(msg); msg = PQerrorMessage(conn->pgconn);
if (msg && *msg) { *error = strdup(msg); }
}
goto cleanup; goto cleanup;
} }
@ -361,8 +361,8 @@ pq_execute_command_locked(connectionObject *conn, const char *query,
retvalue = 0; retvalue = 0;
IFCLEARPGRES(*pgres); IFCLEARPGRES(*pgres);
cleanup: cleanup:
return retvalue; return retvalue;
} }
@ -406,23 +406,17 @@ int
pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error, pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error,
PyThreadState **tstate) PyThreadState **tstate)
{ {
const char *query[] = {
NULL,
"BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED",
"BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"};
int result; int result;
Dprintf("pq_begin_locked: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_begin_locked: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->autocommit, conn->status);
if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT if (conn->autocommit || conn->status != CONN_STATUS_READY) {
|| conn->status != CONN_STATUS_READY) {
Dprintf("pq_begin_locked: transaction in progress"); Dprintf("pq_begin_locked: transaction in progress");
return 0; return 0;
} }
result = pq_execute_command_locked(conn, query[conn->isolation_level], result = pq_execute_command_locked(conn, "BEGIN", pgres, error, tstate);
pgres, error, tstate);
if (result == 0) if (result == 0)
conn->status = CONN_STATUS_BEGIN; conn->status = CONN_STATUS_BEGIN;
@ -442,11 +436,10 @@ pq_commit(connectionObject *conn)
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
Dprintf("pq_commit: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_commit: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->autocommit, conn->status);
if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) {
|| conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_commit: no transaction to commit"); Dprintf("pq_commit: no transaction to commit");
return 0; return 0;
} }
@ -457,11 +450,13 @@ pq_commit(connectionObject *conn)
retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error, &_save); retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error, &_save);
Py_BLOCK_THREADS;
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&conn->lock); pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
conn_notice_process(conn);
if (retvalue < 0) if (retvalue < 0)
pq_complete_error(conn, &pgres, &error); pq_complete_error(conn, &pgres, &error);
@ -478,11 +473,10 @@ pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error,
{ {
int retvalue = -1; int retvalue = -1;
Dprintf("pq_abort_locked: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_abort_locked: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->autocommit, conn->status);
if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) {
|| conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_abort_locked: no transaction to abort"); Dprintf("pq_abort_locked: no transaction to abort");
return 0; return 0;
} }
@ -507,11 +501,10 @@ pq_abort(connectionObject *conn)
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
Dprintf("pq_abort: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_abort: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->autocommit, conn->status);
if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) {
|| conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_abort: no transaction to abort"); Dprintf("pq_abort: no transaction to abort");
return 0; return 0;
} }
@ -521,11 +514,13 @@ pq_abort(connectionObject *conn)
retvalue = pq_abort_locked(conn, &pgres, &error, &_save); retvalue = pq_abort_locked(conn, &pgres, &error, &_save);
Py_BLOCK_THREADS;
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&conn->lock); pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
conn_notice_process(conn);
if (retvalue < 0) if (retvalue < 0)
pq_complete_error(conn, &pgres, &error); pq_complete_error(conn, &pgres, &error);
@ -547,13 +542,12 @@ pq_reset_locked(connectionObject *conn, PGresult **pgres, char **error,
{ {
int retvalue = -1; int retvalue = -1;
Dprintf("pq_reset_locked: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_reset_locked: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->autocommit, conn->status);
conn->mark += 1; conn->mark += 1;
if (conn->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT if (!conn->autocommit && conn->status == CONN_STATUS_BEGIN) {
&& conn->status == CONN_STATUS_BEGIN) {
retvalue = pq_execute_command_locked(conn, "ABORT", pgres, error, tstate); retvalue = pq_execute_command_locked(conn, "ABORT", pgres, error, tstate);
if (retvalue != 0) return retvalue; if (retvalue != 0) return retvalue;
} }
@ -578,19 +572,21 @@ pq_reset(connectionObject *conn)
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
Dprintf("pq_reset: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_reset: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->autocommit, conn->status);
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock); pthread_mutex_lock(&conn->lock);
retvalue = pq_reset_locked(conn, &pgres, &error, &_save); retvalue = pq_reset_locked(conn, &pgres, &error, &_save);
Py_BLOCK_THREADS;
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&conn->lock); pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
conn_notice_process(conn);
if (retvalue < 0) { if (retvalue < 0) {
pq_complete_error(conn, &pgres, &error); pq_complete_error(conn, &pgres, &error);
} }
@ -601,6 +597,98 @@ pq_reset(connectionObject *conn)
} }
/* Get a session parameter.
*
* The function should be called on a locked connection without
* holding the GIL.
*
* The result is a new string allocated with malloc.
*/
char *
pq_get_guc_locked(
connectionObject *conn, const char *param,
PGresult **pgres, char **error, PyThreadState **tstate)
{
char query[256];
int size;
char *rv = NULL;
Dprintf("pq_get_guc_locked: reading %s", param);
size = PyOS_snprintf(query, sizeof(query), "SHOW %s", param);
if (size >= sizeof(query)) {
*error = strdup("SHOW: query too large");
goto cleanup;
}
Dprintf("pq_get_guc_locked: pgconn = %p, query = %s", conn->pgconn, query);
*error = NULL;
if (!psyco_green()) {
*pgres = PQexec(conn->pgconn, query);
} else {
PyEval_RestoreThread(*tstate);
*pgres = psyco_exec_green(conn, query);
*tstate = PyEval_SaveThread();
}
if (*pgres == NULL) {
Dprintf("pq_get_guc_locked: PQexec returned NULL");
if (!PyErr_Occurred()) {
const char *msg;
msg = PQerrorMessage(conn->pgconn);
if (msg && *msg) { *error = strdup(msg); }
}
goto cleanup;
}
if (PQresultStatus(*pgres) != PGRES_TUPLES_OK) {
Dprintf("pq_get_guc_locked: result was not TUPLES_OK (%d)",
PQresultStatus(*pgres));
goto cleanup;
}
rv = strdup(PQgetvalue(*pgres, 0, 0));
CLEARPGRES(*pgres);
cleanup:
return rv;
}
/* Set a session parameter.
*
* The function should be called on a locked connection without
* holding the GIL
*/
int
pq_set_guc_locked(
connectionObject *conn, const char *param, const char *value,
PGresult **pgres, char **error, PyThreadState **tstate)
{
char query[256];
int size;
int rv = -1;
Dprintf("pq_set_guc_locked: setting %s to %s", param, value);
if (0 == strcmp(value, "default")) {
size = PyOS_snprintf(query, sizeof(query),
"SET %s TO DEFAULT", param);
}
else {
size = PyOS_snprintf(query, sizeof(query),
"SET %s TO '%s'", param, value);
}
if (size >= sizeof(query)) {
*error = strdup("SET: query too large");
}
rv = pq_execute_command_locked(conn, query, pgres, error, tstate);
return rv;
}
/* Call one of the PostgreSQL tpc-related commands. /* Call one of the PostgreSQL tpc-related commands.
* *
* This function should only be called on a locked connection without * This function should only be called on a locked connection without
@ -626,12 +714,12 @@ pq_tpc_command_locked(connectionObject *conn, const char *cmd, const char *tid,
{ goto exit; } { goto exit; }
/* prepare the command to the server */ /* prepare the command to the server */
buflen = 3 + strlen(cmd) + strlen(etid); /* add space, semicolon, zero */ buflen = 2 + strlen(cmd) + strlen(etid); /* add space, zero */
if (!(buf = PyMem_Malloc(buflen))) { if (!(buf = PyMem_Malloc(buflen))) {
PyErr_NoMemory(); PyErr_NoMemory();
goto exit; goto exit;
} }
if (0 > PyOS_snprintf(buf, buflen, "%s %s;", cmd, etid)) { goto exit; } if (0 > PyOS_snprintf(buf, buflen, "%s %s", cmd, etid)) { goto exit; }
/* run the command and let it handle the error cases */ /* run the command and let it handle the error cases */
*tstate = PyEval_SaveThread(); *tstate = PyEval_SaveThread();
@ -675,12 +763,14 @@ pq_is_busy(connectionObject *conn)
res = PQisBusy(conn->pgconn); res = PQisBusy(conn->pgconn);
Py_BLOCK_THREADS;
conn_notifies_process(conn);
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&(conn->lock)); pthread_mutex_unlock(&(conn->lock));
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
conn_notice_process(conn);
conn_notifies_process(conn);
return res; return res;
} }
@ -700,9 +790,9 @@ pq_is_busy_locked(connectionObject *conn)
return -1; return -1;
} }
/* We can't call conn_notice_process/conn_notifies_process because /* notices and notifies will be processed at the end of the loop we are in
they try to get the lock. We don't need anyway them because at the end of * (async reading) by pq_fetch. */
the loop we are in (async reading) pq_fetch will be called. */
return PQisBusy(conn->pgconn); return PQisBusy(conn->pgconn);
} }
@ -791,6 +881,15 @@ pq_execute(cursorObject *curs, const char *query, int async)
} }
return -1; return -1;
} }
/* Process notifies here instead of when fetching the tuple as we are
* into the same critical section that received the data. Without this
* care, reading notifies may disrupt other thread communications.
* (as in ticket #55). */
Py_BLOCK_THREADS;
conn_notifies_process(curs->conn);
conn_notice_process(curs->conn);
Py_UNBLOCK_THREADS;
} }
else if (async == 1) { else if (async == 1) {
@ -1379,9 +1478,6 @@ pq_fetch(cursorObject *curs)
break; break;
} }
conn_notice_process(curs->conn);
conn_notifies_process(curs->conn);
/* error checking, close the connection if necessary (some critical errors /* error checking, close the connection if necessary (some critical errors
are not really critical, like a COPY FROM error: if that's the case we are not really critical, like a COPY FROM error: if that's the case we
raise the exception but we avoid to close the connection) */ raise the exception but we avoid to close the connection) */

View File

@ -47,6 +47,12 @@ HIDDEN int pq_abort(connectionObject *conn);
HIDDEN int pq_reset_locked(connectionObject *conn, PGresult **pgres, HIDDEN int pq_reset_locked(connectionObject *conn, PGresult **pgres,
char **error, PyThreadState **tstate); char **error, PyThreadState **tstate);
HIDDEN int pq_reset(connectionObject *conn); HIDDEN int pq_reset(connectionObject *conn);
HIDDEN char *pq_get_guc_locked(connectionObject *conn, const char *param,
PGresult **pgres,
char **error, PyThreadState **tstate);
HIDDEN int pq_set_guc_locked(connectionObject *conn, const char *param,
const char *value, PGresult **pgres,
char **error, PyThreadState **tstate);
HIDDEN int pq_tpc_command_locked(connectionObject *conn, HIDDEN int pq_tpc_command_locked(connectionObject *conn,
const char *cmd, const char *tid, const char *cmd, const char *tid,
PGresult **pgres, char **error, PGresult **pgres, char **error,

View File

@ -39,6 +39,7 @@
#include "psycopg/adapter_qstring.h" #include "psycopg/adapter_qstring.h"
#include "psycopg/adapter_binary.h" #include "psycopg/adapter_binary.h"
#include "psycopg/adapter_pboolean.h" #include "psycopg/adapter_pboolean.h"
#include "psycopg/adapter_pint.h"
#include "psycopg/adapter_pfloat.h" #include "psycopg/adapter_pfloat.h"
#include "psycopg/adapter_pdecimal.h" #include "psycopg/adapter_pdecimal.h"
#include "psycopg/adapter_asis.h" #include "psycopg/adapter_asis.h"
@ -316,9 +317,9 @@ psyco_adapters_init(PyObject *mod)
microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType); microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType);
#if PY_MAJOR_VERSION < 3 #if PY_MAJOR_VERSION < 3
microprotocols_add(&PyInt_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyInt_Type, NULL, (PyObject*)&pintType);
#endif #endif
microprotocols_add(&PyLong_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyLong_Type, NULL, (PyObject*)&pintType);
microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType); microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType);
/* strings */ /* strings */
@ -359,10 +360,16 @@ psyco_adapters_init(PyObject *mod)
#ifdef HAVE_MXDATETIME #ifdef HAVE_MXDATETIME
/* as above, we use the callable objects from the psycopg module */ /* as above, we use the callable objects from the psycopg module */
call = PyMapping_GetItemString(mod, "TimestampFromMx"); if (NULL != (call = PyMapping_GetItemString(mod, "TimestampFromMx"))) {
microprotocols_add(mxDateTime.DateTime_Type, NULL, call); microprotocols_add(mxDateTime.DateTime_Type, NULL, call);
call = PyMapping_GetItemString(mod, "TimeFromMx");
microprotocols_add(mxDateTime.DateTimeDelta_Type, NULL, call); /* if we found the above, we have this too. */
call = PyMapping_GetItemString(mod, "TimeFromMx");
microprotocols_add(mxDateTime.DateTimeDelta_Type, NULL, call);
}
else {
PyErr_Clear();
}
#endif #endif
} }
@ -757,11 +764,13 @@ static PyMethodDef psycopgMethods[] = {
{"QuotedString", (PyCFunction)psyco_QuotedString, {"QuotedString", (PyCFunction)psyco_QuotedString,
METH_VARARGS, psyco_QuotedString_doc}, METH_VARARGS, psyco_QuotedString_doc},
{"Boolean", (PyCFunction)psyco_Boolean, {"Boolean", (PyCFunction)psyco_Boolean,
METH_VARARGS, psyco_Float_doc},
{"Float", (PyCFunction)psyco_Float,
METH_VARARGS, psyco_Decimal_doc},
{"Decimal", (PyCFunction)psyco_Decimal,
METH_VARARGS, psyco_Boolean_doc}, METH_VARARGS, psyco_Boolean_doc},
{"Int", (PyCFunction)psyco_Int,
METH_VARARGS, psyco_Int_doc},
{"Float", (PyCFunction)psyco_Float,
METH_VARARGS, psyco_Float_doc},
{"Decimal", (PyCFunction)psyco_Decimal,
METH_VARARGS, psyco_Decimal_doc},
{"Binary", (PyCFunction)psyco_Binary, {"Binary", (PyCFunction)psyco_Binary,
METH_VARARGS, psyco_Binary_doc}, METH_VARARGS, psyco_Binary_doc},
{"Date", (PyCFunction)psyco_Date, {"Date", (PyCFunction)psyco_Date,
@ -789,6 +798,7 @@ static PyMethodDef psycopgMethods[] = {
METH_VARARGS, psyco_IntervalFromPy_doc}, METH_VARARGS, psyco_IntervalFromPy_doc},
#ifdef HAVE_MXDATETIME #ifdef HAVE_MXDATETIME
/* to be deleted if not found at import time */
{"DateFromMx", (PyCFunction)psyco_DateFromMx, {"DateFromMx", (PyCFunction)psyco_DateFromMx,
METH_VARARGS, psyco_DateFromMx_doc}, METH_VARARGS, psyco_DateFromMx_doc},
{"TimeFromMx", (PyCFunction)psyco_TimeFromMx, {"TimeFromMx", (PyCFunction)psyco_TimeFromMx,
@ -848,6 +858,7 @@ INIT_MODULE(_psycopg)(void)
Py_TYPE(&binaryType) = &PyType_Type; Py_TYPE(&binaryType) = &PyType_Type;
Py_TYPE(&isqlquoteType) = &PyType_Type; Py_TYPE(&isqlquoteType) = &PyType_Type;
Py_TYPE(&pbooleanType) = &PyType_Type; Py_TYPE(&pbooleanType) = &PyType_Type;
Py_TYPE(&pintType) = &PyType_Type;
Py_TYPE(&pfloatType) = &PyType_Type; Py_TYPE(&pfloatType) = &PyType_Type;
Py_TYPE(&pdecimalType) = &PyType_Type; Py_TYPE(&pdecimalType) = &PyType_Type;
Py_TYPE(&asisType) = &PyType_Type; Py_TYPE(&asisType) = &PyType_Type;
@ -863,6 +874,7 @@ INIT_MODULE(_psycopg)(void)
if (PyType_Ready(&binaryType) == -1) goto exit; if (PyType_Ready(&binaryType) == -1) goto exit;
if (PyType_Ready(&isqlquoteType) == -1) goto exit; if (PyType_Ready(&isqlquoteType) == -1) goto exit;
if (PyType_Ready(&pbooleanType) == -1) goto exit; if (PyType_Ready(&pbooleanType) == -1) goto exit;
if (PyType_Ready(&pintType) == -1) goto exit;
if (PyType_Ready(&pfloatType) == -1) goto exit; if (PyType_Ready(&pfloatType) == -1) goto exit;
if (PyType_Ready(&pdecimalType) == -1) goto exit; if (PyType_Ready(&pdecimalType) == -1) goto exit;
if (PyType_Ready(&asisType) == -1) goto exit; if (PyType_Ready(&asisType) == -1) goto exit;
@ -880,12 +892,16 @@ INIT_MODULE(_psycopg)(void)
#ifdef HAVE_MXDATETIME #ifdef HAVE_MXDATETIME
Py_TYPE(&mxdatetimeType) = &PyType_Type; Py_TYPE(&mxdatetimeType) = &PyType_Type;
if (PyType_Ready(&mxdatetimeType) == -1) goto exit; if (PyType_Ready(&mxdatetimeType) == -1) goto exit;
if (mxDateTime_ImportModuleAndAPI() != 0) { if (0 != mxDateTime_ImportModuleAndAPI()) {
Dprintf("initpsycopg: why marc hide mx.DateTime again?!"); PyErr_Clear();
PyErr_SetString(PyExc_ImportError, "can't import mx.DateTime module");
/* only fail if the mx typacaster should have been the default */
#ifdef PSYCOPG_DEFAULT_MXDATETIME
PyErr_SetString(PyExc_ImportError,
"can't import mx.DateTime module (requested as default adapter)");
goto exit; goto exit;
#endif
} }
if (psyco_adapter_mxdatetime_init()) { goto exit; }
#endif #endif
/* import python builtin datetime module, if available */ /* import python builtin datetime module, if available */
@ -962,6 +978,16 @@ INIT_MODULE(_psycopg)(void)
/* encodings dictionary in module dictionary */ /* encodings dictionary in module dictionary */
PyModule_AddObject(module, "encodings", psycoEncodings); PyModule_AddObject(module, "encodings", psycoEncodings);
#ifdef HAVE_MXDATETIME
/* If we can't find mx.DateTime objects at runtime,
* remove them from the module (and, as consequence, from the adapters). */
if (0 != psyco_adapter_mxdatetime_init()) {
PyDict_DelItemString(dict, "DateFromMx");
PyDict_DelItemString(dict, "TimeFromMx");
PyDict_DelItemString(dict, "TimestampFromMx");
PyDict_DelItemString(dict, "IntervalFromMx");
}
#endif
/* initialize default set of typecasters */ /* initialize default set of typecasters */
typecast_init(dict); typecast_init(dict);
@ -978,6 +1004,7 @@ INIT_MODULE(_psycopg)(void)
binaryType.tp_alloc = PyType_GenericAlloc; binaryType.tp_alloc = PyType_GenericAlloc;
isqlquoteType.tp_alloc = PyType_GenericAlloc; isqlquoteType.tp_alloc = PyType_GenericAlloc;
pbooleanType.tp_alloc = PyType_GenericAlloc; pbooleanType.tp_alloc = PyType_GenericAlloc;
pintType.tp_alloc = PyType_GenericAlloc;
pfloatType.tp_alloc = PyType_GenericAlloc; pfloatType.tp_alloc = PyType_GenericAlloc;
pdecimalType.tp_alloc = PyType_GenericAlloc; pdecimalType.tp_alloc = PyType_GenericAlloc;
connectionType.tp_alloc = PyType_GenericAlloc; connectionType.tp_alloc = PyType_GenericAlloc;
@ -993,7 +1020,6 @@ INIT_MODULE(_psycopg)(void)
lobjectType.tp_alloc = PyType_GenericAlloc; lobjectType.tp_alloc = PyType_GenericAlloc;
#endif #endif
#ifdef HAVE_MXDATETIME #ifdef HAVE_MXDATETIME
mxdatetimeType.tp_alloc = PyType_GenericAlloc; mxdatetimeType.tp_alloc = PyType_GenericAlloc;
#endif #endif

View File

@ -105,6 +105,7 @@ typedef unsigned long Py_uhash_t;
#if PY_MAJOR_VERSION > 2 #if PY_MAJOR_VERSION > 2
#define PyInt_Type PyLong_Type #define PyInt_Type PyLong_Type
#define PyInt_Check PyLong_Check
#define PyInt_AsLong PyLong_AsLong #define PyInt_AsLong PyLong_AsLong
#define PyInt_FromLong PyLong_FromLong #define PyInt_FromLong PyLong_FromLong
#define PyInt_FromSsize_t PyLong_FromSsize_t #define PyInt_FromSsize_t PyLong_FromSsize_t
@ -129,6 +130,7 @@ typedef unsigned long Py_uhash_t;
#define Bytes_FromString PyString_FromString #define Bytes_FromString PyString_FromString
#define Bytes_FromStringAndSize PyString_FromStringAndSize #define Bytes_FromStringAndSize PyString_FromStringAndSize
#define Bytes_FromFormat PyString_FromFormat #define Bytes_FromFormat PyString_FromFormat
#define Bytes_ConcatAndDel PyString_ConcatAndDel
#define _Bytes_Resize _PyString_Resize #define _Bytes_Resize _PyString_Resize
#else #else
@ -144,6 +146,7 @@ typedef unsigned long Py_uhash_t;
#define Bytes_FromString PyBytes_FromString #define Bytes_FromString PyBytes_FromString
#define Bytes_FromStringAndSize PyBytes_FromStringAndSize #define Bytes_FromStringAndSize PyBytes_FromStringAndSize
#define Bytes_FromFormat PyBytes_FromFormat #define Bytes_FromFormat PyBytes_FromFormat
#define Bytes_ConcatAndDel PyBytes_ConcatAndDel
#define _Bytes_Resize _PyBytes_Resize #define _Bytes_Resize _PyBytes_Resize
#endif #endif

View File

@ -292,13 +292,14 @@ typecast_init(PyObject *dict)
/* register the date/time typecasters with their original names */ /* register the date/time typecasters with their original names */
#ifdef HAVE_MXDATETIME #ifdef HAVE_MXDATETIME
if (psyco_typecast_mxdatetime_init()) { return -1; } if (0 == psyco_typecast_mxdatetime_init()) {
for (i = 0; typecast_mxdatetime[i].name != NULL; i++) { for (i = 0; typecast_mxdatetime[i].name != NULL; i++) {
typecastObject *t; typecastObject *t;
Dprintf("typecast_init: initializing %s", typecast_mxdatetime[i].name); Dprintf("typecast_init: initializing %s", typecast_mxdatetime[i].name);
t = (typecastObject *)typecast_from_c(&(typecast_mxdatetime[i]), dict); t = (typecastObject *)typecast_from_c(&(typecast_mxdatetime[i]), dict);
if (t == NULL) return -1; if (t == NULL) return -1;
PyDict_SetItem(dict, t->name, (PyObject *)t); PyDict_SetItem(dict, t->name, (PyObject *)t);
}
} }
#endif #endif

View File

@ -25,13 +25,17 @@
#include "mxDateTime.h" #include "mxDateTime.h"
/* Return 0 on success, -1 on failure, but don't set an exception */
static int static int
psyco_typecast_mxdatetime_init(void) psyco_typecast_mxdatetime_init(void)
{ {
Dprintf("psyco_typecast_mxdatetime_init: mx.DateTime init"); Dprintf("psyco_typecast_mxdatetime_init: mx.DateTime init");
if(mxDateTime_ImportModuleAndAPI()) { if (mxDateTime_ImportModuleAndAPI()) {
PyErr_SetString(PyExc_ImportError, "mx.DateTime initialization failed"); Dprintf("psyco_typecast_mxdatetime_init: mx.DateTime initialization failed");
PyErr_Clear();
return -1; return -1;
} }
return 0; return 0;

View File

@ -663,7 +663,7 @@ xid_recover(PyObject *conn)
/* curs.execute(...) */ /* curs.execute(...) */
if (!(tmp = PyObject_CallMethod(curs, "execute", "s", if (!(tmp = PyObject_CallMethod(curs, "execute", "s",
"SELECT gid, prepared, owner, database FROM pg_prepared_xacts;"))) "SELECT gid, prepared, owner, database FROM pg_prepared_xacts")))
{ {
goto exit; goto exit;
} }

75
scripts/ticket58.py Normal file
View File

@ -0,0 +1,75 @@
"""
A script to reproduce the race condition described in ticket #58
from https://bugzilla.redhat.com/show_bug.cgi?id=711095
Results in the error:
python: Modules/gcmodule.c:277: visit_decref: Assertion `gc->gc.gc_refs != 0'
failed.
on unpatched library.
"""
import threading
import gc
import time
import psycopg2
from StringIO import StringIO
done = 0
class GCThread(threading.Thread):
# A thread that sits in an infinite loop, forcing the garbage collector
# to run
def run(self):
global done
while not done:
gc.collect()
time.sleep(0.1) # give the other thread a chance to run
gc_thread = GCThread()
# This assumes a pre-existing db named "test", with:
# "CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);"
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
# Start the other thread, running the GC regularly
gc_thread.start()
# Now do lots of "cursor.copy_from" calls:
print "copy_from"
for i in range(1000):
f = StringIO("42\tfoo\n74\tbar\n")
cur.copy_from(f, 'test', columns=('num', 'data'))
# Assuming the other thread gets a chance to run during this call, expect a
# build of python (with assertions enabled) to bail out here with:
# python: Modules/gcmodule.c:277: visit_decref: Assertion `gc->gc.gc_refs != 0' failed.
# Also exercise the copy_to code path
print "copy_to"
cur.execute("truncate test")
f = StringIO("42\tfoo\n74\tbar\n")
cur.copy_from(f, 'test', columns=('num', 'data'))
for i in range(1000):
f = StringIO()
cur.copy_to(f, 'test', columns=('num', 'data'))
# And copy_expert too
print "copy_expert"
cur.execute("truncate test")
for i in range(1000):
f = StringIO("42\tfoo\n74\tbar\n")
cur.copy_expert("copy test to stdout", f)
# Terminate the GC thread's loop:
done = 1
cur.close()
conn.close()

371
setup.py
View File

@ -45,21 +45,15 @@ Operating System :: Unix
# Note: The setup.py must be compatible with both Python 2 and 3 # Note: The setup.py must be compatible with both Python 2 and 3
import os import os
import os.path
import sys import sys
import re import re
import subprocess import subprocess
from distutils.core import setup, Extension from distutils.core import setup, Extension
from distutils.errors import DistutilsFileError
from distutils.command.build_ext import build_ext from distutils.command.build_ext import build_ext
from distutils.sysconfig import get_python_inc from distutils.sysconfig import get_python_inc
from distutils.ccompiler import get_default_compiler from distutils.ccompiler import get_default_compiler
from distutils.dep_util import newer_group
from distutils.util import get_platform from distutils.util import get_platform
try:
from distutils.msvc9compiler import MSVCCompiler
except ImportError:
MSVCCompiler = None
try: try:
from distutils.command.build_py import build_py_2to3 as build_py from distutils.command.build_py import build_py_2to3 as build_py
except ImportError: except ImportError:
@ -79,27 +73,135 @@ 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.1' PSYCOPG_VERSION = '2.4.2'
version_flags = ['dt', 'dec'] version_flags = ['dt', 'dec']
PLATFORM_IS_WINDOWS = sys.platform.lower().startswith('win') PLATFORM_IS_WINDOWS = sys.platform.lower().startswith('win')
def get_pg_config(kind, pg_config):
try: class PostgresConfig:
p = subprocess.Popen([pg_config, "--" + kind], def __init__(self, build_ext):
stdin=subprocess.PIPE, self.build_ext = build_ext
stdout=subprocess.PIPE, self.pg_config_exe = self.build_ext.pg_config
stderr=subprocess.PIPE) if not self.pg_config_exe:
except OSError: self.pg_config_exe = self.autodetect_pg_config_path()
raise Warning("Unable to find 'pg_config' file in '%s'" % pg_config) if self.pg_config_exe is None:
p.stdin.close() sys.stderr.write("""\
r = p.stdout.readline().strip() Error: pg_config executable not found.
if not r:
raise Warning(p.stderr.readline()) Please add the directory containing pg_config to the PATH
if not isinstance(r, str): or specify the full executable path with the option:
r = r.decode('ascii')
return r python setup.py build_ext --pg-config /path/to/pg_config build ...
or with the pg_config option in 'setup.cfg'.
""")
sys.exit(1)
def query(self, attr_name):
"""Spawn the pg_config executable, querying for the given config
name, and return the printed value, sanitized. """
try:
pg_config_process = subprocess.Popen(
[self.pg_config_exe, "--" + attr_name],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError:
raise Warning("Unable to find 'pg_config' file in '%s'" %
self.pg_config_exe)
pg_config_process.stdin.close()
result = pg_config_process.stdout.readline().strip()
if not result:
raise Warning(pg_config_process.stderr.readline())
if not isinstance(result, str):
result = result.decode('ascii')
return result
def find_on_path(self, exename, path_directories=None):
if not path_directories:
path_directories = os.environ['PATH'].split(os.pathsep)
for dir_name in path_directories:
fullpath = os.path.join(dir_name, exename)
if os.path.isfile(fullpath):
return fullpath
return None
def autodetect_pg_config_path(self):
"""Find and return the path to the pg_config executable."""
if PLATFORM_IS_WINDOWS:
return self.autodetect_pg_config_path_windows()
else:
return self.find_on_path('pg_config')
def autodetect_pg_config_path_windows(self):
"""Attempt several different ways of finding the pg_config
executable on Windows, and return its full path, if found."""
# This code only runs if they have not specified a pg_config option
# in the config file or via the commandline.
# First, check for pg_config.exe on the PATH, and use that if found.
pg_config_exe = self.find_on_path('pg_config.exe')
if pg_config_exe:
return pg_config_exe
# Now, try looking in the Windows Registry to find a PostgreSQL
# installation, and infer the path from that.
pg_config_exe = self._get_pg_config_from_registry()
if pg_config_exe:
return pg_config_exe
return None
def _get_pg_config_from_registry(self):
try:
import winreg
except ImportError:
import _winreg as winreg
reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
pg_inst_list_key = winreg.OpenKey(reg,
'SOFTWARE\\PostgreSQL\\Installations')
except EnvironmentError:
# No PostgreSQL installation, as best as we can tell.
return None
try:
# Determine the name of the first subkey, if any:
try:
first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0)
except EnvironmentError:
return None
pg_first_inst_key = winreg.OpenKey(reg,
'SOFTWARE\\PostgreSQL\\Installations\\'
+ first_sub_key_name)
try:
pg_inst_base_dir = winreg.QueryValueEx(
pg_first_inst_key, 'Base Directory')[0]
finally:
winreg.CloseKey(pg_first_inst_key)
finally:
winreg.CloseKey(pg_inst_list_key)
pg_config_path = os.path.join(
pg_inst_base_dir, 'bin', 'pg_config.exe')
if not os.path.exists(pg_config_path):
return None
# Support unicode paths, if this version of Python provides the
# necessary infrastructure:
if sys.version_info[0] < 3 \
and hasattr(sys, 'getfilesystemencoding'):
pg_config_path = pg_config_path.encode(
sys.getfilesystemencoding())
return pg_config_path
class psycopg_build_ext(build_ext): class psycopg_build_ext(build_ext):
"""Conditionally complement the setup.cfg options file. """Conditionally complement the setup.cfg options file.
@ -126,6 +228,9 @@ class psycopg_build_ext(build_ext):
boolean_options = build_ext.boolean_options[:] boolean_options = build_ext.boolean_options[:]
boolean_options.extend(('use-pydatetime', 'have-ssl', 'static-libpq')) boolean_options.extend(('use-pydatetime', 'have-ssl', 'static-libpq'))
def __init__(self, *args, **kwargs):
build_ext.__init__(self, *args, **kwargs)
def initialize_options(self): def initialize_options(self):
build_ext.initialize_options(self) build_ext.initialize_options(self)
self.use_pg_dll = 1 self.use_pg_dll = 1
@ -136,7 +241,13 @@ class psycopg_build_ext(build_ext):
self.static_libpq = static_libpq self.static_libpq = static_libpq
self.pg_config = None self.pg_config = None
def get_compiler(self): def compiler_is_msvc(self):
return self.get_compiler_name().lower().startswith('msvc')
def compiler_is_mingw(self):
return self.get_compiler_name().lower().startswith('mingw')
def get_compiler_name(self):
"""Return the name of the C compiler used to compile extensions. """Return the name of the C compiler used to compile extensions.
If a compiler was not explicitly set (on the command line, for If a compiler was not explicitly set (on the command line, for
@ -153,43 +264,40 @@ class psycopg_build_ext(build_ext):
name = get_default_compiler() name = get_default_compiler()
return name return name
def get_pg_config(self, kind): def get_export_symbols(self, extension):
return get_pg_config(kind, self.pg_config)
def get_export_symbols(self, ext):
# Fix MSVC seeing two of the same export symbols. # Fix MSVC seeing two of the same export symbols.
if self.get_compiler().lower().startswith('msvc'): if self.compiler_is_msvc():
return [] return []
else: else:
return build_ext.get_export_symbols(self, ext) return build_ext.get_export_symbols(self, extension)
def build_extension(self, ext): def build_extension(self, extension):
build_ext.build_extension(self, ext) build_ext.build_extension(self, extension)
sysVer = sys.version_info[:2]
# For Python versions that use MSVC compiler 2008, re-insert the # For Python versions that use MSVC compiler 2008, re-insert the
# manifest into the resulting .pyd file. # manifest into the resulting .pyd file.
if MSVCCompiler and isinstance(self.compiler, MSVCCompiler): if self.compiler_is_msvc() and sysVer not in ((2, 4), (2, 5)):
platform = get_platform() platform = get_platform()
# Default to the x86 manifest # Default to the x86 manifest
manifest = '_psycopg.vc9.x86.manifest' manifest = '_psycopg.vc9.x86.manifest'
if platform == 'win-amd64': if platform == 'win-amd64':
manifest = '_psycopg.vc9.amd64.manifest' manifest = '_psycopg.vc9.amd64.manifest'
self.compiler.spawn(['mt.exe', '-nologo', '-manifest', self.compiler.spawn(
os.path.join('psycopg', manifest), ['mt.exe', '-nologo', '-manifest',
'-outputresource:%s;2' % (os.path.join(self.build_lib, 'psycopg2', '_psycopg.pyd'))]) os.path.join('psycopg', manifest),
'-outputresource:%s;2' % (
os.path.join(self.build_lib,
'psycopg2', '_psycopg.pyd'))])
def finalize_win32(self): def finalize_win32(self):
"""Finalize build system configuration on win32 platform.""" """Finalize build system configuration on win32 platform."""
import struct
sysVer = sys.version_info[:2] sysVer = sys.version_info[:2]
# Add compiler-specific arguments: # Add compiler-specific arguments:
extra_compiler_args = [] extra_compiler_args = []
compiler_name = self.get_compiler().lower() if self.compiler_is_mingw():
compiler_is_msvc = compiler_name.startswith('msvc')
compiler_is_mingw = compiler_name.startswith('mingw')
if compiler_is_mingw:
# Default MinGW compilation of Python extensions on Windows uses # Default MinGW compilation of Python extensions on Windows uses
# only -O: # only -O:
extra_compiler_args.append('-O3') extra_compiler_args.append('-O3')
@ -201,23 +309,23 @@ class psycopg_build_ext(build_ext):
extra_compiler_args.append('-fno-strict-aliasing') extra_compiler_args.append('-fno-strict-aliasing')
# Force correct C runtime library linkage: # Force correct C runtime library linkage:
if sysVer <= (2,3): if sysVer <= (2, 3):
# Yes: 'msvcr60', rather than 'msvcrt', is the correct value # Yes: 'msvcr60', rather than 'msvcrt', is the correct value
# on the line below: # on the line below:
self.libraries.append('msvcr60') self.libraries.append('msvcr60')
elif sysVer in ((2,4), (2,5)): elif sysVer in ((2, 4), (2, 5)):
self.libraries.append('msvcr71') self.libraries.append('msvcr71')
# Beyond Python 2.5, we take our chances on the default C runtime # Beyond Python 2.5, we take our chances on the default C runtime
# library, because we don't know what compiler those future # library, because we don't know what compiler those future
# versions of Python will use. # versions of Python will use.
for exten in ext: # ext is a global list of Extension objects for extension in ext: # ext is a global list of Extension objects
exten.extra_compile_args.extend(extra_compiler_args) extension.extra_compile_args.extend(extra_compiler_args)
# End of add-compiler-specific arguments section. # End of add-compiler-specific arguments section.
self.libraries.append("ws2_32") self.libraries.append("ws2_32")
self.libraries.append("advapi32") self.libraries.append("advapi32")
if compiler_is_msvc: if self.compiler_is_msvc():
# MSVC requires an explicit "libpq" # MSVC requires an explicit "libpq"
self.libraries.remove("pq") self.libraries.remove("pq")
self.libraries.append("secur32") self.libraries.append("secur32")
@ -242,48 +350,39 @@ class psycopg_build_ext(build_ext):
def finalize_linux2(self): def finalize_linux2(self):
"""Finalize build system configuration on GNU/Linux platform.""" """Finalize build system configuration on GNU/Linux platform."""
# tell piro that GCC is fine and dandy, but not so MS compilers # tell piro that GCC is fine and dandy, but not so MS compilers
for ext in self.extensions: for extension in self.extensions:
ext.extra_compile_args.append('-Wdeclaration-after-statement') extension.extra_compile_args.append(
'-Wdeclaration-after-statement')
def finalize_options(self): def finalize_options(self):
"""Complete the build system configuation.""" """Complete the build system configuation."""
build_ext.finalize_options(self) build_ext.finalize_options(self)
if self.pg_config is None:
self.pg_config = self.autodetect_pg_config_path()
if self.pg_config is None:
sys.stderr.write("""\
Error: pg_config executable not found.
Please add the directory containing pg_config to the PATH pg_config_helper = PostgresConfig(self)
or specify the full executable path with the option:
python setup.py build_ext --pg-config /path/to/pg_config build ...
or with the pg_config option in 'setup.cfg'.
""")
sys.exit(1)
self.include_dirs.append(".") self.include_dirs.append(".")
if self.static_libpq: if self.static_libpq:
if not self.link_objects: self.link_objects = [] if not hasattr(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(pg_config_helper.query("libdir"), "libpq.a"))
else: else:
self.libraries.append("pq") self.libraries.append("pq")
try: try:
self.library_dirs.append(self.get_pg_config("libdir")) self.library_dirs.append(pg_config_helper.query("libdir"))
self.include_dirs.append(self.get_pg_config("includedir")) self.include_dirs.append(pg_config_helper.query("includedir"))
self.include_dirs.append(self.get_pg_config("includedir-server")) self.include_dirs.append(pg_config_helper.query("includedir-server"))
try: try:
# Here we take a conservative approach: we suppose that # Here we take a conservative approach: we suppose that
# *at least* PostgreSQL 7.4 is available (this is the only # *at least* PostgreSQL 7.4 is available (this is the only
# 7.x series supported by psycopg 2) # 7.x series supported by psycopg 2)
pgversion = self.get_pg_config("version").split()[1] pgversion = pg_config_helper.query("version").split()[1]
except: except:
pgversion = "7.4.0" pgversion = "7.4.0"
verre = re.compile(r"(\d+)\.(\d+)(?:(?:\.(\d+))|(devel|(alpha|beta|rc)\d+))") verre = re.compile(
r"(\d+)\.(\d+)(?:(?:\.(\d+))|(devel|(alpha|beta|rc)\d+))")
m = verre.match(pgversion) m = verre.match(pgversion)
if m: if m:
pgmajor, pgminor, pgpatch = m.group(1, 2, 3) pgmajor, pgminor, pgpatch = m.group(1, 2, 3)
@ -298,108 +397,21 @@ or with the pg_config option in 'setup.cfg'.
define_macros.append(("PG_VERSION_HEX", "0x%02X%02X%02X" % define_macros.append(("PG_VERSION_HEX", "0x%02X%02X%02X" %
(int(pgmajor), int(pgminor), int(pgpatch)))) (int(pgmajor), int(pgminor), int(pgpatch))))
except Warning: except Warning:
w = sys.exc_info()[1] # work around py 2/3 different syntax w = sys.exc_info()[1] # work around py 2/3 different syntax
sys.stderr.write("Error: %s\n" % w) sys.stderr.write("Error: %s\n" % w)
sys.exit(1) sys.exit(1)
if hasattr(self, "finalize_" + sys.platform): if hasattr(self, "finalize_" + sys.platform):
getattr(self, "finalize_" + sys.platform)() getattr(self, "finalize_" + sys.platform)()
def autodetect_pg_config_path(self):
if PLATFORM_IS_WINDOWS:
return self.autodetect_pg_config_path_windows()
else:
return self.autodetect_pg_config_path_posix()
def autodetect_pg_config_path_posix(self):
exename = 'pg_config'
for dir in os.environ['PATH'].split(os.pathsep):
fn = os.path.join(dir, exename)
if os.path.isfile(fn):
return fn
def autodetect_pg_config_path_windows(self):
# Find the first PostgreSQL installation listed in the registry and
# return the full path to its pg_config utility.
#
# This autodetection is performed *only* if the following conditions
# hold:
#
# 1) The pg_config utility is not already available on the PATH:
if os.popen('pg_config').close() is None: # .close()->None == success
return None
# 2) The user has not specified any of the following settings in
# setup.cfg:
# - pg_config
# - include_dirs
# - library_dirs
for settingName in ('pg_config', 'include_dirs', 'library_dirs'):
try:
val = parser.get('build_ext', settingName)
except configparser.NoOptionError:
pass
else:
if val.strip() != '':
return None
# end of guard conditions
try:
import winreg
except ImportError:
import _winreg as winreg
pg_inst_base_dir = None
pg_config_path = None
reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
pg_inst_list_key = winreg.OpenKey(reg,
'SOFTWARE\\PostgreSQL\\Installations'
)
except EnvironmentError:
pg_inst_list_key = None
if pg_inst_list_key is not None:
try:
# Determine the name of the first subkey, if any:
try:
first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0)
except EnvironmentError:
first_sub_key_name = None
if first_sub_key_name is not None:
pg_first_inst_key = winreg.OpenKey(reg,
'SOFTWARE\\PostgreSQL\\Installations\\'
+ first_sub_key_name
)
try:
pg_inst_base_dir = winreg.QueryValueEx(
pg_first_inst_key, 'Base Directory'
)[0]
finally:
winreg.CloseKey(pg_first_inst_key)
finally:
winreg.CloseKey(pg_inst_list_key)
if pg_inst_base_dir and os.path.exists(pg_inst_base_dir):
pg_config_path = os.path.join(pg_inst_base_dir, 'bin',
'pg_config.exe'
)
# Support unicode paths, if this version of Python provides the
# necessary infrastructure:
if hasattr(sys, 'getfilesystemencoding'):
pg_config_path = pg_config_path.encode(
sys.getfilesystemencoding()
)
return pg_config_path
# let's start with macro definitions (the ones not already in setup.cfg) # let's start with macro definitions (the ones not already in setup.cfg)
define_macros = [] define_macros = []
include_dirs = [] include_dirs = []
# gather information to build the extension module # gather information to build the extension module
ext = [] ; data_files = [] ext = []
data_files = []
# sources # sources
@ -414,7 +426,7 @@ sources = [
'adapter_asis.c', 'adapter_binary.c', 'adapter_datetime.c', 'adapter_asis.c', 'adapter_binary.c', 'adapter_datetime.c',
'adapter_list.c', 'adapter_pboolean.c', 'adapter_pdecimal.c', 'adapter_list.c', 'adapter_pboolean.c', 'adapter_pdecimal.c',
'adapter_pfloat.c', 'adapter_qstring.c', 'adapter_pint.c', 'adapter_pfloat.c', 'adapter_qstring.c',
'microprotocols.c', 'microprotocols_proto.c', 'microprotocols.c', 'microprotocols_proto.c',
'typecast.c', 'typecast.c',
] ]
@ -427,7 +439,7 @@ depends = [
'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h',
'adapter_list.h', 'adapter_pboolean.h', 'adapter_pdecimal.h', 'adapter_list.h', 'adapter_pboolean.h', 'adapter_pdecimal.h',
'adapter_pfloat.h', 'adapter_qstring.h', 'adapter_pint.h', 'adapter_pfloat.h', 'adapter_qstring.h',
'microprotocols.h', 'microprotocols_proto.h', 'microprotocols.h', 'microprotocols_proto.h',
'typecast.h', 'typecast_binary.h', 'typecast.h', 'typecast_binary.h',
@ -450,8 +462,9 @@ if parser.has_option('build_ext', 'mx_include_dir'):
else: else:
mxincludedir = os.path.join(get_python_inc(plat_specific=1), "mx") mxincludedir = os.path.join(get_python_inc(plat_specific=1), "mx")
if os.path.exists(mxincludedir): if os.path.exists(mxincludedir):
# Build the support for mx: we will check at runtime if it can be imported
include_dirs.append(mxincludedir) include_dirs.append(mxincludedir)
define_macros.append(('HAVE_MXDATETIME','1')) define_macros.append(('HAVE_MXDATETIME', '1'))
sources.append('adapter_mxdatetime.c') sources.append('adapter_mxdatetime.c')
depends.extend(['adapter_mxdatetime.h', 'typecast_mxdatetime.c']) depends.extend(['adapter_mxdatetime.h', 'typecast_mxdatetime.c'])
have_mxdatetime = True have_mxdatetime = True
@ -459,18 +472,21 @@ if os.path.exists(mxincludedir):
# now decide which package will be the default for date/time typecasts # now decide which package will be the default for date/time typecasts
if have_pydatetime and (use_pydatetime or not have_mxdatetime): if have_pydatetime and (use_pydatetime or not have_mxdatetime):
define_macros.append(('PSYCOPG_DEFAULT_PYDATETIME','1')) define_macros.append(('PSYCOPG_DEFAULT_PYDATETIME', '1'))
elif have_mxdatetime: elif have_mxdatetime:
define_macros.append(('PSYCOPG_DEFAULT_MXDATETIME','1')) define_macros.append(('PSYCOPG_DEFAULT_MXDATETIME', '1'))
else: else:
def e(msg): error_message = """\
sys.stderr.write("error: " + msg + "\n") psycopg requires a datetime module:
e("psycopg requires a datetime module:") mx.DateTime module not found
e(" mx.DateTime module not found") python datetime module not found
e(" python datetime module not found")
e("Note that psycopg needs the module headers and not just the module") Note that psycopg needs the module headers and not just the module
e("itself. If you installed Python or mx.DateTime from a binary package") itself. If you installed Python or mx.DateTime from a binary package
e("you probably need to install its companion -dev or -devel package.") you probably need to install its companion -dev or -devel package."""
for line in error_message.split("\n"):
sys.stderr.write("error: " + line)
sys.exit(1) sys.exit(1)
# generate a nice version string to avoid confusion when users report bugs # generate a nice version string to avoid confusion when users report bugs
@ -484,9 +500,9 @@ else:
PSYCOPG_VERSION_EX = PSYCOPG_VERSION PSYCOPG_VERSION_EX = PSYCOPG_VERSION
if not PLATFORM_IS_WINDOWS: if not PLATFORM_IS_WINDOWS:
define_macros.append(('PSYCOPG_VERSION', '"'+PSYCOPG_VERSION_EX+'"')) define_macros.append(('PSYCOPG_VERSION', '"' + PSYCOPG_VERSION_EX + '"'))
else: else:
define_macros.append(('PSYCOPG_VERSION', '\\"'+PSYCOPG_VERSION_EX+'\\"')) define_macros.append(('PSYCOPG_VERSION', '\\"' + PSYCOPG_VERSION_EX + '\\"'))
if parser.has_option('build_ext', 'have_ssl'): if parser.has_option('build_ext', 'have_ssl'):
have_ssl = int(parser.get('build_ext', 'have_ssl')) have_ssl = int(parser.get('build_ext', 'have_ssl'))
@ -524,17 +540,16 @@ setup(name="psycopg2",
author="Federico Di Gregorio", author="Federico Di Gregorio",
author_email="fog@initd.org", author_email="fog@initd.org",
url="http://initd.org/psycopg/", url="http://initd.org/psycopg/",
download_url = download_url, download_url=download_url,
license="GPL with exceptions or ZPL", license="GPL with exceptions or ZPL",
platforms = ["any"], platforms=["any"],
description=__doc__.split("\n")[0], description=__doc__.split("\n")[0],
long_description="\n".join(__doc__.split("\n")[2:]), long_description="\n".join(__doc__.split("\n")[2:]),
classifiers=[x for x in classifiers.split("\n") if x], classifiers=[x for x in classifiers.split("\n") if x],
data_files=data_files, data_files=data_files,
package_dir={'psycopg2':'lib', 'psycopg2.tests': 'tests'}, package_dir={'psycopg2': 'lib', 'psycopg2.tests': 'tests'},
packages=['psycopg2', 'psycopg2.tests'], packages=['psycopg2', 'psycopg2.tests'],
cmdclass={ cmdclass={
'build_ext': psycopg_build_ext, 'build_ext': psycopg_build_ext,
'build_py': build_py, }, 'build_py': build_py, },
ext_modules=ext) ext_modules=ext)

View File

@ -25,7 +25,8 @@
import os 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
from testutils import skip_before_postgres, skip_after_postgres
from operator import attrgetter from operator import attrgetter
import psycopg2 import psycopg2
@ -201,24 +202,39 @@ class IsolationLevelsTestCase(unittest.TestCase):
def test_set_isolation_level(self): def test_set_isolation_level(self):
conn = self.connect() conn = self.connect()
curs = conn.cursor()
conn.set_isolation_level( levels = [
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) (None, psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT),
self.assertEqual(conn.isolation_level, ('read uncommitted', psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED),
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) ('read committed', psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED),
('repeatable read', psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ),
('serializable', psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE),
]
for name, level in levels:
conn.set_isolation_level(level)
conn.set_isolation_level( # the only values available on prehistoric PG versions
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) if conn.server_version < 80000:
self.assertEqual(conn.isolation_level, if level in (
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ):
name, level = levels[levels.index((name, level)) + 1]
conn.set_isolation_level( self.assertEqual(conn.isolation_level, level)
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
self.assertEqual(conn.isolation_level, curs.execute('show transaction_isolation;')
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE) got_name = curs.fetchone()[0]
if name is None:
curs.execute('show default_transaction_isolation;')
name = curs.fetchone()[0]
self.assertEqual(name, got_name)
conn.commit()
self.assertRaises(ValueError, conn.set_isolation_level, -1) self.assertRaises(ValueError, conn.set_isolation_level, -1)
self.assertRaises(ValueError, conn.set_isolation_level, 3) self.assertRaises(ValueError, conn.set_isolation_level, 5)
def test_set_isolation_level_abort(self): def test_set_isolation_level_abort(self):
conn = self.connect() conn = self.connect()
@ -701,6 +717,241 @@ from testutils import skip_if_tpc_disabled
decorate_all_tests(ConnectionTwoPhaseTests, skip_if_tpc_disabled) decorate_all_tests(ConnectionTwoPhaseTests, skip_if_tpc_disabled)
class TransactionControlTests(unittest.TestCase):
def setUp(self):
self.conn = psycopg2.connect(dsn)
def tearDown(self):
if not self.conn.closed:
self.conn.close()
def test_not_in_transaction(self):
cur = self.conn.cursor()
cur.execute("select 1")
self.assertRaises(psycopg2.ProgrammingError,
self.conn.set_session,
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
def test_set_isolation_level(self):
cur = self.conn.cursor()
self.conn.set_session(
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
cur.execute("SHOW default_transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback()
self.conn.set_session(
psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ)
cur.execute("SHOW default_transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'repeatable read')
else:
self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback()
self.conn.set_session(
isolation_level=psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
cur.execute("SHOW default_transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback()
self.conn.set_session(
isolation_level=psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED)
cur.execute("SHOW default_transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'read uncommitted')
else:
self.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback()
def test_set_isolation_level_str(self):
cur = self.conn.cursor()
self.conn.set_session("serializable")
cur.execute("SHOW default_transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback()
self.conn.set_session("repeatable read")
cur.execute("SHOW default_transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'repeatable read')
else:
self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback()
self.conn.set_session("read committed")
cur.execute("SHOW default_transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback()
self.conn.set_session("read uncommitted")
cur.execute("SHOW default_transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'read uncommitted')
else:
self.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback()
def test_bad_isolation_level(self):
self.assertRaises(ValueError, self.conn.set_session, 0)
self.assertRaises(ValueError, self.conn.set_session, 5)
self.assertRaises(ValueError, self.conn.set_session, 'whatever')
def test_set_read_only(self):
cur = self.conn.cursor()
self.conn.set_session(readonly=True)
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
cur = self.conn.cursor()
self.conn.set_session(readonly=None)
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
self.conn.set_session(readonly=False)
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'off')
self.conn.rollback()
def test_set_default(self):
cur = self.conn.cursor()
cur.execute("SHOW default_transaction_isolation;")
default_isolevel = cur.fetchone()[0]
cur.execute("SHOW default_transaction_read_only;")
default_readonly = cur.fetchone()[0]
self.conn.rollback()
self.conn.set_session(isolation_level='serializable', readonly=True)
self.conn.set_session(isolation_level='default', readonly='default')
cur.execute("SHOW default_transaction_isolation;")
self.assertEqual(cur.fetchone()[0], default_isolevel)
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], default_readonly)
@skip_before_postgres(9, 1)
def test_set_deferrable(self):
cur = self.conn.cursor()
self.conn.set_session(readonly=True, deferrable=True)
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
cur.execute("SHOW default_transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
cur.execute("SHOW default_transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
self.conn.set_session(deferrable=False)
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
cur.execute("SHOW default_transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'off')
self.conn.rollback()
@skip_after_postgres(9, 1)
def test_set_deferrable_error(self):
self.assertRaises(psycopg2.ProgrammingError,
self.conn.set_session, readonly=True, deferrable=True)
class AutocommitTests(unittest.TestCase):
def setUp(self):
self.conn = psycopg2.connect(dsn)
def tearDown(self):
if not self.conn.closed:
self.conn.close()
def test_default_no_autocommit(self):
self.assert_(not self.conn.autocommit)
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur = self.conn.cursor()
cur.execute('select 1;')
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_BEGIN)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_INTRANS)
self.conn.rollback()
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
def test_set_autocommit(self):
self.conn.autocommit = True
self.assert_(self.conn.autocommit)
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur = self.conn.cursor()
cur.execute('select 1;')
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
self.conn.autocommit = False
self.assert_(not self.conn.autocommit)
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur.execute('select 1;')
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_BEGIN)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_INTRANS)
def test_set_intrans_error(self):
cur = self.conn.cursor()
cur.execute('select 1;')
self.assertRaises(psycopg2.ProgrammingError,
setattr, self.conn, 'autocommit', True)
def test_set_session_autocommit(self):
self.conn.set_session(autocommit=True)
self.assert_(self.conn.autocommit)
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur = self.conn.cursor()
cur.execute('select 1;')
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
self.conn.set_session(autocommit=False)
self.assert_(not self.conn.autocommit)
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur.execute('select 1;')
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_BEGIN)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_INTRANS)
self.conn.rollback()
self.conn.set_session('serializable', readonly=True, autocommit=True)
self.assert_(self.conn.autocommit)
cur.execute('select 1;')
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur.execute("SHOW default_transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable')
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
def test_suite(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -244,6 +244,15 @@ class CopyTests(unittest.TestCase):
self.assertEqual(ntests, len(string.ascii_letters)) self.assertEqual(ntests, len(string.ascii_letters))
def test_copy_expert_file_refcount(self):
class Whatever(object):
pass
f = Whatever()
curs = self.conn.cursor()
self.assertRaises(TypeError,
curs.copy_expert, 'COPY tcopy (data) FROM STDIN', f)
decorate_all_tests(CopyTests, skip_if_green) decorate_all_tests(CopyTests, skip_if_green)

View File

@ -417,7 +417,11 @@ class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
from mx.DateTime import DateTime from mx.DateTime import DateTime
value = self.execute('select (%s)::timestamp::text', value = self.execute('select (%s)::timestamp::text',
[DateTime(-41, 1, 1, 13, 30, 29.123456)]) [DateTime(-41, 1, 1, 13, 30, 29.123456)])
self.assertEqual(value, '0042-01-01 13:30:29.123456 BC') # microsecs for BC timestamps look not available in PG < 8.4
# but more likely it's determined at compile time.
self.assert_(value in (
'0042-01-01 13:30:29.123456 BC',
'0042-01-01 13:30:29 BC'), value)
def test_adapt_timedelta(self): def test_adapt_timedelta(self):
from mx.DateTime import DateTimeDeltaFrom from mx.DateTime import DateTimeDeltaFrom

View File

@ -275,6 +275,16 @@ class TypesBasicTests(unittest.TestCase):
o2 = self.execute("SELECT %s::bytea AS foo", (o1,)) o2 = self.execute("SELECT %s::bytea AS foo", (o1,))
self.assertEqual(b('x'), o2[0]) self.assertEqual(b('x'), o2[0])
def testNegNumber(self):
d1 = self.execute("select -%s;", (decimal.Decimal('-1.0'),))
self.assertEqual(1, d1)
f1 = self.execute("select -%s;", (-1.0,))
self.assertEqual(1, f1)
i1 = self.execute("select -%s;", (-1,))
self.assertEqual(1, i1)
l1 = self.execute("select -%s;", (-1L,))
self.assertEqual(1, l1)
class AdaptSubclassTest(unittest.TestCase): class AdaptSubclassTest(unittest.TestCase):
def test_adapt_subtype(self): def test_adapt_subtype(self):