mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-22 08:56:34 +03:00
Merge remote-tracking branch 'zalando/feature/replication-protocol' into feature/replication-protocol
This commit is contained in:
commit
058db56430
15
NEWS
15
NEWS
|
@ -1,9 +1,24 @@
|
||||||
Current release
|
Current release
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
What's new in psycopg 2.7
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Added `~psycopg2.__libpq_version__` and
|
||||||
|
`~psycopg2.extensions.libpq_version()` to inspect the version of the
|
||||||
|
``libpq`` library the module was compiled/loaded with
|
||||||
|
(:tickets:`#35, #323`).
|
||||||
|
- The attributes `~connection.notices` and `~connection.notifies` can be
|
||||||
|
customized replacing them with any object exposing an `!append()` method
|
||||||
|
(:ticket:`#326`).
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.6.1
|
What's new in psycopg 2.6.1
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Lists consisting of only `None` are escaped correctly (:ticket:`#285`).
|
||||||
- Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`).
|
- Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`).
|
||||||
- Correctly unlock the connection after error in flush (:ticket:`#294`).
|
- Correctly unlock the connection after error in flush (:ticket:`#294`).
|
||||||
- Fixed ``MinTimeLoggingCursor.callproc()`` (:ticket:`#309`).
|
- Fixed ``MinTimeLoggingCursor.callproc()`` (:ticket:`#309`).
|
||||||
|
|
|
@ -291,7 +291,7 @@ something to read::
|
||||||
else:
|
else:
|
||||||
conn.poll()
|
conn.poll()
|
||||||
while conn.notifies:
|
while conn.notifies:
|
||||||
notify = conn.notifies.pop()
|
notify = conn.notifies.pop(0)
|
||||||
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
|
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
|
||||||
|
|
||||||
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
|
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
|
||||||
|
@ -312,6 +312,10 @@ received from a previous version server will have the
|
||||||
Added `~psycopg2.extensions.Notify` object and handling notification
|
Added `~psycopg2.extensions.Notify` object and handling notification
|
||||||
payload.
|
payload.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
The `~connection.notifies` attribute is writable: it is possible to
|
||||||
|
replace it with any object exposing an `!append()` method. An useful
|
||||||
|
example would be to use a `~collections.deque` object.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
|
|
@ -419,8 +419,8 @@ The ``connection`` class
|
||||||
|
|
||||||
By default, any query execution, including a simple :sql:`SELECT`
|
By default, any query execution, including a simple :sql:`SELECT`
|
||||||
will start a transaction: for long-running programs, if no further
|
will start a transaction: for long-running programs, if no further
|
||||||
action is taken, the session will remain "idle in transaction", a
|
action is taken, the session will remain "idle in transaction", an
|
||||||
condition non desiderable for several reasons (locks are held by
|
undesirable condition for several reasons (locks are held by
|
||||||
the session, tables bloat...). For long lived scripts, either
|
the session, tables bloat...). For long lived scripts, either
|
||||||
ensure to terminate a transaction as soon as possible or use an
|
ensure to terminate a transaction as soon as possible or use an
|
||||||
autocommit connection.
|
autocommit connection.
|
||||||
|
@ -483,13 +483,21 @@ The ``connection`` class
|
||||||
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n',
|
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n',
|
||||||
'NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"\n']
|
'NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"\n']
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
The `!notices` attribute is writable: the user may replace it
|
||||||
|
with any Python object exposing an `!append()` method. If
|
||||||
|
appending raises an exception the notice is silently
|
||||||
|
dropped.
|
||||||
|
|
||||||
To avoid a leak in case excessive notices are generated, only the last
|
To avoid a leak in case excessive notices are generated, only the last
|
||||||
50 messages are kept.
|
50 messages are kept. This check is only in place if the `!notices`
|
||||||
|
attribute is a list: if any other object is used it will be up to the
|
||||||
|
user to guard from leakage.
|
||||||
|
|
||||||
You can configure what messages to receive using `PostgreSQL logging
|
You can configure what messages to receive using `PostgreSQL logging
|
||||||
configuration parameters`__ such as ``log_statement``,
|
configuration parameters`__ such as ``log_statement``,
|
||||||
``client_min_messages``, ``log_min_duration_statement`` etc.
|
``client_min_messages``, ``log_min_duration_statement`` etc.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-logging.html
|
.. __: http://www.postgresql.org/docs/current/static/runtime-config-logging.html
|
||||||
|
|
||||||
|
|
||||||
|
@ -506,6 +514,12 @@ The ``connection`` class
|
||||||
the payload was not accessible. To keep backward compatibility,
|
the payload was not accessible. To keep backward compatibility,
|
||||||
`!Notify` objects can still be accessed as 2 items tuples.
|
`!Notify` objects can still be accessed as 2 items tuples.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
The `!notifies` attribute is writable: the user may replace it
|
||||||
|
with any Python object exposing an `!append()` method. If
|
||||||
|
appending raises an exception the notification is silently
|
||||||
|
dropped.
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: cursor_factory
|
.. attribute:: cursor_factory
|
||||||
|
|
||||||
|
|
|
@ -197,6 +197,18 @@ functionalities defined by the |DBAPI|_.
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
.. versionadded:: 2.2.0
|
||||||
|
|
||||||
|
.. function:: libpq_version()
|
||||||
|
|
||||||
|
Return the version number of the ``libpq`` dynamic library loaded as an
|
||||||
|
integer, in the same format of `~connection.server_version`.
|
||||||
|
|
||||||
|
Raise `~psycopg2.NotSupportedError` if the ``psycopg2`` module was
|
||||||
|
compiled with a ``libpq`` version lesser than 9.1 (which can be detected
|
||||||
|
by the `~psycopg2.__libpq_version__` constant).
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQlibVersion()`__.
|
||||||
|
|
||||||
|
.. __: http://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQLIBVERSION
|
||||||
|
|
||||||
.. _sql-adaptation-objects:
|
.. _sql-adaptation-objects:
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,13 @@ The module interface respects the standard defined in the |DBAPI|_.
|
||||||
by the interface. For `psycopg2` is ``pyformat``. See also
|
by the interface. For `psycopg2` is ``pyformat``. See also
|
||||||
:ref:`query-parameters`.
|
:ref:`query-parameters`.
|
||||||
|
|
||||||
|
.. data:: __libpq_version__
|
||||||
|
|
||||||
|
Integer constant reporting the version of the ``libpq`` library this
|
||||||
|
``psycopg2`` module was compiled with (in the same format of
|
||||||
|
`~connection.server_version`). If this value is greater or equal than
|
||||||
|
``90100`` then you may query the version of the actually loaded library
|
||||||
|
using the `~psycopg2.extensions.libpq_version()` function.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
|
|
@ -679,7 +679,7 @@ older versions).
|
||||||
|
|
||||||
By default even a simple :sql:`SELECT` will start a transaction: in
|
By default even a simple :sql:`SELECT` will start a transaction: in
|
||||||
long-running programs, if no further action is taken, the session will
|
long-running programs, if no further action is taken, the session will
|
||||||
remain "idle in transaction", a condition non desiderable for several
|
remain "idle in transaction", an undesirable condition for several
|
||||||
reasons (locks are held by the session, tables bloat...). For long lived
|
reasons (locks are held by the session, tables bloat...). For long lived
|
||||||
scripts, either make sure to terminate a transaction as soon as possible or
|
scripts, either make sure to terminate a transaction as soon as possible or
|
||||||
use an autocommit connection.
|
use an autocommit connection.
|
||||||
|
|
|
@ -57,7 +57,7 @@ from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError
|
||||||
from psycopg2._psycopg import NotSupportedError, OperationalError
|
from psycopg2._psycopg import NotSupportedError, OperationalError
|
||||||
|
|
||||||
from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle
|
from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle
|
||||||
from psycopg2._psycopg import __version__
|
from psycopg2._psycopg import __version__, __libpq_version__
|
||||||
|
|
||||||
from psycopg2 import tz
|
from psycopg2 import tz
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, replicationMessage, lobject, Xid
|
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, replicationMessage, lobject, Xid, libpq_version
|
||||||
from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type
|
from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type
|
||||||
from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics, Column
|
from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics, Column
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ static unsigned char *
|
||||||
binary_escape(unsigned char *from, size_t from_length,
|
binary_escape(unsigned char *from, size_t from_length,
|
||||||
size_t *to_length, PGconn *conn)
|
size_t *to_length, PGconn *conn)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_HEX >= 0x080104
|
#if PG_VERSION_NUM >= 80104
|
||||||
if (conn)
|
if (conn)
|
||||||
return PQescapeByteaConn(conn, from, from_length, to_length);
|
return PQescapeByteaConn(conn, from, from_length, to_length);
|
||||||
else
|
else
|
||||||
|
|
|
@ -39,6 +39,14 @@ list_quote(listObject *self)
|
||||||
/* adapt the list by calling adapt() recursively and then wrapping
|
/* adapt the list by calling adapt() recursively and then wrapping
|
||||||
everything into "ARRAY[]" */
|
everything into "ARRAY[]" */
|
||||||
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL;
|
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL;
|
||||||
|
|
||||||
|
/* list consisting of only NULL don't work with the ARRAY[] construct
|
||||||
|
* so we use the {NULL,...} syntax. Note however that list of lists where
|
||||||
|
* some element is a list of only null still fails: for that we should use
|
||||||
|
* the '{...}' syntax uniformly but we cannot do it in the current
|
||||||
|
* infrastructure. TODO in psycopg3 */
|
||||||
|
int all_nulls = 1;
|
||||||
|
|
||||||
Py_ssize_t i, len;
|
Py_ssize_t i, len;
|
||||||
|
|
||||||
len = PyList_GET_SIZE(self->wrapped);
|
len = PyList_GET_SIZE(self->wrapped);
|
||||||
|
@ -60,6 +68,7 @@ list_quote(listObject *self)
|
||||||
quoted = microprotocol_getquoted(wrapped,
|
quoted = microprotocol_getquoted(wrapped,
|
||||||
(connectionObject*)self->connection);
|
(connectionObject*)self->connection);
|
||||||
if (quoted == NULL) goto error;
|
if (quoted == NULL) goto error;
|
||||||
|
all_nulls = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* here we don't loose a refcnt: SET_ITEM does not change the
|
/* here we don't loose a refcnt: SET_ITEM does not change the
|
||||||
|
@ -74,7 +83,12 @@ list_quote(listObject *self)
|
||||||
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
|
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
|
||||||
if (joined == NULL) goto error;
|
if (joined == NULL) goto error;
|
||||||
|
|
||||||
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
|
/* PG doesn't like ARRAY[NULL..] */
|
||||||
|
if (!all_nulls) {
|
||||||
|
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
|
||||||
|
} else {
|
||||||
|
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined));
|
||||||
|
}
|
||||||
|
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(tmp);
|
Py_XDECREF(tmp);
|
||||||
|
|
|
@ -71,7 +71,7 @@ extern HIDDEN PyTypeObject connectionType;
|
||||||
|
|
||||||
struct connectionObject_notice {
|
struct connectionObject_notice {
|
||||||
struct connectionObject_notice *next;
|
struct connectionObject_notice *next;
|
||||||
const char *message;
|
char *message;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* the typedef is forward-declared in psycopg.h */
|
/* the typedef is forward-declared in psycopg.h */
|
||||||
|
@ -106,8 +106,8 @@ struct connectionObject {
|
||||||
|
|
||||||
/* notice processing */
|
/* notice processing */
|
||||||
PyObject *notice_list;
|
PyObject *notice_list;
|
||||||
PyObject *notice_filter;
|
|
||||||
struct connectionObject_notice *notice_pending;
|
struct connectionObject_notice *notice_pending;
|
||||||
|
struct connectionObject_notice *last_notice;
|
||||||
|
|
||||||
/* notifies */
|
/* notifies */
|
||||||
PyObject *notifies;
|
PyObject *notifies;
|
||||||
|
|
|
@ -87,13 +87,20 @@ conn_notice_callback(void *args, const char *message)
|
||||||
/* Discard the notice in case of failed allocation. */
|
/* Discard the notice in case of failed allocation. */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
notice->next = NULL;
|
||||||
notice->message = strdup(message);
|
notice->message = strdup(message);
|
||||||
if (NULL == notice->message) {
|
if (NULL == notice->message) {
|
||||||
free(notice);
|
free(notice);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notice->next = self->notice_pending;
|
|
||||||
self->notice_pending = notice;
|
if (NULL == self->last_notice) {
|
||||||
|
self->notice_pending = self->last_notice = notice;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self->last_notice->next = notice;
|
||||||
|
self->last_notice = notice;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Expose the notices received as Python objects.
|
/* Expose the notices received as Python objects.
|
||||||
|
@ -104,44 +111,60 @@ void
|
||||||
conn_notice_process(connectionObject *self)
|
conn_notice_process(connectionObject *self)
|
||||||
{
|
{
|
||||||
struct connectionObject_notice *notice;
|
struct connectionObject_notice *notice;
|
||||||
Py_ssize_t nnotices;
|
PyObject *msg = NULL;
|
||||||
|
PyObject *tmp = NULL;
|
||||||
|
static PyObject *append;
|
||||||
|
|
||||||
if (NULL == self->notice_pending) {
|
if (NULL == self->notice_pending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
notice = self->notice_pending;
|
if (!append) {
|
||||||
nnotices = PyList_GET_SIZE(self->notice_list);
|
if (!(append = Text_FromUTF8("append"))) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notice = self->notice_pending;
|
||||||
while (notice != NULL) {
|
while (notice != NULL) {
|
||||||
PyObject *msg;
|
|
||||||
msg = conn_text_from_chars(self, notice->message);
|
|
||||||
Dprintf("conn_notice_process: %s", notice->message);
|
Dprintf("conn_notice_process: %s", notice->message);
|
||||||
|
|
||||||
/* Respect the order in which notices were produced,
|
if (!(msg = conn_text_from_chars(self, notice->message))) { goto error; }
|
||||||
because in notice_list they are reversed (see ticket #9) */
|
|
||||||
if (msg) {
|
if (!(tmp = PyObject_CallMethodObjArgs(
|
||||||
PyList_Insert(self->notice_list, nnotices, msg);
|
self->notice_list, append, msg, NULL))) {
|
||||||
Py_DECREF(msg);
|
|
||||||
}
|
goto error;
|
||||||
else {
|
|
||||||
/* We don't really have a way to report errors, so gulp it.
|
|
||||||
* The function should only fail for out of memory, so we are
|
|
||||||
* likely going to die anyway. */
|
|
||||||
PyErr_Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Py_DECREF(tmp); tmp = NULL;
|
||||||
|
Py_DECREF(msg); msg = NULL;
|
||||||
|
|
||||||
notice = notice->next;
|
notice = notice->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove the oldest item if the queue is getting too long. */
|
/* Remove the oldest item if the queue is getting too long. */
|
||||||
nnotices = PyList_GET_SIZE(self->notice_list);
|
if (PyList_Check(self->notice_list)) {
|
||||||
if (nnotices > CONN_NOTICES_LIMIT) {
|
Py_ssize_t nnotices;
|
||||||
PySequence_DelSlice(self->notice_list,
|
nnotices = PyList_GET_SIZE(self->notice_list);
|
||||||
0, nnotices - CONN_NOTICES_LIMIT);
|
if (nnotices > CONN_NOTICES_LIMIT) {
|
||||||
|
if (-1 == PySequence_DelSlice(self->notice_list,
|
||||||
|
0, nnotices - CONN_NOTICES_LIMIT)) {
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_notice_clean(self);
|
conn_notice_clean(self);
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Py_XDECREF(tmp);
|
||||||
|
Py_XDECREF(msg);
|
||||||
|
conn_notice_clean(self);
|
||||||
|
|
||||||
|
/* TODO: the caller doesn't expects errors from us */
|
||||||
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -154,11 +177,11 @@ conn_notice_clean(connectionObject *self)
|
||||||
while (notice != NULL) {
|
while (notice != NULL) {
|
||||||
tmp = notice;
|
tmp = notice;
|
||||||
notice = notice->next;
|
notice = notice->next;
|
||||||
free((void*)tmp->message);
|
free(tmp->message);
|
||||||
free(tmp);
|
free(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
self->notice_pending = NULL;
|
self->last_notice = self->notice_pending = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -173,6 +196,15 @@ conn_notifies_process(connectionObject *self)
|
||||||
PGnotify *pgn = NULL;
|
PGnotify *pgn = NULL;
|
||||||
PyObject *notify = NULL;
|
PyObject *notify = NULL;
|
||||||
PyObject *pid = NULL, *channel = NULL, *payload = NULL;
|
PyObject *pid = NULL, *channel = NULL, *payload = NULL;
|
||||||
|
PyObject *tmp = NULL;
|
||||||
|
|
||||||
|
static PyObject *append;
|
||||||
|
|
||||||
|
if (!append) {
|
||||||
|
if (!(append = Text_FromUTF8("append"))) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while ((pgn = PQnotifies(self->pgconn)) != NULL) {
|
while ((pgn = PQnotifies(self->pgconn)) != NULL) {
|
||||||
|
|
||||||
|
@ -192,7 +224,11 @@ conn_notifies_process(connectionObject *self)
|
||||||
Py_DECREF(channel); channel = NULL;
|
Py_DECREF(channel); channel = NULL;
|
||||||
Py_DECREF(payload); payload = NULL;
|
Py_DECREF(payload); payload = NULL;
|
||||||
|
|
||||||
PyList_Append(self->notifies, (PyObject *)notify);
|
if (!(tmp = PyObject_CallMethodObjArgs(
|
||||||
|
self->notifies, append, notify, NULL))) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_DECREF(tmp); tmp = NULL;
|
||||||
|
|
||||||
Py_DECREF(notify); notify = NULL;
|
Py_DECREF(notify); notify = NULL;
|
||||||
PQfreemem(pgn); pgn = NULL;
|
PQfreemem(pgn); pgn = NULL;
|
||||||
|
@ -201,6 +237,7 @@ conn_notifies_process(connectionObject *self)
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (pgn) { PQfreemem(pgn); }
|
if (pgn) { PQfreemem(pgn); }
|
||||||
|
Py_XDECREF(tmp);
|
||||||
Py_XDECREF(notify);
|
Py_XDECREF(notify);
|
||||||
Py_XDECREF(pid);
|
Py_XDECREF(pid);
|
||||||
Py_XDECREF(channel);
|
Py_XDECREF(channel);
|
||||||
|
|
|
@ -1001,8 +1001,8 @@ static struct PyMemberDef connectionObject_members[] = {
|
||||||
"True if the connection is closed."},
|
"True if the connection is closed."},
|
||||||
{"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), 0},
|
||||||
{"notifies", T_OBJECT, offsetof(connectionObject, notifies), READONLY},
|
{"notifies", T_OBJECT, offsetof(connectionObject, notifies), 0},
|
||||||
{"dsn", T_STRING, offsetof(connectionObject, dsn), READONLY,
|
{"dsn", T_STRING, offsetof(connectionObject, dsn), READONLY,
|
||||||
"The current connection string."},
|
"The current connection string."},
|
||||||
{"async", T_LONG, offsetof(connectionObject, async), READONLY,
|
{"async", T_LONG, offsetof(connectionObject, async), READONLY,
|
||||||
|
@ -1105,7 +1105,6 @@ connection_clear(connectionObject *self)
|
||||||
Py_CLEAR(self->tpc_xid);
|
Py_CLEAR(self->tpc_xid);
|
||||||
Py_CLEAR(self->async_cursor);
|
Py_CLEAR(self->async_cursor);
|
||||||
Py_CLEAR(self->notice_list);
|
Py_CLEAR(self->notice_list);
|
||||||
Py_CLEAR(self->notice_filter);
|
|
||||||
Py_CLEAR(self->notifies);
|
Py_CLEAR(self->notifies);
|
||||||
Py_CLEAR(self->string_types);
|
Py_CLEAR(self->string_types);
|
||||||
Py_CLEAR(self->binary_types);
|
Py_CLEAR(self->binary_types);
|
||||||
|
@ -1181,7 +1180,6 @@ connection_traverse(connectionObject *self, visitproc visit, void *arg)
|
||||||
Py_VISIT((PyObject *)(self->tpc_xid));
|
Py_VISIT((PyObject *)(self->tpc_xid));
|
||||||
Py_VISIT(self->async_cursor);
|
Py_VISIT(self->async_cursor);
|
||||||
Py_VISIT(self->notice_list);
|
Py_VISIT(self->notice_list);
|
||||||
Py_VISIT(self->notice_filter);
|
|
||||||
Py_VISIT(self->notifies);
|
Py_VISIT(self->notifies);
|
||||||
Py_VISIT(self->string_types);
|
Py_VISIT(self->string_types);
|
||||||
Py_VISIT(self->binary_types);
|
Py_VISIT(self->binary_types);
|
||||||
|
|
|
@ -474,7 +474,7 @@ lobject_export(lobjectObject *self, const char *filename)
|
||||||
return retvalue;
|
return retvalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_HEX >= 0x080300
|
#if PG_VERSION_NUM >= 80300
|
||||||
|
|
||||||
RAISES_NEG int
|
RAISES_NEG int
|
||||||
lobject_truncate(lobjectObject *self, size_t len)
|
lobject_truncate(lobjectObject *self, size_t len)
|
||||||
|
@ -511,4 +511,4 @@ lobject_truncate(lobjectObject *self, size_t len)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* PG_VERSION_HEX >= 0x080300 */
|
#endif /* PG_VERSION_NUM >= 80300 */
|
||||||
|
|
|
@ -266,7 +266,7 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
|
||||||
return closed;
|
return closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_HEX >= 0x080300
|
#if PG_VERSION_NUM >= 80300
|
||||||
|
|
||||||
#define psyco_lobj_truncate_doc \
|
#define psyco_lobj_truncate_doc \
|
||||||
"truncate(len=0) -- Truncate large object to given size."
|
"truncate(len=0) -- Truncate large object to given size."
|
||||||
|
@ -327,10 +327,10 @@ static struct PyMethodDef lobjectObject_methods[] = {
|
||||||
METH_NOARGS, psyco_lobj_unlink_doc},
|
METH_NOARGS, psyco_lobj_unlink_doc},
|
||||||
{"export",(PyCFunction)psyco_lobj_export,
|
{"export",(PyCFunction)psyco_lobj_export,
|
||||||
METH_VARARGS, psyco_lobj_export_doc},
|
METH_VARARGS, psyco_lobj_export_doc},
|
||||||
#if PG_VERSION_HEX >= 0x080300
|
#if PG_VERSION_NUM >= 80300
|
||||||
{"truncate",(PyCFunction)psyco_lobj_truncate,
|
{"truncate",(PyCFunction)psyco_lobj_truncate,
|
||||||
METH_VARARGS, psyco_lobj_truncate_doc},
|
METH_VARARGS, psyco_lobj_truncate_doc},
|
||||||
#endif /* PG_VERSION_HEX >= 0x080300 */
|
#endif /* PG_VERSION_NUM >= 80300 */
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,7 @@ psyco_libcrypto_threads_init(void)
|
||||||
if (PyImport_ImportModule("ssl") != NULL) {
|
if (PyImport_ImportModule("ssl") != NULL) {
|
||||||
/* disable libcrypto setup in libpq, so it won't stomp on the callbacks
|
/* disable libcrypto setup in libpq, so it won't stomp on the callbacks
|
||||||
that have already been set up */
|
that have already been set up */
|
||||||
#if PG_VERSION_HEX >= 0x080400
|
#if PG_VERSION_NUM >= 80400
|
||||||
PQinitOpenSSL(1, 0);
|
PQinitOpenSSL(1, 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -301,6 +301,19 @@ exit:
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define psyco_libpq_version_doc "Query actual libpq version loaded."
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
psyco_libpq_version(PyObject *self)
|
||||||
|
{
|
||||||
|
#if PG_VERSION_NUM >= 90100
|
||||||
|
return PyInt_FromLong(PQlibVersion());
|
||||||
|
#else
|
||||||
|
PyErr_SetString(NotSupportedError, "version discovery is not supported in libpq < 9.1");
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* psyco_encodings_fill
|
/* psyco_encodings_fill
|
||||||
|
|
||||||
Fill the module's postgresql<->python encoding table */
|
Fill the module's postgresql<->python encoding table */
|
||||||
|
@ -705,6 +718,8 @@ static PyMethodDef psycopgMethods[] = {
|
||||||
METH_VARARGS|METH_KEYWORDS, typecast_from_python_doc},
|
METH_VARARGS|METH_KEYWORDS, typecast_from_python_doc},
|
||||||
{"new_array_type", (PyCFunction)typecast_array_from_python,
|
{"new_array_type", (PyCFunction)typecast_array_from_python,
|
||||||
METH_VARARGS|METH_KEYWORDS, typecast_array_from_python_doc},
|
METH_VARARGS|METH_KEYWORDS, typecast_array_from_python_doc},
|
||||||
|
{"libpq_version", (PyCFunction)psyco_libpq_version,
|
||||||
|
METH_NOARGS, psyco_libpq_version_doc},
|
||||||
|
|
||||||
{"Date", (PyCFunction)psyco_Date,
|
{"Date", (PyCFunction)psyco_Date,
|
||||||
METH_VARARGS, psyco_Date_doc},
|
METH_VARARGS, psyco_Date_doc},
|
||||||
|
@ -904,6 +919,7 @@ INIT_MODULE(_psycopg)(void)
|
||||||
/* set some module's parameters */
|
/* set some module's parameters */
|
||||||
PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION);
|
PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION);
|
||||||
PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver");
|
PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver");
|
||||||
|
PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
|
||||||
PyModule_AddObject(module, "apilevel", Text_FromUTF8(APILEVEL));
|
PyModule_AddObject(module, "apilevel", Text_FromUTF8(APILEVEL));
|
||||||
PyModule_AddObject(module, "threadsafety", PyInt_FromLong(THREADSAFETY));
|
PyModule_AddObject(module, "threadsafety", PyInt_FromLong(THREADSAFETY));
|
||||||
PyModule_AddObject(module, "paramstyle", Text_FromUTF8(PARAMSTYLE));
|
PyModule_AddObject(module, "paramstyle", Text_FromUTF8(PARAMSTYLE));
|
||||||
|
|
|
@ -62,7 +62,7 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
#if PG_VERSION_HEX >= 0x080104
|
#if PG_VERSION_NUM >= 80104
|
||||||
int err;
|
int err;
|
||||||
if (conn && conn->pgconn)
|
if (conn && conn->pgconn)
|
||||||
ql = PQescapeStringConn(conn->pgconn, to+eq+1, from, len, &err);
|
ql = PQescapeStringConn(conn->pgconn, to+eq+1, from, len, &err);
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -416,7 +416,7 @@ class psycopg_build_ext(build_ext):
|
||||||
% pgversion)
|
% pgversion)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
define_macros.append(("PG_VERSION_HEX", "0x%02X%02X%02X" %
|
define_macros.append(("PG_VERSION_NUM", "%d%02d%02d" %
|
||||||
(pgmajor, pgminor, pgpatch)))
|
(pgmajor, pgminor, pgpatch)))
|
||||||
|
|
||||||
# enable lo64 if libpq >= 9.3 and Python 64 bits
|
# enable lo64 if libpq >= 9.3 and Python 64 bits
|
||||||
|
|
|
@ -129,6 +129,42 @@ class ConnectionTests(ConnectingTestCase):
|
||||||
self.assertEqual(50, len(conn.notices))
|
self.assertEqual(50, len(conn.notices))
|
||||||
self.assert_('table99' in conn.notices[-1], conn.notices[-1])
|
self.assert_('table99' in conn.notices[-1], conn.notices[-1])
|
||||||
|
|
||||||
|
def test_notices_deque(self):
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
conn = self.conn
|
||||||
|
self.conn.notices = deque()
|
||||||
|
cur = conn.cursor()
|
||||||
|
if self.conn.server_version >= 90300:
|
||||||
|
cur.execute("set client_min_messages=debug1")
|
||||||
|
|
||||||
|
cur.execute("create temp table table1 (id serial); create temp table table2 (id serial);")
|
||||||
|
cur.execute("create temp table table3 (id serial); create temp table table4 (id serial);")
|
||||||
|
self.assertEqual(len(conn.notices), 4)
|
||||||
|
self.assert_('table1' in conn.notices.popleft())
|
||||||
|
self.assert_('table2' in conn.notices.popleft())
|
||||||
|
self.assert_('table3' in conn.notices.popleft())
|
||||||
|
self.assert_('table4' in conn.notices.popleft())
|
||||||
|
self.assertEqual(len(conn.notices), 0)
|
||||||
|
|
||||||
|
# not limited, but no error
|
||||||
|
for i in range(0, 100, 10):
|
||||||
|
sql = " ".join(["create temp table table2_%d (id serial);" % j for j in range(i, i+10)])
|
||||||
|
cur.execute(sql)
|
||||||
|
|
||||||
|
self.assertEqual(100, len(conn.notices))
|
||||||
|
|
||||||
|
def test_notices_noappend(self):
|
||||||
|
conn = self.conn
|
||||||
|
self.conn.notices = None # will make an error swallowes ok
|
||||||
|
cur = conn.cursor()
|
||||||
|
if self.conn.server_version >= 90300:
|
||||||
|
cur.execute("set client_min_messages=debug1")
|
||||||
|
|
||||||
|
cur.execute("create temp table table1 (id serial);")
|
||||||
|
|
||||||
|
self.assertEqual(self.conn.notices, None)
|
||||||
|
|
||||||
def test_server_version(self):
|
def test_server_version(self):
|
||||||
self.assert_(self.conn.server_version)
|
self.assert_(self.conn.server_version)
|
||||||
|
|
||||||
|
|
|
@ -320,6 +320,15 @@ import _psycopg
|
||||||
self.assertEqual(0, proc.returncode)
|
self.assertEqual(0, proc.returncode)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVersionDiscovery(unittest.TestCase):
|
||||||
|
def test_libpq_version(self):
|
||||||
|
self.assertTrue(type(psycopg2.__libpq_version__) is int)
|
||||||
|
try:
|
||||||
|
self.assertTrue(type(psycopg2.extensions.libpq_version()) is int)
|
||||||
|
except NotSupportedError:
|
||||||
|
self.assertTrue(psycopg2.__libpq_version__ < 90100)
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,27 @@ conn.close()
|
||||||
self.assertEqual('foo', notify.channel)
|
self.assertEqual('foo', notify.channel)
|
||||||
self.assertEqual('Hello, world!', notify.payload)
|
self.assertEqual('Hello, world!', notify.payload)
|
||||||
|
|
||||||
|
def test_notify_deque(self):
|
||||||
|
from collections import deque
|
||||||
|
self.autocommit(self.conn)
|
||||||
|
self.conn.notifies = deque()
|
||||||
|
self.listen('foo')
|
||||||
|
self.notify('foo').communicate()
|
||||||
|
time.sleep(0.5)
|
||||||
|
self.conn.poll()
|
||||||
|
notify = self.conn.notifies.popleft()
|
||||||
|
self.assert_(isinstance(notify, psycopg2.extensions.Notify))
|
||||||
|
self.assertEqual(len(self.conn.notifies), 0)
|
||||||
|
|
||||||
|
def test_notify_noappend(self):
|
||||||
|
self.autocommit(self.conn)
|
||||||
|
self.conn.notifies = None
|
||||||
|
self.listen('foo')
|
||||||
|
self.notify('foo').communicate()
|
||||||
|
time.sleep(0.5)
|
||||||
|
self.conn.poll()
|
||||||
|
self.assertEqual(self.conn.notifies, None)
|
||||||
|
|
||||||
def test_notify_init(self):
|
def test_notify_init(self):
|
||||||
n = psycopg2.extensions.Notify(10, 'foo')
|
n = psycopg2.extensions.Notify(10, 'foo')
|
||||||
self.assertEqual(10, n.pid)
|
self.assertEqual(10, n.pid)
|
||||||
|
@ -192,6 +213,7 @@ conn.close()
|
||||||
self.assertNotEqual(hash(Notify(10, 'foo', 'bar')),
|
self.assertNotEqual(hash(Notify(10, 'foo', 'bar')),
|
||||||
hash(Notify(10, 'foo')))
|
hash(Notify(10, 'foo')))
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
|
||||||
|
|
|
@ -192,6 +192,40 @@ class TypesBasicTests(ConnectingTestCase):
|
||||||
self.assertRaises(psycopg2.DataError,
|
self.assertRaises(psycopg2.DataError,
|
||||||
psycopg2.extensions.STRINGARRAY, b(s), curs)
|
psycopg2.extensions.STRINGARRAY, b(s), curs)
|
||||||
|
|
||||||
|
def testArrayOfNulls(self):
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
curs.execute("""
|
||||||
|
create table na (
|
||||||
|
texta text[],
|
||||||
|
inta int[],
|
||||||
|
boola boolean[],
|
||||||
|
|
||||||
|
textaa text[][],
|
||||||
|
intaa int[][],
|
||||||
|
boolaa boolean[][]
|
||||||
|
)""")
|
||||||
|
|
||||||
|
curs.execute("insert into na (texta) values (%s)", ([None],))
|
||||||
|
curs.execute("insert into na (texta) values (%s)", (['a', None],))
|
||||||
|
curs.execute("insert into na (texta) values (%s)", ([None, None],))
|
||||||
|
curs.execute("insert into na (inta) values (%s)", ([None],))
|
||||||
|
curs.execute("insert into na (inta) values (%s)", ([42, None],))
|
||||||
|
curs.execute("insert into na (inta) values (%s)", ([None, None],))
|
||||||
|
curs.execute("insert into na (boola) values (%s)", ([None],))
|
||||||
|
curs.execute("insert into na (boola) values (%s)", ([True, None],))
|
||||||
|
curs.execute("insert into na (boola) values (%s)", ([None, None],))
|
||||||
|
|
||||||
|
# TODO: array of array of nulls are not supported yet
|
||||||
|
# curs.execute("insert into na (textaa) values (%s)", ([[None]],))
|
||||||
|
curs.execute("insert into na (textaa) values (%s)", ([['a', None]],))
|
||||||
|
# curs.execute("insert into na (textaa) values (%s)", ([[None, None]],))
|
||||||
|
# curs.execute("insert into na (intaa) values (%s)", ([[None]],))
|
||||||
|
curs.execute("insert into na (intaa) values (%s)", ([[42, None]],))
|
||||||
|
# curs.execute("insert into na (intaa) values (%s)", ([[None, None]],))
|
||||||
|
# curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
|
||||||
|
curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],))
|
||||||
|
# curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
|
||||||
|
|
||||||
@testutils.skip_from_python(3)
|
@testutils.skip_from_python(3)
|
||||||
def testTypeRoundtripBuffer(self):
|
def testTypeRoundtripBuffer(self):
|
||||||
o1 = buffer("".join(map(chr, range(256))))
|
o1 = buffer("".join(map(chr, range(256))))
|
||||||
|
|
|
@ -236,6 +236,43 @@ def skip_after_postgres(*ver):
|
||||||
return skip_after_postgres__
|
return skip_after_postgres__
|
||||||
return skip_after_postgres_
|
return skip_after_postgres_
|
||||||
|
|
||||||
|
def libpq_version():
|
||||||
|
import psycopg2
|
||||||
|
v = psycopg2.__libpq_version__
|
||||||
|
if v >= 90100:
|
||||||
|
v = psycopg2.extensions.libpq_version()
|
||||||
|
return v
|
||||||
|
|
||||||
|
def skip_before_libpq(*ver):
|
||||||
|
"""Skip a test if libpq we're linked to is older than a certain version."""
|
||||||
|
ver = ver + (0,) * (3 - len(ver))
|
||||||
|
def skip_before_libpq_(f):
|
||||||
|
@wraps(f)
|
||||||
|
def skip_before_libpq__(self):
|
||||||
|
v = libpq_version()
|
||||||
|
if v < int("%d%02d%02d" % ver):
|
||||||
|
return self.skipTest("skipped because libpq %d" % v)
|
||||||
|
else:
|
||||||
|
return f(self)
|
||||||
|
|
||||||
|
return skip_before_libpq__
|
||||||
|
return skip_before_libpq_
|
||||||
|
|
||||||
|
def skip_after_libpq(*ver):
|
||||||
|
"""Skip a test if libpq we're linked to is newer than a certain version."""
|
||||||
|
ver = ver + (0,) * (3 - len(ver))
|
||||||
|
def skip_after_libpq_(f):
|
||||||
|
@wraps(f)
|
||||||
|
def skip_after_libpq__(self):
|
||||||
|
v = libpq_version()
|
||||||
|
if v >= int("%d%02d%02d" % ver):
|
||||||
|
return self.skipTest("skipped because libpq %s" % v)
|
||||||
|
else:
|
||||||
|
return f(self)
|
||||||
|
|
||||||
|
return skip_after_libpq__
|
||||||
|
return skip_after_libpq_
|
||||||
|
|
||||||
def skip_before_python(*ver):
|
def skip_before_python(*ver):
|
||||||
"""Skip a test on Python before a certain version."""
|
"""Skip a test on Python before a certain version."""
|
||||||
def skip_before_python_(f):
|
def skip_before_python_(f):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user