mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-25 18:33:44 +03:00
Merge branch 'master' into feature/replication-protocol
This commit is contained in:
commit
8e518d4954
12
NEWS
12
NEWS
|
@ -14,14 +14,19 @@ New features:
|
||||||
- The attributes `~connection.notices` and `~connection.notifies` can be
|
- The attributes `~connection.notices` and `~connection.notifies` can be
|
||||||
customized replacing them with any object exposing an `!append()` method
|
customized replacing them with any object exposing an `!append()` method
|
||||||
(:ticket:`#326`).
|
(:ticket:`#326`).
|
||||||
|
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.6.2
|
What's new in psycopg 2.6.2
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
- Report the server response status on errors (such as :ticket:`#281`).
|
- Report the server response status on errors (such as :ticket:`#281`).
|
||||||
- Raise NotSupportedError on unhandled server response status
|
- The `~psycopg2.extras.wait_select` callback allows interrupting a
|
||||||
|
long-running query in an interactive shell using :kbd:`Ctrl-C`
|
||||||
|
(:ticket:`#333`).
|
||||||
|
- Raise `!NotSupportedError` on unhandled server response status
|
||||||
(:ticket:`#352`).
|
(:ticket:`#352`).
|
||||||
|
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.6.1
|
What's new in psycopg 2.6.1
|
||||||
|
@ -30,7 +35,8 @@ What's new in psycopg 2.6.1
|
||||||
- Lists consisting of only `None` are escaped correctly (:ticket:`#285`).
|
- 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`).
|
||||||
|
- Added support for MSVC 2015 compiler (:ticket:`#350`).
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.6
|
What's new in psycopg 2.6
|
||||||
|
@ -45,7 +51,7 @@ New features:
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
- Json apapter's `!str()` returns the adapted content instead of the `!repr()`
|
- Json adapter's `!str()` returns the adapted content instead of the `!repr()`
|
||||||
(:ticket:`#191`).
|
(:ticket:`#191`).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,19 @@ functionalities defined by the |DBAPI|_.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQLIBVERSION
|
.. __: http://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQLIBVERSION
|
||||||
|
|
||||||
|
.. function:: quote_ident(str, scope)
|
||||||
|
|
||||||
|
Return quoted identifier according to PostgreSQL quoting rules.
|
||||||
|
|
||||||
|
The *scope* must be a `connection` or a `cursor`, the underlying
|
||||||
|
connection encoding is used for any necessary character conversion.
|
||||||
|
|
||||||
|
Requires libpq >= 9.0.
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQescapeIdentifier()`__
|
||||||
|
|
||||||
|
.. __: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQESCAPEIDENTIFIER
|
||||||
|
|
||||||
.. _sql-adaptation-objects:
|
.. _sql-adaptation-objects:
|
||||||
|
|
||||||
SQL adaptation protocol objects
|
SQL adaptation protocol objects
|
||||||
|
|
|
@ -979,3 +979,6 @@ Coroutine support
|
||||||
|
|
||||||
.. autofunction:: wait_select(conn)
|
.. autofunction:: wait_select(conn)
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6.2
|
||||||
|
allow to cancel a query using :kbd:`Ctrl-C`, see
|
||||||
|
:ref:`the FAQ <faq-interrupt-query>` for an example.
|
||||||
|
|
|
@ -223,6 +223,37 @@ What are the advantages or disadvantages of using named cursors?
|
||||||
little memory on the client and to skip or discard parts of the result set.
|
little memory on the client and to skip or discard parts of the result set.
|
||||||
|
|
||||||
|
|
||||||
|
.. _faq-interrupt-query:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
How do I interrupt a long-running query in an interactive shell?
|
||||||
|
Normally the interactive shell becomes unresponsive to :kbd:`Ctrl-C` when
|
||||||
|
running a query. Using a connection in green mode allows Python to
|
||||||
|
receive and handle the interrupt, although it may leave the connection
|
||||||
|
broken, if the async callback doesn't handle the `!KeyboardInterrupt`
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
Starting from psycopg 2.6.2, the `~psycopg2.extras.wait_select` callback
|
||||||
|
can handle a :kbd:`Ctrl-C` correctly. For previous versions, you can use
|
||||||
|
`this implementation`__.
|
||||||
|
|
||||||
|
.. __: http://initd.org/psycopg/articles/2014/07/20/cancelling-postgresql-statements-python/
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> psycopg2.extensions.set_wait_callback(psycopg2.extensions.wait_select)
|
||||||
|
>>> cnn = psycopg2.connect('')
|
||||||
|
>>> cur = cnn.cursor()
|
||||||
|
>>> cur.execute("select pg_sleep(10)")
|
||||||
|
^C
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1, in <module>
|
||||||
|
QueryCanceledError: canceling statement due to user request
|
||||||
|
|
||||||
|
>>> cnn.rollback()
|
||||||
|
>>> # You can use the connection and cursor again from here
|
||||||
|
|
||||||
|
|
||||||
.. _faq-compile:
|
.. _faq-compile:
|
||||||
|
|
||||||
Problems compiling and deploying psycopg2
|
Problems compiling and deploying psycopg2
|
||||||
|
|
|
@ -56,7 +56,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid, libpq_version, parse_dsn
|
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid, libpq_version, parse_dsn, quote_ident
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -735,6 +735,7 @@ def wait_select(conn):
|
||||||
from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE
|
from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
|
try:
|
||||||
state = conn.poll()
|
state = conn.poll()
|
||||||
if state == POLL_OK:
|
if state == POLL_OK:
|
||||||
break
|
break
|
||||||
|
@ -744,6 +745,10 @@ def wait_select(conn):
|
||||||
select.select([], [conn.fileno()], [])
|
select.select([], [conn.fileno()], [])
|
||||||
else:
|
else:
|
||||||
raise conn.OperationalError("bad state from poll: %s" % state)
|
raise conn.OperationalError("bad state from poll: %s" % state)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
conn.cancel()
|
||||||
|
# the loop will be broken by a server error
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
def _solve_conn_curs(conn_or_curs):
|
def _solve_conn_curs(conn_or_curs):
|
||||||
|
|
|
@ -204,8 +204,8 @@ class PersistentConnectionPool(AbstractConnectionPool):
|
||||||
|
|
||||||
# we we'll need the thread module, to determine thread ids, so we
|
# we we'll need the thread module, to determine thread ids, so we
|
||||||
# import it here and copy it in an instance variable
|
# import it here and copy it in an instance variable
|
||||||
import thread
|
import thread as _thread # work around for 2to3 bug - see ticket #348
|
||||||
self.__thread = thread
|
self.__thread = _thread
|
||||||
|
|
||||||
def getconn(self):
|
def getconn(self):
|
||||||
"""Generate thread id and return a connection."""
|
"""Generate thread id and return a connection."""
|
||||||
|
|
|
@ -166,6 +166,62 @@ exit:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define psyco_quote_ident_doc \
|
||||||
|
"quote_ident(str, conn_or_curs) -> str -- wrapper around PQescapeIdentifier\n\n" \
|
||||||
|
":Parameters:\n" \
|
||||||
|
" * `str`: A bytes or unicode object\n" \
|
||||||
|
" * `conn_or_curs`: A connection or cursor, required"
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
psyco_quote_ident(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
{
|
||||||
|
#if PG_VERSION_NUM >= 90000
|
||||||
|
PyObject *ident = NULL, *obj = NULL, *result = NULL;
|
||||||
|
connectionObject *conn;
|
||||||
|
const char *str;
|
||||||
|
char *quoted = NULL;
|
||||||
|
|
||||||
|
static char *kwlist[] = {"ident", "scope", NULL};
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &ident, &obj)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyObject_TypeCheck(obj, &cursorType)) {
|
||||||
|
conn = ((cursorObject*)obj)->conn;
|
||||||
|
}
|
||||||
|
else if (PyObject_TypeCheck(obj, &connectionType)) {
|
||||||
|
conn = (connectionObject*)obj;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"argument 2 must be a connection or a cursor");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(ident); /* for ensure_bytes */
|
||||||
|
if (!(ident = psycopg_ensure_bytes(ident))) { goto exit; }
|
||||||
|
|
||||||
|
str = Bytes_AS_STRING(ident);
|
||||||
|
|
||||||
|
quoted = PQescapeIdentifier(conn->pgconn, str, strlen(str));
|
||||||
|
if (!quoted) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
result = conn_text_from_chars(conn, quoted);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
PQfreemem(quoted);
|
||||||
|
Py_XDECREF(ident);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
PyErr_SetString(NotSupportedError, "PQescapeIdentifier not available in libpq < 9.0");
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/** type registration **/
|
/** type registration **/
|
||||||
#define psyco_register_type_doc \
|
#define psyco_register_type_doc \
|
||||||
"register_type(obj, conn_or_curs) -> None -- register obj with psycopg type system\n\n" \
|
"register_type(obj, conn_or_curs) -> None -- register obj with psycopg type system\n\n" \
|
||||||
|
@ -235,13 +291,16 @@ psyco_register_type(PyObject *self, PyObject *args)
|
||||||
static void
|
static void
|
||||||
psyco_libcrypto_threads_init(void)
|
psyco_libcrypto_threads_init(void)
|
||||||
{
|
{
|
||||||
|
PyObject *m;
|
||||||
|
|
||||||
/* importing the ssl module sets up Python's libcrypto callbacks */
|
/* importing the ssl module sets up Python's libcrypto callbacks */
|
||||||
if (PyImport_ImportModule("ssl") != NULL) {
|
if ((m = PyImport_ImportModule("ssl"))) {
|
||||||
/* 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_NUM >= 80400
|
#if PG_VERSION_NUM >= 80400
|
||||||
PQinitOpenSSL(1, 0);
|
PQinitOpenSSL(1, 0);
|
||||||
#endif
|
#endif
|
||||||
|
Py_DECREF(m);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* might mean that Python has been compiled without OpenSSL support,
|
/* might mean that Python has been compiled without OpenSSL support,
|
||||||
|
@ -764,6 +823,8 @@ static PyMethodDef psycopgMethods[] = {
|
||||||
METH_VARARGS|METH_KEYWORDS, psyco_connect_doc},
|
METH_VARARGS|METH_KEYWORDS, psyco_connect_doc},
|
||||||
{"parse_dsn", (PyCFunction)psyco_parse_dsn,
|
{"parse_dsn", (PyCFunction)psyco_parse_dsn,
|
||||||
METH_VARARGS|METH_KEYWORDS, psyco_parse_dsn_doc},
|
METH_VARARGS|METH_KEYWORDS, psyco_parse_dsn_doc},
|
||||||
|
{"quote_ident", (PyCFunction)psyco_quote_ident,
|
||||||
|
METH_VARARGS|METH_KEYWORDS, psyco_quote_ident_doc},
|
||||||
{"adapt", (PyCFunction)psyco_microprotocols_adapt,
|
{"adapt", (PyCFunction)psyco_microprotocols_adapt,
|
||||||
METH_VARARGS, psyco_microprotocols_adapt_doc},
|
METH_VARARGS, psyco_microprotocols_adapt_doc},
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ 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 to build a valid PostgreSQL identifier.
|
||||||
*
|
*
|
||||||
* Allocate a new buffer on the Python heap containing the new string.
|
* 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.
|
||||||
|
@ -96,7 +96,7 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
|
||||||
*
|
*
|
||||||
* WARNING: this function is not so safe to allow untrusted input: it does no
|
* 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
|
* check for multibyte chars. Such a function should be built on
|
||||||
* PQescapeIndentifier, which is only available from PostgreSQL 9.0.
|
* 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_easy(const char *from, Py_ssize_t len)
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from testutils import unittest, ConnectingTestCase
|
from testutils import unittest, ConnectingTestCase, skip_before_libpq
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extensions
|
import psycopg2.extensions
|
||||||
|
@ -165,6 +165,24 @@ class TestQuotedString(ConnectingTestCase):
|
||||||
self.assertEqual(q.encoding, 'utf_8')
|
self.assertEqual(q.encoding, 'utf_8')
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuotedIdentifier(ConnectingTestCase):
|
||||||
|
@skip_before_libpq(9, 0)
|
||||||
|
def test_identifier(self):
|
||||||
|
from psycopg2.extensions import quote_ident
|
||||||
|
self.assertEqual(quote_ident('blah-blah', self.conn), '"blah-blah"')
|
||||||
|
self.assertEqual(quote_ident('quote"inside', self.conn), '"quote""inside"')
|
||||||
|
|
||||||
|
@skip_before_libpq(9, 0)
|
||||||
|
def test_unicode_ident(self):
|
||||||
|
from psycopg2.extensions import quote_ident
|
||||||
|
snowman = u"\u2603"
|
||||||
|
quoted = '"' + snowman + '"'
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
self.assertEqual(quote_ident(snowman, self.conn), quoted.encode('utf8'))
|
||||||
|
else:
|
||||||
|
self.assertEqual(quote_ident(snowman, self.conn), quoted)
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user