mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-23 01:16:34 +03:00
Merge branch 'master' into fast-codecs
This commit is contained in:
commit
7caba160b7
21
.travis.yml
21
.travis.yml
|
@ -1,13 +1,24 @@
|
||||||
|
# Travis CI configuration file for psycopg2
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- 2.6
|
|
||||||
- 2.7
|
- 2.7
|
||||||
|
- 3.6-dev
|
||||||
before_script:
|
- 2.6
|
||||||
- psql -c 'create database psycopg2_test;' -U postgres
|
- 3.5
|
||||||
|
- 3.4
|
||||||
|
- 3.3
|
||||||
|
- 3.2
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
|
- sudo scripts/travis_prepare.sh
|
||||||
|
|
||||||
script: make check
|
script:
|
||||||
|
- scripts/travis_test.sh
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
|
3
NEWS
3
NEWS
|
@ -24,6 +24,7 @@ New features:
|
||||||
adapter is deprecated (:tickets:`#317, #343, #387`).
|
adapter is deprecated (:tickets:`#317, #343, #387`).
|
||||||
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
|
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
|
||||||
- Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`).
|
- Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`).
|
||||||
|
- `~cursor.callproc()` now accepts a dictionary of parameters (:ticket:`#381`).
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ What's new in psycopg 2.6.3
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
- Throw an exception trying to pass ``NULL`` chars as parameters
|
- Throw an exception trying to pass ``NULL`` chars as parameters
|
||||||
(:ticket:`#420).
|
(:ticket:`#420`).
|
||||||
- Make `~psycopg2.extras.Range` objects picklable (:ticket:`#462`).
|
- Make `~psycopg2.extras.Range` objects picklable (:ticket:`#462`).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,3 +44,8 @@ For any other resource (source code repository, bug tracker, mailing list)
|
||||||
please check the `project homepage`__.
|
please check the `project homepage`__.
|
||||||
|
|
||||||
.. __: http://initd.org/psycopg/
|
.. __: http://initd.org/psycopg/
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.org/psycopg/psycopg2.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/psycopg/psycopg2
|
||||||
|
:alt: Build Status
|
||||||
|
|
|
@ -61,8 +61,8 @@ except ImportError:
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'py': ('http://docs.python.org/', None),
|
'py': ('http://docs.python.org/2', None),
|
||||||
'py3': ('http://docs.python.org/3.4', None),
|
'py3': ('http://docs.python.org/3', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Pattern to generate links to the bug tracker
|
# Pattern to generate links to the bug tracker
|
||||||
|
|
|
@ -201,13 +201,19 @@ The ``cursor`` class
|
||||||
|
|
||||||
Call a stored database procedure with the given name. The sequence of
|
Call a stored database procedure with the given name. The sequence of
|
||||||
parameters must contain one entry for each argument that the procedure
|
parameters must contain one entry for each argument that the procedure
|
||||||
expects. The result of the call is returned as modified copy of the
|
expects. Overloaded procedures are supported. Named parameters can be
|
||||||
input sequence. Input parameters are left untouched, output and
|
used by supplying the parameters as a dictionary.
|
||||||
input/output parameters replaced with possibly new values.
|
|
||||||
|
|
||||||
The procedure may also provide a result set as output. This must then
|
This function is, at present, not DBAPI-compliant. The return value is
|
||||||
be made available through the standard |fetch*|_ methods.
|
supposed to consist of the sequence of parameters with modified output
|
||||||
|
and input/output parameters. In future versions, the DBAPI-compliant
|
||||||
|
return value may be implemented, but for now the function returns None.
|
||||||
|
|
||||||
|
The procedure may provide a result set as output. This is then made
|
||||||
|
available through the standard |fetch*|_ methods.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
added support for named arguments.
|
||||||
|
|
||||||
.. method:: mogrify(operation [, parameters])
|
.. method:: mogrify(operation [, parameters])
|
||||||
|
|
||||||
|
|
|
@ -241,7 +241,7 @@ How do I interrupt a long-running query in an interactive shell?
|
||||||
|
|
||||||
.. code-block:: pycon
|
.. code-block:: pycon
|
||||||
|
|
||||||
>>> psycopg2.extensions.set_wait_callback(psycopg2.extensions.wait_select)
|
>>> psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select)
|
||||||
>>> cnn = psycopg2.connect('')
|
>>> cnn = psycopg2.connect('')
|
||||||
>>> cur = cnn.cursor()
|
>>> cur = cnn.cursor()
|
||||||
>>> cur.execute("select pg_sleep(10)")
|
>>> cur.execute("select pg_sleep(10)")
|
||||||
|
|
|
@ -908,7 +908,7 @@ WHERE typname = 'hstore';
|
||||||
|
|
||||||
def register_hstore(conn_or_curs, globally=False, unicode=False,
|
def register_hstore(conn_or_curs, globally=False, unicode=False,
|
||||||
oid=None, array_oid=None):
|
oid=None, array_oid=None):
|
||||||
"""Register adapter and typecaster for `!dict`\-\ |hstore| conversions.
|
r"""Register adapter and typecaster for `!dict`\-\ |hstore| conversions.
|
||||||
|
|
||||||
:param conn_or_curs: a connection or cursor: the typecaster will be
|
:param conn_or_curs: a connection or cursor: the typecaster will be
|
||||||
registered only on this object unless *globally* is set to `!True`
|
registered only on this object unless *globally* is set to `!True`
|
||||||
|
|
|
@ -521,6 +521,25 @@ conn_setup_cancel(connectionObject *self, PGconn *pgconn)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return 1 if the "replication" keyword is set in the DSN, 0 otherwise */
|
||||||
|
static int
|
||||||
|
dsn_has_replication(char *pgdsn)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
PQconninfoOption *connopts, *ptr;
|
||||||
|
|
||||||
|
connopts = PQconninfoParse(pgdsn, NULL);
|
||||||
|
|
||||||
|
for(ptr = connopts; ptr->keyword != NULL; ptr++) {
|
||||||
|
if(strcmp(ptr->keyword, "replication") == 0 && ptr->val != NULL)
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PQconninfoFree(connopts);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Return 1 if the server datestyle allows us to work without problems,
|
/* Return 1 if the server datestyle allows us to work without problems,
|
||||||
0 if it needs to be set to something better, e.g. ISO. */
|
0 if it needs to be set to something better, e.g. ISO. */
|
||||||
|
@ -549,28 +568,29 @@ conn_setup(connectionObject *self, PGconn *pgconn)
|
||||||
{
|
{
|
||||||
PGresult *pgres = NULL;
|
PGresult *pgres = NULL;
|
||||||
char *error = NULL;
|
char *error = NULL;
|
||||||
|
int rv = -1;
|
||||||
|
|
||||||
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);
|
||||||
self->protocol = conn_get_protocol_version(self->pgconn);
|
self->protocol = conn_get_protocol_version(self->pgconn);
|
||||||
if (3 != self->protocol) {
|
if (3 != self->protocol) {
|
||||||
PyErr_SetString(InterfaceError, "only protocol 3 supported");
|
PyErr_SetString(InterfaceError, "only protocol 3 supported");
|
||||||
return -1;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 > conn_read_encoding(self, pgconn)) {
|
if (0 > conn_read_encoding(self, pgconn)) {
|
||||||
return -1;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 > conn_setup_cancel(self, pgconn)) {
|
if (0 > conn_setup_cancel(self, pgconn)) {
|
||||||
return -1;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS;
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
pthread_mutex_lock(&self->lock);
|
pthread_mutex_lock(&self->lock);
|
||||||
Py_BLOCK_THREADS;
|
Py_BLOCK_THREADS;
|
||||||
|
|
||||||
if (!conn_is_datestyle_ok(self->pgconn)) {
|
if (!dsn_has_replication(self->dsn) && !conn_is_datestyle_ok(self->pgconn)) {
|
||||||
int res;
|
int res;
|
||||||
Py_UNBLOCK_THREADS;
|
Py_UNBLOCK_THREADS;
|
||||||
res = pq_set_guc_locked(self, "datestyle", "ISO",
|
res = pq_set_guc_locked(self, "datestyle", "ISO",
|
||||||
|
@ -578,18 +598,23 @@ conn_setup(connectionObject *self, PGconn *pgconn)
|
||||||
Py_BLOCK_THREADS;
|
Py_BLOCK_THREADS;
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
pq_complete_error(self, &pgres, &error);
|
pq_complete_error(self, &pgres, &error);
|
||||||
return -1;
|
goto unlock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for reset */
|
/* for reset */
|
||||||
self->autocommit = 0;
|
self->autocommit = 0;
|
||||||
|
|
||||||
|
/* success */
|
||||||
|
rv = 0;
|
||||||
|
|
||||||
|
unlock:
|
||||||
Py_UNBLOCK_THREADS;
|
Py_UNBLOCK_THREADS;
|
||||||
pthread_mutex_unlock(&self->lock);
|
pthread_mutex_unlock(&self->lock);
|
||||||
Py_END_ALLOW_THREADS;
|
Py_END_ALLOW_THREADS;
|
||||||
|
|
||||||
return 0;
|
exit:
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* conn_connect - execute a connection to the database */
|
/* conn_connect - execute a connection to the database */
|
||||||
|
@ -886,8 +911,11 @@ _conn_poll_setup_async(connectionObject *self)
|
||||||
self->autocommit = 1;
|
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.
|
||||||
if (!conn_is_datestyle_ok(self->pgconn)) {
|
* Note that we cannot change the datestyle on a replication
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
if (!dsn_has_replication(self->dsn) && !conn_is_datestyle_ok(self->pgconn)) {
|
||||||
Dprintf("conn_poll: status -> CONN_STATUS_DATESTYLE");
|
Dprintf("conn_poll: status -> CONN_STATUS_DATESTYLE");
|
||||||
self->status = CONN_STATUS_DATESTYLE;
|
self->status = CONN_STATUS_DATESTYLE;
|
||||||
if (0 == pq_send_query(self, psyco_datestyle)) {
|
if (0 == pq_send_query(self, psyco_datestyle)) {
|
||||||
|
|
|
@ -80,6 +80,7 @@ struct cursorObject {
|
||||||
char *qattr; /* quoting attr, used when quoting strings */
|
char *qattr; /* quoting attr, used when quoting strings */
|
||||||
char *notice; /* a notice from the backend */
|
char *notice; /* a notice from the backend */
|
||||||
char *name; /* this cursor name */
|
char *name; /* this cursor name */
|
||||||
|
char *qname; /* this cursor name, quoted */
|
||||||
|
|
||||||
PyObject *string_types; /* a set of typecasters for string types */
|
PyObject *string_types; /* a set of typecasters for string types */
|
||||||
PyObject *binary_types; /* a set of typecasters for binary types */
|
PyObject *binary_types; /* a set of typecasters for binary types */
|
||||||
|
|
|
@ -55,7 +55,7 @@ psyco_curs_close(cursorObject *self)
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self->name != NULL) {
|
if (self->qname != NULL) {
|
||||||
char buffer[128];
|
char buffer[128];
|
||||||
PGTransactionStatusType status;
|
PGTransactionStatusType status;
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ psyco_curs_close(cursorObject *self)
|
||||||
|
|
||||||
if (!(status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR)) {
|
if (!(status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR)) {
|
||||||
EXC_IF_NO_MARK(self);
|
EXC_IF_NO_MARK(self);
|
||||||
PyOS_snprintf(buffer, 127, "CLOSE \"%s\"", self->name);
|
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname);
|
||||||
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
|
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -422,10 +422,10 @@ _psyco_curs_execute(cursorObject *self,
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self->name != NULL) {
|
if (self->qname != NULL) {
|
||||||
self->query = Bytes_FromFormat(
|
self->query = Bytes_FromFormat(
|
||||||
"DECLARE \"%s\" %sCURSOR %s HOLD FOR %s",
|
"DECLARE %s %sCURSOR %s HOLD FOR %s",
|
||||||
self->name,
|
self->qname,
|
||||||
scroll,
|
scroll,
|
||||||
self->withhold ? "WITH" : "WITHOUT",
|
self->withhold ? "WITH" : "WITHOUT",
|
||||||
Bytes_AS_STRING(fquery));
|
Bytes_AS_STRING(fquery));
|
||||||
|
@ -436,10 +436,10 @@ _psyco_curs_execute(cursorObject *self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (self->name != NULL) {
|
if (self->qname != NULL) {
|
||||||
self->query = Bytes_FromFormat(
|
self->query = Bytes_FromFormat(
|
||||||
"DECLARE \"%s\" %sCURSOR %s HOLD FOR %s",
|
"DECLARE %s %sCURSOR %s HOLD FOR %s",
|
||||||
self->name,
|
self->qname,
|
||||||
scroll,
|
scroll,
|
||||||
self->withhold ? "WITH" : "WITHOUT",
|
self->withhold ? "WITH" : "WITHOUT",
|
||||||
Bytes_AS_STRING(operation));
|
Bytes_AS_STRING(operation));
|
||||||
|
@ -768,13 +768,13 @@ psyco_curs_fetchone(cursorObject *self)
|
||||||
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);
|
||||||
|
|
||||||
if (self->name != NULL) {
|
if (self->qname != NULL) {
|
||||||
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_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->qname);
|
||||||
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
|
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
|
||||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||||
}
|
}
|
||||||
|
@ -823,8 +823,8 @@ psyco_curs_next_named(cursorObject *self)
|
||||||
if (self->row >= self->rowcount) {
|
if (self->row >= self->rowcount) {
|
||||||
char buffer[128];
|
char buffer[128];
|
||||||
|
|
||||||
PyOS_snprintf(buffer, 127, "FETCH FORWARD %ld FROM \"%s\"",
|
PyOS_snprintf(buffer, 127, "FETCH FORWARD %ld FROM %s",
|
||||||
self->itersize, self->name);
|
self->itersize, self->qname);
|
||||||
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
|
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
|
||||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||||
}
|
}
|
||||||
|
@ -886,14 +886,14 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords)
|
||||||
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);
|
||||||
|
|
||||||
if (self->name != NULL) {
|
if (self->qname != NULL) {
|
||||||
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_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->qname);
|
||||||
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
|
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
|
||||||
if (_psyco_curs_prefetch(self) < 0) { goto exit; }
|
if (_psyco_curs_prefetch(self) < 0) { goto exit; }
|
||||||
}
|
}
|
||||||
|
@ -962,13 +962,13 @@ psyco_curs_fetchall(cursorObject *self)
|
||||||
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);
|
||||||
|
|
||||||
if (self->name != NULL) {
|
if (self->qname != NULL) {
|
||||||
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_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->qname);
|
||||||
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
|
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
|
||||||
if (_psyco_curs_prefetch(self) < 0) { goto exit; }
|
if (_psyco_curs_prefetch(self) < 0) { goto exit; }
|
||||||
}
|
}
|
||||||
|
@ -1025,10 +1025,17 @@ psyco_curs_callproc(cursorObject *self, PyObject *args)
|
||||||
PyObject *operation = NULL;
|
PyObject *operation = NULL;
|
||||||
PyObject *res = NULL;
|
PyObject *res = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s#|O",
|
int using_dict;
|
||||||
&procname, &procname_len, ¶meters
|
PyObject *pname = NULL;
|
||||||
))
|
PyObject *pnames = NULL;
|
||||||
{ goto exit; }
|
PyObject *pvals = NULL;
|
||||||
|
char *cpname = NULL;
|
||||||
|
char **scpnames = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s#|O", &procname, &procname_len,
|
||||||
|
¶meters)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
EXC_IF_CURS_CLOSED(self);
|
EXC_IF_CURS_CLOSED(self);
|
||||||
EXC_IF_ASYNC_IN_PROGRESS(self, callproc);
|
EXC_IF_ASYNC_IN_PROGRESS(self, callproc);
|
||||||
|
@ -1044,8 +1051,65 @@ psyco_curs_callproc(cursorObject *self, PyObject *args)
|
||||||
if (-1 == (nparameters = PyObject_Length(parameters))) { goto exit; }
|
if (-1 == (nparameters = PyObject_Length(parameters))) { goto exit; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allocate some memory, build the SQL and create a PyString from it */
|
using_dict = nparameters > 0 && PyDict_Check(parameters);
|
||||||
sl = procname_len + 17 + nparameters*3 - (nparameters ? 1 : 0);
|
|
||||||
|
/* a Dict is complicated; the parameter names go into the query */
|
||||||
|
if (using_dict) {
|
||||||
|
if (!(pnames = PyDict_Keys(parameters))) { goto exit; }
|
||||||
|
if (!(pvals = PyDict_Values(parameters))) { goto exit; }
|
||||||
|
|
||||||
|
sl = procname_len + 17 + nparameters * 5 - (nparameters ? 1 : 0);
|
||||||
|
|
||||||
|
if (!(scpnames = PyMem_New(char *, nparameters))) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(scpnames, 0, sizeof(char *) * nparameters);
|
||||||
|
|
||||||
|
/* each parameter has to be processed; it's a few steps. */
|
||||||
|
for (i = 0; i < nparameters; i++) {
|
||||||
|
/* all errors are RuntimeErrors as they should never occur */
|
||||||
|
|
||||||
|
if (!(pname = PyList_GetItem(pnames, i))) { goto exit; }
|
||||||
|
Py_INCREF(pname); /* was borrowed */
|
||||||
|
|
||||||
|
/* this also makes a check for keys being strings */
|
||||||
|
if (!(pname = psycopg_ensure_bytes(pname))) { goto exit; }
|
||||||
|
if (!(cpname = Bytes_AsString(pname))) { goto exit; }
|
||||||
|
|
||||||
|
if (!(scpnames[i] = psycopg_escape_identifier(
|
||||||
|
self->conn, cpname, 0))) {
|
||||||
|
Py_CLEAR(pname);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_CLEAR(pname);
|
||||||
|
|
||||||
|
sl += strlen(scpnames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(sql = (char*)PyMem_Malloc(sl))) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(sql, "SELECT * FROM %s(", procname);
|
||||||
|
for (i = 0; i < nparameters; i++) {
|
||||||
|
strcat(sql, scpnames[i]);
|
||||||
|
strcat(sql, ":=%s,");
|
||||||
|
}
|
||||||
|
sql[sl-2] = ')';
|
||||||
|
sql[sl-1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a list (or None, or empty data structure) is a little bit simpler */
|
||||||
|
else {
|
||||||
|
Py_INCREF(parameters);
|
||||||
|
pvals = parameters;
|
||||||
|
|
||||||
|
sl = procname_len + 17 + nparameters * 3 - (nparameters ? 1 : 0);
|
||||||
|
|
||||||
sql = (char*)PyMem_Malloc(sl);
|
sql = (char*)PyMem_Malloc(sl);
|
||||||
if (sql == NULL) {
|
if (sql == NULL) {
|
||||||
PyErr_NoMemory();
|
PyErr_NoMemory();
|
||||||
|
@ -1053,22 +1117,42 @@ psyco_curs_callproc(cursorObject *self, PyObject *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(sql, "SELECT * FROM %s(", procname);
|
sprintf(sql, "SELECT * FROM %s(", procname);
|
||||||
for(i=0; i<nparameters; i++) {
|
for (i = 0; i < nparameters; i++) {
|
||||||
strcat(sql, "%s,");
|
strcat(sql, "%s,");
|
||||||
}
|
}
|
||||||
sql[sl-2] = ')';
|
sql[sl-2] = ')';
|
||||||
sql[sl-1] = '\0';
|
sql[sl-1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
if (!(operation = Bytes_FromString(sql))) { goto exit; }
|
if (!(operation = Bytes_FromString(sql))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (0 <= _psyco_curs_execute(self, operation, parameters,
|
if (0 <= _psyco_curs_execute(
|
||||||
self->conn->async, 0)) {
|
self, operation, pvals, self->conn->async, 0)) {
|
||||||
Py_INCREF(parameters);
|
/* The dict case is outside DBAPI scope anyway, so simply return None */
|
||||||
res = parameters;
|
if (using_dict) {
|
||||||
|
res = Py_None;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res = pvals;
|
||||||
|
}
|
||||||
|
Py_INCREF(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
|
if (scpnames != NULL) {
|
||||||
|
for (i = 0; i < nparameters; i++) {
|
||||||
|
if (scpnames[i] != NULL) {
|
||||||
|
PQfreemem(scpnames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PyMem_Del(scpnames);
|
||||||
|
Py_XDECREF(pname);
|
||||||
|
Py_XDECREF(pnames);
|
||||||
Py_XDECREF(operation);
|
Py_XDECREF(operation);
|
||||||
|
Py_XDECREF(pvals);
|
||||||
PyMem_Free((void*)sql);
|
PyMem_Free((void*)sql);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -1153,7 +1237,7 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
|
||||||
/* 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
|
||||||
to the MOVE SQL statement */
|
to the MOVE SQL statement */
|
||||||
if (self->name == NULL) {
|
if (self->qname == NULL) {
|
||||||
if (strcmp(mode, "relative") == 0) {
|
if (strcmp(mode, "relative") == 0) {
|
||||||
newpos = self->row + value;
|
newpos = self->row + value;
|
||||||
} else if (strcmp( mode, "absolute") == 0) {
|
} else if (strcmp( mode, "absolute") == 0) {
|
||||||
|
@ -1181,11 +1265,11 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
|
||||||
EXC_IF_TPC_PREPARED(self->conn, scroll);
|
EXC_IF_TPC_PREPARED(self->conn, scroll);
|
||||||
|
|
||||||
if (strcmp(mode, "absolute") == 0) {
|
if (strcmp(mode, "absolute") == 0) {
|
||||||
PyOS_snprintf(buffer, 127, "MOVE ABSOLUTE %d FROM \"%s\"",
|
PyOS_snprintf(buffer, 127, "MOVE ABSOLUTE %d FROM %s",
|
||||||
value, self->name);
|
value, self->qname);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyOS_snprintf(buffer, 127, "MOVE %d FROM \"%s\"", value, self->name);
|
PyOS_snprintf(buffer, 127, "MOVE %d FROM %s", value, self->qname);
|
||||||
}
|
}
|
||||||
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
|
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
|
||||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||||
|
@ -1815,7 +1899,10 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
|
||||||
Dprintf("cursor_setup: parameters: name = %s, conn = %p", name, conn);
|
Dprintf("cursor_setup: parameters: name = %s, conn = %p", name, conn);
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
if (!(self->name = psycopg_escape_identifier_easy(name, 0))) {
|
if (0 > psycopg_strdup(&self->name, name, 0)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!(self->qname = psycopg_escape_identifier(conn, name, 0))) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1891,6 +1978,7 @@ cursor_dealloc(PyObject* obj)
|
||||||
cursor_clear(self);
|
cursor_clear(self);
|
||||||
|
|
||||||
PyMem_Free(self->name);
|
PyMem_Free(self->name);
|
||||||
|
PQfreemem(self->qname);
|
||||||
|
|
||||||
CLEARPGRES(self->pgres);
|
CLEARPGRES(self->pgres);
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,8 @@ RAISES HIDDEN PyObject *psyco_set_error(PyObject *exc, cursorObject *curs, const
|
||||||
|
|
||||||
HIDDEN char *psycopg_escape_string(connectionObject *conn,
|
HIDDEN char *psycopg_escape_string(connectionObject *conn,
|
||||||
const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen);
|
const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen);
|
||||||
HIDDEN char *psycopg_escape_identifier_easy(const char *from, Py_ssize_t len);
|
HIDDEN char *psycopg_escape_identifier(connectionObject *conn,
|
||||||
|
const char *str, size_t len);
|
||||||
HIDDEN int psycopg_strdup(char **to, const char *from, Py_ssize_t len);
|
HIDDEN int psycopg_strdup(char **to, const char *from, Py_ssize_t len);
|
||||||
HIDDEN int psycopg_is_text_file(PyObject *f);
|
HIDDEN int psycopg_is_text_file(PyObject *f);
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,6 @@
|
||||||
HIDDEN PyObject *pyDateTimeModuleP = NULL;
|
HIDDEN PyObject *pyDateTimeModuleP = NULL;
|
||||||
|
|
||||||
HIDDEN PyObject *psycoEncodings = NULL;
|
HIDDEN PyObject *psycoEncodings = NULL;
|
||||||
|
|
||||||
#ifdef PSYCOPG_DEBUG
|
#ifdef PSYCOPG_DEBUG
|
||||||
HIDDEN int psycopg_debug_enabled = 0;
|
HIDDEN int psycopg_debug_enabled = 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -191,9 +190,8 @@ psyco_quote_ident(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
|
||||||
str = Bytes_AS_STRING(ident);
|
str = Bytes_AS_STRING(ident);
|
||||||
|
|
||||||
quoted = PQescapeIdentifier(conn->pgconn, str, strlen(str));
|
quoted = psycopg_escape_identifier(conn, str, strlen(str));
|
||||||
if (!quoted) {
|
if (!quoted) {
|
||||||
PyErr_NoMemory();
|
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
result = conn_text_from_chars(conn, quoted);
|
result = conn_text_from_chars(conn, quoted);
|
||||||
|
|
|
@ -90,43 +90,40 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Escape a string to build a valid PostgreSQL identifier.
|
/* Escape a string for inclusion in a query as identifier.
|
||||||
*
|
*
|
||||||
* Allocate a new buffer on the Python heap containing the new string.
|
|
||||||
* 'len' is optional: if 0 the length is calculated.
|
* 'len' is optional: if 0 the length is calculated.
|
||||||
*
|
*
|
||||||
* The returned string doesn't include quotes.
|
* Return a string allocated by Postgres: free it using PQfreemem
|
||||||
*
|
* In case of error set a Python exception.
|
||||||
* WARNING: this function is not so safe to allow untrusted input: it does no
|
|
||||||
* check for multibyte chars. Such a function should be built on
|
|
||||||
* PQescapeIdentifier, which is only available from PostgreSQL 9.0.
|
|
||||||
*/
|
*/
|
||||||
char *
|
char *
|
||||||
psycopg_escape_identifier_easy(const char *from, Py_ssize_t len)
|
psycopg_escape_identifier(connectionObject *conn, const char *str, size_t len)
|
||||||
{
|
{
|
||||||
char *rv;
|
char *rv = NULL;
|
||||||
const char *src;
|
|
||||||
char *dst;
|
|
||||||
|
|
||||||
if (!len) { len = strlen(from); }
|
if (!conn || !conn->pgconn) {
|
||||||
if (!(rv = PyMem_New(char, 1 + 2 * len))) {
|
PyErr_SetString(InterfaceError, "connection not valid");
|
||||||
PyErr_NoMemory();
|
goto exit;
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The only thing to do is double quotes */
|
if (!len) { len = strlen(str); }
|
||||||
for (src = from, dst = rv; *src; ++src, ++dst) {
|
|
||||||
*dst = *src;
|
rv = PQescapeIdentifier(conn->pgconn, str, len);
|
||||||
if ('"' == *src) {
|
if (!rv) {
|
||||||
*++dst = '"';
|
char *msg;
|
||||||
|
msg = PQerrorMessage(conn->pgconn);
|
||||||
|
if (!msg || !msg[0]) {
|
||||||
|
msg = "no message provided";
|
||||||
}
|
}
|
||||||
|
PyErr_Format(InterfaceError, "failed to escape identifier: %s", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
*dst = '\0';
|
exit:
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Duplicate a string.
|
/* Duplicate a string.
|
||||||
*
|
*
|
||||||
* Allocate a new buffer on the Python heap containing the new string.
|
* Allocate a new buffer on the Python heap containing the new string.
|
||||||
|
|
60
scripts/travis_prepare.sh
Executable file
60
scripts/travis_prepare.sh
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Prepare the test databases in Travis CI.
|
||||||
|
# The script should be run with sudo.
|
||||||
|
# The script is not idempotent: it assumes the machine in a clean state
|
||||||
|
# and is designed for a sudo-enabled Trusty environment.
|
||||||
|
|
||||||
|
set_param () {
|
||||||
|
# Set a parameter in a postgresql.conf file
|
||||||
|
version=$1
|
||||||
|
param=$2
|
||||||
|
value=$3
|
||||||
|
|
||||||
|
sed -i "s/^\s*#\?\s*$param.*/$param = $value/" \
|
||||||
|
"/etc/postgresql/$version/psycopg/postgresql.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
create () {
|
||||||
|
version=$1
|
||||||
|
port=$2
|
||||||
|
dbname=psycopg2_test
|
||||||
|
|
||||||
|
pg_createcluster -p $port --start-conf manual $version psycopg
|
||||||
|
|
||||||
|
# for two-phase commit testing
|
||||||
|
set_param "$version" max_prepared_transactions 10
|
||||||
|
|
||||||
|
# for replication testing
|
||||||
|
set_param "$version" max_wal_senders 5
|
||||||
|
set_param "$version" max_replication_slots 5
|
||||||
|
if [ "$version" == "9.2" -o "$version" == "9.3" ]
|
||||||
|
then
|
||||||
|
set_param "$version" wal_level hot_standby
|
||||||
|
else
|
||||||
|
set_param "$version" wal_level logical
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "local replication travis trust" \
|
||||||
|
>> "/etc/postgresql/$version/psycopg/pg_hba.conf"
|
||||||
|
|
||||||
|
|
||||||
|
pg_ctlcluster "$version" psycopg start
|
||||||
|
|
||||||
|
sudo -u postgres psql -c "create user travis replication" "port=$port"
|
||||||
|
sudo -u postgres psql -c "create database $dbname" "port=$port"
|
||||||
|
sudo -u postgres psql -c "grant create on database $dbname to travis" "port=$port"
|
||||||
|
sudo -u postgres psql -c "create extension hstore" "port=$port dbname=$dbname"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Would give a permission denied error in the travis build dir
|
||||||
|
cd /
|
||||||
|
|
||||||
|
create 9.6 54396
|
||||||
|
create 9.5 54395
|
||||||
|
create 9.4 54394
|
||||||
|
create 9.3 54393
|
||||||
|
create 9.2 54392
|
30
scripts/travis_test.sh
Executable file
30
scripts/travis_test.sh
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run the tests in all the databases
|
||||||
|
# The script is designed for a Trusty environment.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
run_test () {
|
||||||
|
version=$1
|
||||||
|
port=$2
|
||||||
|
dbname=psycopg2_test
|
||||||
|
|
||||||
|
printf "\n\nRunning tests against PostgreSQL $version\n\n"
|
||||||
|
export PSYCOPG2_TESTDB=$dbname
|
||||||
|
export PSYCOPG2_TESTDB_PORT=$port
|
||||||
|
export PSYCOPG2_TESTDB_USER=travis
|
||||||
|
export PSYCOPG2_TEST_REPL_DSN=
|
||||||
|
unset PSYCOPG2_TEST_GREEN
|
||||||
|
python -c "from psycopg2 import tests; tests.unittest.main(defaultTest='tests.test_suite')"
|
||||||
|
|
||||||
|
printf "\n\nRunning tests against PostgreSQL $version (green mode)\n\n"
|
||||||
|
export PSYCOPG2_TEST_GREEN=1
|
||||||
|
python -c "from psycopg2 import tests; tests.unittest.main(defaultTest='tests.test_suite')"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test 9.6 54396
|
||||||
|
run_test 9.5 54395
|
||||||
|
run_test 9.4 54394
|
||||||
|
run_test 9.3 54393
|
||||||
|
run_test 9.2 54392
|
15
setup.cfg
15
setup.cfg
|
@ -7,24 +7,23 @@ define=
|
||||||
|
|
||||||
# "pg_config" is required to locate PostgreSQL headers and libraries needed to
|
# "pg_config" is required to locate PostgreSQL headers and libraries needed to
|
||||||
# build psycopg2. If pg_config is not in the path or is installed under a
|
# build psycopg2. If pg_config is not in the path or is installed under a
|
||||||
# different name uncomment the following option and set it to the pg_config
|
# different name set the following option to the pg_config full path.
|
||||||
# full path.
|
pg_config=
|
||||||
#pg_config=
|
|
||||||
|
|
||||||
# Set to 1 to use Python datetime objects for default date/time representation.
|
# Set to 1 to use Python datetime objects for default date/time representation.
|
||||||
use_pydatetime=1
|
use_pydatetime=1
|
||||||
|
|
||||||
# If the build system does not find the mx.DateTime headers, try
|
# If the build system does not find the mx.DateTime headers, try
|
||||||
# uncommenting the following line and setting its value to the right path.
|
# setting its value to the right path.
|
||||||
#mx_include_dir=
|
mx_include_dir=
|
||||||
|
|
||||||
# For Windows only:
|
# For Windows only:
|
||||||
# Set to 1 if the PostgreSQL library was built with OpenSSL.
|
# Set to 1 if the PostgreSQL library was built with OpenSSL.
|
||||||
# Required to link in OpenSSL libraries and dependencies.
|
# Required to link in OpenSSL libraries and dependencies.
|
||||||
have_ssl=0
|
have_ssl=0
|
||||||
|
|
||||||
# Statically link against the postgresql client library.
|
# Set to 1 to statically link against the postgresql client library.
|
||||||
#static_libpq=1
|
static_libpq=0
|
||||||
|
|
||||||
# Add here eventual extra libraries required to link the module.
|
# Add here eventual extra libraries required to link the module.
|
||||||
#libraries=
|
libraries=
|
||||||
|
|
7
setup.py
7
setup.py
|
@ -381,6 +381,11 @@ class psycopg_build_ext(build_ext):
|
||||||
|
|
||||||
def finalize_options(self):
|
def finalize_options(self):
|
||||||
"""Complete the build system configuration."""
|
"""Complete the build system configuration."""
|
||||||
|
# An empty option in the setup.cfg causes self.libraries to include
|
||||||
|
# an empty string in the list of libraries
|
||||||
|
if self.libraries is not None and not self.libraries.strip():
|
||||||
|
self.libraries = None
|
||||||
|
|
||||||
build_ext.finalize_options(self)
|
build_ext.finalize_options(self)
|
||||||
|
|
||||||
pg_config_helper = PostgresConfig(self)
|
pg_config_helper = PostgresConfig(self)
|
||||||
|
@ -521,7 +526,7 @@ if parser.has_option('build_ext', 'mx_include_dir'):
|
||||||
mxincludedir = parser.get('build_ext', 'mx_include_dir')
|
mxincludedir = parser.get('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 mxincludedir.strip() and os.path.exists(mxincludedir):
|
||||||
# Build the support for mx: we will check at runtime if it can be imported
|
# 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'))
|
||||||
|
|
|
@ -465,7 +465,7 @@ class MakeDsnTestCase(ConnectingTestCase):
|
||||||
conn = self.connect()
|
conn = self.connect()
|
||||||
d = conn.get_dsn_parameters()
|
d = conn.get_dsn_parameters()
|
||||||
self.assertEqual(d['dbname'], dbname) # the only param we can check reliably
|
self.assertEqual(d['dbname'], dbname) # the only param we can check reliably
|
||||||
self.assertNotIn('password', d)
|
self.assert_('password' not in d, d)
|
||||||
|
|
||||||
|
|
||||||
class IsolationLevelsTestCase(ConnectingTestCase):
|
class IsolationLevelsTestCase(ConnectingTestCase):
|
||||||
|
|
|
@ -498,6 +498,48 @@ class CursorTests(ConnectingTestCase):
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
|
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
|
||||||
|
|
||||||
|
# It would be inappropriate to test callproc's named parameters in the
|
||||||
|
# DBAPI2.0 test section because they are a psycopg2 extension.
|
||||||
|
@skip_before_postgres(9, 0)
|
||||||
|
def test_callproc_dict(self):
|
||||||
|
# This parameter name tests for injection and quote escaping
|
||||||
|
paramname = '''
|
||||||
|
Robert'); drop table "students" --
|
||||||
|
'''.strip()
|
||||||
|
escaped_paramname = '"%s"' % paramname.replace('"', '""')
|
||||||
|
procname = 'pg_temp.randall'
|
||||||
|
|
||||||
|
cur = self.conn.cursor()
|
||||||
|
|
||||||
|
# Set up the temporary function
|
||||||
|
cur.execute('''
|
||||||
|
CREATE FUNCTION %s(%s INT)
|
||||||
|
RETURNS INT AS
|
||||||
|
'SELECT $1 * $1'
|
||||||
|
LANGUAGE SQL
|
||||||
|
''' % (procname, escaped_paramname));
|
||||||
|
|
||||||
|
# Make sure callproc works right
|
||||||
|
cur.callproc(procname, { paramname: 2 })
|
||||||
|
self.assertEquals(cur.fetchone()[0], 4)
|
||||||
|
|
||||||
|
# Make sure callproc fails right
|
||||||
|
failing_cases = [
|
||||||
|
({ paramname: 2, 'foo': 'bar' }, psycopg2.ProgrammingError),
|
||||||
|
({ paramname: '2' }, psycopg2.ProgrammingError),
|
||||||
|
({ paramname: 'two' }, psycopg2.ProgrammingError),
|
||||||
|
({ u'bj\xc3rn': 2 }, psycopg2.ProgrammingError),
|
||||||
|
({ 3: 2 }, TypeError),
|
||||||
|
({ self: 2 }, TypeError),
|
||||||
|
]
|
||||||
|
for parameter_sequence, exception in failing_cases:
|
||||||
|
self.assertRaises(exception, cur.callproc, procname, parameter_sequence)
|
||||||
|
self.conn.rollback()
|
||||||
|
|
||||||
|
def test_callproc_badparam(self):
|
||||||
|
cur = self.conn.cursor()
|
||||||
|
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
|
|
@ -119,8 +119,8 @@ class ConnectTestCase(unittest.TestCase):
|
||||||
def test_int_port_param(self):
|
def test_int_port_param(self):
|
||||||
psycopg2.connect(database='sony', port=6543)
|
psycopg2.connect(database='sony', port=6543)
|
||||||
dsn = " %s " % self.args[0]
|
dsn = " %s " % self.args[0]
|
||||||
self.assertIn(" dbname=sony ", dsn)
|
self.assert_(" dbname=sony " in dsn, dsn)
|
||||||
self.assertIn(" port=6543 ", dsn)
|
self.assert_(" port=6543 " in dsn, dsn)
|
||||||
|
|
||||||
def test_empty_param(self):
|
def test_empty_param(self):
|
||||||
psycopg2.connect(database='sony', password='')
|
psycopg2.connect(database='sony', password='')
|
||||||
|
|
|
@ -38,6 +38,30 @@ class Psycopg2Tests(dbapi20.DatabaseAPI20Test):
|
||||||
|
|
||||||
lower_func = 'lower' # For stored procedure test
|
lower_func = 'lower' # For stored procedure test
|
||||||
|
|
||||||
|
def test_callproc(self):
|
||||||
|
# Until DBAPI 2.0 compliance, callproc should return None or it's just
|
||||||
|
# misleading. Therefore, we will skip the return value test for
|
||||||
|
# callproc and only perform the fetch test.
|
||||||
|
#
|
||||||
|
# For what it's worth, the DBAPI2.0 test_callproc doesn't actually
|
||||||
|
# test for DBAPI2.0 compliance! It doesn't check for modified OUT and
|
||||||
|
# IN/OUT parameters in the return values!
|
||||||
|
con = self._connect()
|
||||||
|
try:
|
||||||
|
cur = con.cursor()
|
||||||
|
if self.lower_func and hasattr(cur,'callproc'):
|
||||||
|
cur.callproc(self.lower_func,('FOO',))
|
||||||
|
r = cur.fetchall()
|
||||||
|
self.assertEqual(len(r),1,'callproc produced no result set')
|
||||||
|
self.assertEqual(len(r[0]),1,
|
||||||
|
'callproc produced invalid result set'
|
||||||
|
)
|
||||||
|
self.assertEqual(r[0][0],'foo',
|
||||||
|
'callproc produced invalid results'
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
con.close()
|
||||||
|
|
||||||
def test_setoutputsize(self):
|
def test_setoutputsize(self):
|
||||||
# psycopg2's setoutputsize() is a no-op
|
# psycopg2's setoutputsize() is a no-op
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -65,11 +65,13 @@ class QuotingTestCase(ConnectingTestCase):
|
||||||
curs = self.conn.cursor()
|
curs = self.conn.cursor()
|
||||||
data = 'abcd\x01\x00cdefg'
|
data = 'abcd\x01\x00cdefg'
|
||||||
|
|
||||||
with self.assertRaises(ValueError) as e:
|
try:
|
||||||
curs.execute("SELECT %s", (data,))
|
curs.execute("SELECT %s", (data,))
|
||||||
|
except ValueError as e:
|
||||||
self.assertEquals(str(e.exception),
|
self.assertEquals(str(e),
|
||||||
'A string literal cannot contain NUL (0x00) characters.')
|
'A string literal cannot contain NUL (0x00) characters.')
|
||||||
|
else:
|
||||||
|
self.fail("ValueError not raised")
|
||||||
|
|
||||||
def test_binary(self):
|
def test_binary(self):
|
||||||
data = b"""some data with \000\013 binary
|
data = b"""some data with \000\013 binary
|
||||||
|
|
32
tests/test_replication.py
Normal file → Executable file
32
tests/test_replication.py
Normal file → Executable file
|
@ -23,23 +23,19 @@
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extensions
|
|
||||||
from psycopg2.extras import (
|
from psycopg2.extras import (
|
||||||
PhysicalReplicationConnection, LogicalReplicationConnection, StopReplication)
|
PhysicalReplicationConnection, LogicalReplicationConnection, StopReplication)
|
||||||
|
|
||||||
import testconfig
|
import testconfig
|
||||||
from testutils import unittest
|
from testutils import unittest, ConnectingTestCase
|
||||||
from testutils import skip_before_postgres
|
from testutils import skip_before_postgres, skip_if_green
|
||||||
from testutils import ConnectingTestCase
|
|
||||||
|
skip_repl_if_green = skip_if_green("replication not supported in green mode")
|
||||||
|
|
||||||
|
|
||||||
class ReplicationTestCase(ConnectingTestCase):
|
class ReplicationTestCase(ConnectingTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if not testconfig.repl_dsn:
|
|
||||||
self.skipTest("replication tests disabled by default")
|
|
||||||
|
|
||||||
super(ReplicationTestCase, self).setUp()
|
super(ReplicationTestCase, self).setUp()
|
||||||
|
|
||||||
self.slot = testconfig.repl_slot
|
self.slot = testconfig.repl_slot
|
||||||
self._slots = []
|
self._slots = []
|
||||||
|
|
||||||
|
@ -93,6 +89,20 @@ class ReplicationTest(ReplicationTestCase):
|
||||||
cur.execute("IDENTIFY_SYSTEM")
|
cur.execute("IDENTIFY_SYSTEM")
|
||||||
cur.fetchall()
|
cur.fetchall()
|
||||||
|
|
||||||
|
@skip_before_postgres(9, 0)
|
||||||
|
def test_datestyle(self):
|
||||||
|
if testconfig.repl_dsn is None:
|
||||||
|
return self.skipTest("replication tests disabled by default")
|
||||||
|
|
||||||
|
conn = self.repl_connect(
|
||||||
|
dsn=testconfig.repl_dsn, options='-cdatestyle=german',
|
||||||
|
connection_factory=PhysicalReplicationConnection)
|
||||||
|
if conn is None:
|
||||||
|
return
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("IDENTIFY_SYSTEM")
|
||||||
|
cur.fetchall()
|
||||||
|
|
||||||
@skip_before_postgres(9, 4)
|
@skip_before_postgres(9, 4)
|
||||||
def test_logical_replication_connection(self):
|
def test_logical_replication_connection(self):
|
||||||
conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
|
conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
|
||||||
|
@ -114,6 +124,7 @@ class ReplicationTest(ReplicationTestCase):
|
||||||
psycopg2.ProgrammingError, self.create_replication_slot, cur)
|
psycopg2.ProgrammingError, self.create_replication_slot, cur)
|
||||||
|
|
||||||
@skip_before_postgres(9, 4) # slots require 9.4
|
@skip_before_postgres(9, 4) # slots require 9.4
|
||||||
|
@skip_repl_if_green
|
||||||
def test_start_on_missing_replication_slot(self):
|
def test_start_on_missing_replication_slot(self):
|
||||||
conn = self.repl_connect(connection_factory=PhysicalReplicationConnection)
|
conn = self.repl_connect(connection_factory=PhysicalReplicationConnection)
|
||||||
if conn is None:
|
if conn is None:
|
||||||
|
@ -127,6 +138,7 @@ class ReplicationTest(ReplicationTestCase):
|
||||||
cur.start_replication(self.slot)
|
cur.start_replication(self.slot)
|
||||||
|
|
||||||
@skip_before_postgres(9, 4) # slots require 9.4
|
@skip_before_postgres(9, 4) # slots require 9.4
|
||||||
|
@skip_repl_if_green
|
||||||
def test_start_and_recover_from_error(self):
|
def test_start_and_recover_from_error(self):
|
||||||
conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
|
conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
|
||||||
if conn is None:
|
if conn is None:
|
||||||
|
@ -148,6 +160,7 @@ class ReplicationTest(ReplicationTestCase):
|
||||||
cur.start_replication(slot_name=self.slot)
|
cur.start_replication(slot_name=self.slot)
|
||||||
|
|
||||||
@skip_before_postgres(9, 4) # slots require 9.4
|
@skip_before_postgres(9, 4) # slots require 9.4
|
||||||
|
@skip_repl_if_green
|
||||||
def test_stop_replication(self):
|
def test_stop_replication(self):
|
||||||
conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
|
conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
|
||||||
if conn is None:
|
if conn is None:
|
||||||
|
@ -167,12 +180,13 @@ class ReplicationTest(ReplicationTestCase):
|
||||||
|
|
||||||
class AsyncReplicationTest(ReplicationTestCase):
|
class AsyncReplicationTest(ReplicationTestCase):
|
||||||
@skip_before_postgres(9, 4) # slots require 9.4
|
@skip_before_postgres(9, 4) # slots require 9.4
|
||||||
|
@skip_repl_if_green
|
||||||
def test_async_replication(self):
|
def test_async_replication(self):
|
||||||
conn = self.repl_connect(
|
conn = self.repl_connect(
|
||||||
connection_factory=LogicalReplicationConnection, async=1)
|
connection_factory=LogicalReplicationConnection, async=1)
|
||||||
if conn is None:
|
if conn is None:
|
||||||
return
|
return
|
||||||
self.wait(conn)
|
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
self.create_replication_slot(cur, output_plugin='test_decoding')
|
self.create_replication_slot(cur, output_plugin='test_decoding')
|
||||||
|
|
|
@ -122,13 +122,25 @@ class ConnectingTestCase(unittest.TestCase):
|
||||||
Should raise a skip test if not available, but guard for None on
|
Should raise a skip test if not available, but guard for None on
|
||||||
old Python versions.
|
old Python versions.
|
||||||
"""
|
"""
|
||||||
|
if repl_dsn is None:
|
||||||
|
return self.skipTest("replication tests disabled by default")
|
||||||
|
|
||||||
if 'dsn' not in kwargs:
|
if 'dsn' not in kwargs:
|
||||||
kwargs['dsn'] = repl_dsn
|
kwargs['dsn'] = repl_dsn
|
||||||
import psycopg2
|
import psycopg2
|
||||||
try:
|
try:
|
||||||
conn = self.connect(**kwargs)
|
conn = self.connect(**kwargs)
|
||||||
|
if conn.async == 1:
|
||||||
|
self.wait(conn)
|
||||||
except psycopg2.OperationalError, e:
|
except psycopg2.OperationalError, e:
|
||||||
|
# If pgcode is not set it is a genuine connection error
|
||||||
|
# Otherwise we tried to run some bad operation in the connection
|
||||||
|
# (e.g. bug #482) and we'd rather know that.
|
||||||
|
if e.pgcode is None:
|
||||||
return self.skipTest("replication db not configured: %s" % e)
|
return self.skipTest("replication db not configured: %s" % e)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def _get_conn(self):
|
def _get_conn(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user