diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 4db76b01..d96cca4f 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -221,6 +221,19 @@ functionalities defined by the |DBAPI|_. .. __: 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 protocol objects diff --git a/lib/extensions.py b/lib/extensions.py index d10e8ac6..b40e28b8 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -56,7 +56,7 @@ try: except ImportError: 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 ISQLQuote, Notify, Diagnostics, Column diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index c77dce5b..9906b7be 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -165,6 +165,42 @@ exit: return res; } + +#define psyco_quote_ident_doc "quote_ident(str, conn_or_curs) -> str" + +static PyObject * +psyco_quote_ident(PyObject *self, PyObject *args) +{ + const char *str = NULL; + char *quoted; + PyObject *obj, *result; + connectionObject *conn; + + if (!PyArg_ParseTuple(args, "sO", &str, &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; + } + + quoted = PQescapeIdentifier(conn->pgconn, str, strlen(str)); + if (!quoted) { + PyErr_NoMemory(); + return NULL; + } + result = conn_text_from_chars(conn, quoted); + PQfreemem(quoted); + + return result; +} + /** type registration **/ #define psyco_register_type_doc \ "register_type(obj, conn_or_curs) -> None -- register obj with psycopg type system\n\n" \ @@ -768,6 +804,8 @@ static PyMethodDef psycopgMethods[] = { METH_VARARGS|METH_KEYWORDS, psyco_parse_dsn_doc}, {"adapt", (PyCFunction)psyco_microprotocols_adapt, METH_VARARGS, psyco_microprotocols_adapt_doc}, + {"quote_ident", (PyCFunction)psyco_quote_ident, + METH_VARARGS, psyco_quote_ident_doc}, {"register_type", (PyCFunction)psyco_register_type, METH_VARARGS, psyco_register_type_doc}, diff --git a/psycopg/utils.c b/psycopg/utils.c index 836f6129..ec8e47c8 100644 --- a/psycopg/utils.c +++ b/psycopg/utils.c @@ -87,7 +87,7 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len, 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. * '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 * 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 * psycopg_escape_identifier_easy(const char *from, Py_ssize_t len) diff --git a/tests/test_quote.py b/tests/test_quote.py index e7b3c316..a24ab6d4 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -165,6 +165,13 @@ class TestQuotedString(ConnectingTestCase): self.assertEqual(q.encoding, 'utf_8') +class TestQuotedIdentifier(ConnectingTestCase): + 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"') + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__)