mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-10 19:16:34 +03:00
The cursor name can be a non-valid PostgreSQL identifier
This commit is contained in:
parent
66555c5f11
commit
1db9c9b8ce
1
NEWS
1
NEWS
|
@ -11,6 +11,7 @@ What's new in psycopg 2.4
|
|||
into Python tuples/namedtuples.
|
||||
- More efficient iteration on named cursors, fetching 'itersize' records at
|
||||
time from the backend.
|
||||
- The named cursors name can be an invalid identifier.
|
||||
- 'cursor.description' is provided in named tuples if available.
|
||||
- Connections and cursors are weakly referenceable.
|
||||
- Added 'b' and 't' mode to large objects: write can deal with both bytes
|
||||
|
|
|
@ -25,11 +25,24 @@ The ``connection`` class
|
|||
|
||||
Return a new `cursor` object using the connection.
|
||||
|
||||
If `name` is specified, the returned cursor will be a *server
|
||||
side* (or *named*) cursor. Otherwise the cursor will be *client side*.
|
||||
See :ref:`server-side-cursors` for further details.
|
||||
If *name* is specified, the returned cursor will be a :ref:`server
|
||||
side cursor <server-side-cursors>` (also known as *named cursor*).
|
||||
Otherwise it will be a regular *client side* cursor.
|
||||
|
||||
The `cursor_factory` argument can be used to create non-standard
|
||||
The name can be a string not valid as a PostgreSQL identifier: for
|
||||
example it may start with a digit and contain non-alphanumeric
|
||||
characters and quotes.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
previously only valid PostgreSQL identifiers were accepted as
|
||||
cursor name.
|
||||
|
||||
.. warning::
|
||||
It is unsafe to expose the *name* to an untrusted source, for
|
||||
instance you shouldn't allow *name* to be read from a HTML form.
|
||||
Consider it as part of the query, not as a query parameter.
|
||||
|
||||
The *cursor_factory* argument can be used to create non-standard
|
||||
cursors. The class returned should be a subclass of
|
||||
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
|
||||
details.
|
||||
|
|
|
@ -59,7 +59,7 @@ psyco_curs_close(cursorObject *self, PyObject *args)
|
|||
char buffer[128];
|
||||
|
||||
EXC_IF_NO_MARK(self);
|
||||
PyOS_snprintf(buffer, 127, "CLOSE %s", self->name);
|
||||
PyOS_snprintf(buffer, 127, "CLOSE \"%s\"", self->name);
|
||||
if (pq_execute(self, buffer, 0) == -1) return NULL;
|
||||
}
|
||||
|
||||
|
@ -391,7 +391,7 @@ _psyco_curs_execute(cursorObject *self,
|
|||
|
||||
if (self->name != NULL) {
|
||||
self->query = Bytes_FromFormat(
|
||||
"DECLARE %s CURSOR WITHOUT HOLD FOR %s",
|
||||
"DECLARE \"%s\" CURSOR WITHOUT HOLD FOR %s",
|
||||
self->name, Bytes_AS_STRING(fquery));
|
||||
Py_DECREF(fquery);
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ _psyco_curs_execute(cursorObject *self,
|
|||
else {
|
||||
if (self->name != NULL) {
|
||||
self->query = Bytes_FromFormat(
|
||||
"DECLARE %s CURSOR WITHOUT HOLD FOR %s",
|
||||
"DECLARE \"%s\" CURSOR WITHOUT HOLD FOR %s",
|
||||
self->name, Bytes_AS_STRING(operation));
|
||||
}
|
||||
else {
|
||||
|
@ -748,7 +748,7 @@ psyco_curs_fetchone(cursorObject *self, PyObject *args)
|
|||
|
||||
EXC_IF_NO_MARK(self);
|
||||
EXC_IF_TPC_PREPARED(self->conn, fetchone);
|
||||
PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM %s", self->name);
|
||||
PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM \"%s\"", self->name);
|
||||
if (pq_execute(self, buffer, 0) == -1) return NULL;
|
||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||
}
|
||||
|
@ -802,7 +802,7 @@ psyco_curs_next_named(cursorObject *self)
|
|||
if (self->row >= self->rowcount) {
|
||||
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);
|
||||
if (pq_execute(self, buffer, 0) == -1) return NULL;
|
||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||
|
@ -862,7 +862,7 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords)
|
|||
|
||||
EXC_IF_NO_MARK(self);
|
||||
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);
|
||||
if (pq_execute(self, buffer, 0) == -1) return NULL;
|
||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||
|
@ -933,7 +933,7 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args)
|
|||
|
||||
EXC_IF_NO_MARK(self);
|
||||
EXC_IF_TPC_PREPARED(self->conn, fetchall);
|
||||
PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM %s", self->name);
|
||||
PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name);
|
||||
if (pq_execute(self, buffer, 0) == -1) return NULL;
|
||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||
}
|
||||
|
@ -1144,11 +1144,11 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
|
|||
EXC_IF_TPC_PREPARED(self->conn, scroll);
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
PyOS_snprintf(buffer, 127, "MOVE %d FROM %s", value, self->name);
|
||||
PyOS_snprintf(buffer, 127, "MOVE %d FROM \"%s\"", value, self->name);
|
||||
}
|
||||
if (pq_execute(self, buffer, 0) == -1) return NULL;
|
||||
if (_psyco_curs_prefetch(self) < 0) return NULL;
|
||||
|
@ -1666,11 +1666,9 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
|
|||
Dprintf("cursor_setup: parameters: name = %s, conn = %p", name, conn);
|
||||
|
||||
if (name) {
|
||||
if (!(self->name = PyMem_Malloc(strlen(name)+1))) {
|
||||
PyErr_NoMemory();
|
||||
if (!(self->name = psycopg_escape_identifier_easy(name, 0))) {
|
||||
return 1;
|
||||
}
|
||||
strncpy(self->name, name, strlen(name)+1);
|
||||
}
|
||||
|
||||
/* FIXME: why does this raise an excpetion on the _next_ line of code?
|
||||
|
|
|
@ -125,7 +125,7 @@ HIDDEN void psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg,
|
|||
|
||||
HIDDEN char *psycopg_escape_string(PyObject *conn,
|
||||
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_strdup(const char *from, Py_ssize_t len);
|
||||
HIDDEN PyObject * psycopg_ensure_bytes(PyObject *obj);
|
||||
HIDDEN PyObject * psycopg_ensure_text(PyObject *obj);
|
||||
|
|
|
@ -71,6 +71,43 @@ psycopg_escape_string(PyObject *obj, const char *from, Py_ssize_t len,
|
|||
return to;
|
||||
}
|
||||
|
||||
/* Escape a string to build a valid PostgreSQL identifier
|
||||
*
|
||||
* Allocate a new buffer on the Python heap containing the new string.
|
||||
* 'len' is optional: if 0 the length is calculated.
|
||||
*
|
||||
* The returned string doesn't include quotes.
|
||||
*
|
||||
* 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
|
||||
* PQescapeIndentifier, which is only available from PostgreSQL 9.0.
|
||||
*/
|
||||
char *
|
||||
psycopg_escape_identifier_easy(const char *from, Py_ssize_t len)
|
||||
{
|
||||
char *rv;
|
||||
const char *src;
|
||||
char *dst;
|
||||
|
||||
if (!len) { len = strlen(from); }
|
||||
if (!(rv = PyMem_New(char, 1 + 2 * len))) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The only thing to do is double quotes */
|
||||
for (src = from, dst = rv; *src; ++src, ++dst) {
|
||||
*dst = *src;
|
||||
if ('"' == *src) {
|
||||
*++dst = '"';
|
||||
}
|
||||
}
|
||||
|
||||
*dst = '\0';
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Duplicate a string.
|
||||
*
|
||||
* Allocate a new buffer on the Python heap containing the new string.
|
||||
|
|
|
@ -141,6 +141,16 @@ class CursorTests(unittest.TestCase):
|
|||
del curs
|
||||
self.assert_(w() is None)
|
||||
|
||||
def test_invalid_name(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("create temp table invname (data int);")
|
||||
curs.execute("insert into invname values (10), (20), (30)")
|
||||
curs.close()
|
||||
|
||||
curs = self.conn.cursor(r'1-2-3 \ "test"')
|
||||
curs.execute("select data from invname order by data")
|
||||
self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)])
|
||||
|
||||
@skip_before_postgres(8, 2)
|
||||
def test_iter_named_cursor_efficient(self):
|
||||
curs = self.conn.cursor('tmp')
|
||||
|
|
Loading…
Reference in New Issue
Block a user