From a4cbb088fe2b1b3441b249a06e6498d17c3e56d9 Mon Sep 17 00:00:00 2001 From: Oleksandr Shulgin Date: Fri, 30 Oct 2015 11:10:41 +0100 Subject: [PATCH 01/34] Add connection.get_dsn_parameters() --- doc/src/connection.rst | 18 ++++++++++++++++++ psycopg/connection_type.c | 33 +++++++++++++++++++++++++++++++++ psycopg/psycopg.h | 3 +++ psycopg/psycopgmodule.c | 22 +++------------------- psycopg/utils.c | 29 +++++++++++++++++++++++++++++ tests/test_connection.py | 6 ++++++ 6 files changed, 92 insertions(+), 19 deletions(-) diff --git a/doc/src/connection.rst b/doc/src/connection.rst index cceef1e5..3d38180a 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -568,6 +568,24 @@ The ``connection`` class .. versionadded:: 2.0.12 + .. method:: get_dsn_parameters() + + Get the effective dsn parameters for the connection as a dictionary. + + The *password* parameter is removed from the result. + + Example:: + + >>> conn.get_dsn_parameters() + {'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'} + + Requires libpq >= 9.3. + + .. seealso:: libpq docs for `PQconninfo()`__ for details. + + .. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFO + + .. index:: pair: Transaction; Status diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 2c1dddf2..5c74c301 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -733,6 +733,37 @@ psyco_conn_get_parameter_status(connectionObject *self, PyObject *args) return conn_text_from_chars(self, val); } +/* get_dsn_parameters method - Get connection parameters */ + +#define psyco_conn_get_dsn_parameters_doc \ +"get_dsn_parameters() -- Get effective connection parameters.\n\n" + +static PyObject * +psyco_conn_get_dsn_parameters(connectionObject *self) +{ +#if PG_VERSION_NUM >= 90300 + PyObject *res = NULL; + PQconninfoOption *options = NULL; + + EXC_IF_CONN_CLOSED(self); + + if (!(options = PQconninfo(self->pgconn))) { + PyErr_NoMemory(); + goto exit; + } + + res = psycopg_dict_from_conninfo_options(options, /* include_password = */ 0); + +exit: + PQconninfoFree(options); + + return res; +#else + PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3"); + return NULL; +#endif +} + /* lobject method - allocate a new lobject */ @@ -977,6 +1008,8 @@ static struct PyMethodDef connectionObject_methods[] = { METH_NOARGS, psyco_conn_get_transaction_status_doc}, {"get_parameter_status", (PyCFunction)psyco_conn_get_parameter_status, METH_VARARGS, psyco_conn_get_parameter_status_doc}, + {"get_dsn_parameters", (PyCFunction)psyco_conn_get_dsn_parameters, + METH_NOARGS, psyco_conn_get_dsn_parameters_doc}, {"get_backend_pid", (PyCFunction)psyco_conn_get_backend_pid, METH_NOARGS, psyco_conn_get_backend_pid_doc}, {"lobject", (PyCFunction)psyco_conn_lobject, diff --git a/psycopg/psycopg.h b/psycopg/psycopg.h index eb406fd2..13326ccf 100644 --- a/psycopg/psycopg.h +++ b/psycopg/psycopg.h @@ -131,6 +131,9 @@ STEALS(1) HIDDEN PyObject * psycopg_ensure_bytes(PyObject *obj); STEALS(1) HIDDEN PyObject * psycopg_ensure_text(PyObject *obj); +HIDDEN PyObject *psycopg_dict_from_conninfo_options(PQconninfoOption *options, + int include_password); + /* Exceptions docstrings */ #define Error_doc \ "Base class for error exceptions." diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index cf70a4ad..38dd539b 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -118,8 +118,8 @@ static PyObject * psyco_parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs) { char *err = NULL; - PQconninfoOption *options = NULL, *o; - PyObject *dict = NULL, *res = NULL, *dsn; + PQconninfoOption *options = NULL; + PyObject *res = NULL, *dsn; static char *kwlist[] = {"dsn", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &dsn)) { @@ -140,26 +140,10 @@ psyco_parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs) goto exit; } - if (!(dict = PyDict_New())) { goto exit; } - for (o = options; o->keyword != NULL; o++) { - if (o->val != NULL) { - PyObject *value; - if (!(value = Text_FromUTF8(o->val))) { goto exit; } - if (PyDict_SetItemString(dict, o->keyword, value) != 0) { - Py_DECREF(value); - goto exit; - } - Py_DECREF(value); - } - } - - /* success */ - res = dict; - dict = NULL; + res = psycopg_dict_from_conninfo_options(options, /* include_password = */ 1); exit: PQconninfoFree(options); /* safe on null */ - Py_XDECREF(dict); Py_XDECREF(dsn); return res; diff --git a/psycopg/utils.c b/psycopg/utils.c index ec8e47c8..1b10c4aa 100644 --- a/psycopg/utils.c +++ b/psycopg/utils.c @@ -247,3 +247,32 @@ psycopg_is_text_file(PyObject *f) } } +/* Make a dict out of PQconninfoOption array */ +PyObject * +psycopg_dict_from_conninfo_options(PQconninfoOption *options, int include_password) +{ + PyObject *dict, *res = NULL; + PQconninfoOption *o; + + if (!(dict = PyDict_New())) { goto exit; } + for (o = options; o->keyword != NULL; o++) { + if (o->val != NULL && + (include_password || strcmp(o->keyword, "password") != 0)) { + PyObject *value; + if (!(value = Text_FromUTF8(o->val))) { goto exit; } + if (PyDict_SetItemString(dict, o->keyword, value) != 0) { + Py_DECREF(value); + goto exit; + } + Py_DECREF(value); + } + } + + res = dict; + dict = NULL; + +exit: + Py_XDECREF(dict); + + return res; +} diff --git a/tests/test_connection.py b/tests/test_connection.py index 68bb6f05..7e183a82 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -381,6 +381,12 @@ class ParseDsnTestCase(ConnectingTestCase): self.assertRaises(TypeError, parse_dsn, None) self.assertRaises(TypeError, parse_dsn, 42) + def test_get_dsn_paramaters(self): + conn = self.connect() + d = conn.get_dsn_parameters() + self.assertEqual(d['dbname'], dbname) # the only param we can check reliably + self.assertNotIn('password', d) + class IsolationLevelsTestCase(ConnectingTestCase): From 602fefcae33f52544e2fd4fd7883929999b1b5a0 Mon Sep 17 00:00:00 2001 From: Oleksandr Shulgin Date: Fri, 30 Oct 2015 11:38:28 +0100 Subject: [PATCH 02/34] Fix typo in a new test name --- tests/test_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_connection.py b/tests/test_connection.py index 7e183a82..eddb0536 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -381,7 +381,7 @@ class ParseDsnTestCase(ConnectingTestCase): self.assertRaises(TypeError, parse_dsn, None) self.assertRaises(TypeError, parse_dsn, 42) - def test_get_dsn_paramaters(self): + def test_get_dsn_parameters(self): conn = self.connect() d = conn.get_dsn_parameters() self.assertEqual(d['dbname'], dbname) # the only param we can check reliably From 051e6d13646d2cafe17e647ce79e69b32e6397b3 Mon Sep 17 00:00:00 2001 From: Oleksandr Shulgin Date: Fri, 30 Oct 2015 13:02:45 +0100 Subject: [PATCH 03/34] Add skip_before_libpq for test_get_dsn_parameters --- tests/test_connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index eddb0536..6f7ab88b 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -381,6 +381,7 @@ class ParseDsnTestCase(ConnectingTestCase): self.assertRaises(TypeError, parse_dsn, None) self.assertRaises(TypeError, parse_dsn, 42) + @skip_before_libpq(9, 3) def test_get_dsn_parameters(self): conn = self.connect() d = conn.get_dsn_parameters() From cf83470891f233156657ac4c9d20bd16c85690a3 Mon Sep 17 00:00:00 2001 From: Udi Oron Date: Tue, 10 Nov 2015 00:35:02 +0200 Subject: [PATCH 04/34] Suggest installing psycopg2 in windows using pip pip is becoming the standard method for installing python packages, and now binary wheels are a better and easier option for users: https://github.com/psycopg/psycopg2/issues/368 --- doc/src/install.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/src/install.rst b/doc/src/install.rst index ec1eeea8..a7a973c8 100644 --- a/doc/src/install.rst +++ b/doc/src/install.rst @@ -95,7 +95,15 @@ Install from a package pair: Install; Windows **Microsoft Windows** - Jason Erickson maintains a packaged `Windows port of Psycopg`__ with + There are two options to install a precompiled `psycopg2` package under windows: + + **Option 1:** Using `pip`__ (Included in python 2.7.9+ and python 3.4+) and a binary wheel package. Launch windows' command prompt (`cmd.exe`) and execute the following command:: + + pip install psycopg2 + + .. __: https://pip.pypa.io/en/stable/installing/ + + **Option 2:** Jason Erickson maintains a packaged `Windows port of Psycopg`__ with installation executable. Download. Double click. Done. .. __: http://www.stickpeople.com/projects/python/win-psycopg/ From d829a75f2ef1b784a4304c65eb2b21c1a3b2aaf1 Mon Sep 17 00:00:00 2001 From: "Bernhard M. Wiedemann" Date: Wed, 9 Mar 2016 16:32:38 +0100 Subject: [PATCH 05/34] dont claim copyright for future years otherwise, when building from unchanged source in 2018, it would claim Copyright 2018 which is not true Being able to reproduce identical output from identical input is important to Linux distributions --- doc/src/conf.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/src/conf.py b/doc/src/conf.py index 18b81e07..94ffa349 100644 --- a/doc/src/conf.py +++ b/doc/src/conf.py @@ -42,9 +42,7 @@ master_doc = 'index' # General information about the project. project = u'Psycopg' -from datetime import date -year = date.today().year -copyright = u'2001-%s, Federico Di Gregorio, Daniele Varrazzo' % year +copyright = u'2001-2016, Federico Di Gregorio, Daniele Varrazzo' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 88d3d7fc7ef761da15dbe7e4f38b4d2c7415ccfb Mon Sep 17 00:00:00 2001 From: Gabriel Linder Date: Wed, 9 Mar 2016 21:51:02 +0100 Subject: [PATCH 06/34] Typo. --- doc/src/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/advanced.rst b/doc/src/advanced.rst index e63fcff1..f2e279f8 100644 --- a/doc/src/advanced.rst +++ b/doc/src/advanced.rst @@ -47,7 +47,7 @@ it is the class where query building, execution and result type-casting into Python variables happens. The `~psycopg2.extras` module contains several examples of :ref:`connection -and cursor sublcasses `. +and cursor subclasses `. .. note:: From 2cdc8d61a2da9f02c5b61daca6c83b61aca386f3 Mon Sep 17 00:00:00 2001 From: Jason Erickson Date: Mon, 8 Jun 2015 11:37:23 -0600 Subject: [PATCH 07/34] Fix Windows 64bit lobject support for very (>2GB) large objects The type 'long' with Windows Visual C is 32bits in size for both 32bit and 64bit platforms. Changed type of variables that could be > 2GB from long to Py_ssize_t. --- psycopg/lobject.h | 4 ++-- psycopg/lobject_int.c | 22 +++++++++++----------- psycopg/lobject_type.c | 20 +++++++++++--------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/psycopg/lobject.h b/psycopg/lobject.h index b9c8c3d8..73cf6192 100644 --- a/psycopg/lobject.h +++ b/psycopg/lobject.h @@ -60,8 +60,8 @@ RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename); RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len); RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf, size_t len); -RAISES_NEG HIDDEN long lobject_seek(lobjectObject *self, long pos, int whence); -RAISES_NEG HIDDEN long lobject_tell(lobjectObject *self); +RAISES_NEG HIDDEN Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence); +RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self); RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len); RAISES_NEG HIDDEN int lobject_close(lobjectObject *self); diff --git a/psycopg/lobject_int.c b/psycopg/lobject_int.c index 8788c100..279ef1e2 100644 --- a/psycopg/lobject_int.c +++ b/psycopg/lobject_int.c @@ -376,12 +376,12 @@ lobject_read(lobjectObject *self, char *buf, size_t len) /* lobject_seek - move the current position in the lo */ -RAISES_NEG long -lobject_seek(lobjectObject *self, long pos, int whence) +RAISES_NEG Py_ssize_t +lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence) { PGresult *pgres = NULL; char *error = NULL; - long where; + Py_ssize_t where; Dprintf("lobject_seek: fd = %d, pos = %ld, whence = %d", self->fd, pos, whence); @@ -391,12 +391,12 @@ lobject_seek(lobjectObject *self, long pos, int whence) #ifdef HAVE_LO64 if (self->conn->server_version < 90300) { - where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence); + where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence); } else { - where = lo_lseek64(self->conn->pgconn, self->fd, pos, whence); + where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence); } #else - where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence); + where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence); #endif Dprintf("lobject_seek: where = %ld", where); if (where < 0) @@ -412,12 +412,12 @@ lobject_seek(lobjectObject *self, long pos, int whence) /* lobject_tell - tell the current position in the lo */ -RAISES_NEG long +RAISES_NEG Py_ssize_t lobject_tell(lobjectObject *self) { PGresult *pgres = NULL; char *error = NULL; - long where; + Py_ssize_t where; Dprintf("lobject_tell: fd = %d", self->fd); @@ -426,12 +426,12 @@ lobject_tell(lobjectObject *self) #ifdef HAVE_LO64 if (self->conn->server_version < 90300) { - where = (long)lo_tell(self->conn->pgconn, self->fd); + where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd); } else { - where = lo_tell64(self->conn->pgconn, self->fd); + where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd); } #else - where = (long)lo_tell(self->conn->pgconn, self->fd); + where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd); #endif Dprintf("lobject_tell: where = %ld", where); if (where < 0) diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index a43325d4..634e76ca 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -105,7 +105,7 @@ psyco_lobj_write(lobjectObject *self, PyObject *args) goto exit; } - rv = PyInt_FromLong((long)res); + rv = PyInt_FromSsize_t((Py_ssize_t)res); exit: Py_XDECREF(data); @@ -121,7 +121,7 @@ static PyObject * psyco_lobj_read(lobjectObject *self, PyObject *args) { PyObject *res; - long where, end; + Py_ssize_t where, end; Py_ssize_t size = -1; char *buffer; @@ -165,10 +165,10 @@ psyco_lobj_read(lobjectObject *self, PyObject *args) static PyObject * psyco_lobj_seek(lobjectObject *self, PyObject *args) { - long offset, pos=0; + Py_ssize_t offset, pos=0; int whence=0; - if (!PyArg_ParseTuple(args, "l|i", &offset, &whence)) + if (!PyArg_ParseTuple(args, "n|i", &offset, &whence)) return NULL; EXC_IF_LOBJ_CLOSED(self); @@ -197,7 +197,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args) if ((pos = lobject_seek(self, offset, whence)) < 0) return NULL; - return PyLong_FromLong(pos); + return PyLong_FromSsize_t(pos); } /* tell method - tell current position in the lobject */ @@ -208,7 +208,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args) static PyObject * psyco_lobj_tell(lobjectObject *self, PyObject *args) { - long pos; + Py_ssize_t pos; EXC_IF_LOBJ_CLOSED(self); EXC_IF_LOBJ_LEVEL0(self); @@ -217,7 +217,7 @@ psyco_lobj_tell(lobjectObject *self, PyObject *args) if ((pos = lobject_tell(self)) < 0) return NULL; - return PyLong_FromLong(pos); + return PyLong_FromSsize_t(pos); } /* unlink method - unlink (destroy) the lobject */ @@ -274,10 +274,12 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure) static PyObject * psyco_lobj_truncate(lobjectObject *self, PyObject *args) { - long len = 0; + Py_ssize_t len = 0; - if (!PyArg_ParseTuple(args, "|l", &len)) + Dprintf("psyco_lobj_truncate: Enter lobject object at %p", self); + if (!PyArg_ParseTuple(args, "|n", &len)) return NULL; + Dprintf("psyco_lobj_truncate: Parsed Successfully"); EXC_IF_LOBJ_CLOSED(self); EXC_IF_LOBJ_LEVEL0(self); From d0309333b77fa589760d44ad112a580535196a51 Mon Sep 17 00:00:00 2001 From: Jason Erickson Date: Mon, 8 Jun 2015 14:05:05 -0600 Subject: [PATCH 08/34] Removed added Dprintf statements Removed extra Dprintf statements added to trouble large objects --- psycopg/lobject_type.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index 634e76ca..c4009f7a 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -276,10 +276,8 @@ psyco_lobj_truncate(lobjectObject *self, PyObject *args) { Py_ssize_t len = 0; - Dprintf("psyco_lobj_truncate: Enter lobject object at %p", self); if (!PyArg_ParseTuple(args, "|n", &len)) return NULL; - Dprintf("psyco_lobj_truncate: Parsed Successfully"); EXC_IF_LOBJ_CLOSED(self); EXC_IF_LOBJ_LEVEL0(self); From c13956dc10e66dc60269674ca414269e8b34bec5 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 14 Jun 2015 18:33:22 +0100 Subject: [PATCH 09/34] Fixed compiler warnings about Py_ssize_t printf format --- psycopg/lobject_type.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index c4009f7a..926c109c 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -187,8 +187,8 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args) #else if (offset < INT_MIN || offset > INT_MAX) { PyErr_Format(InterfaceError, - "offset out of range (%ld): this psycopg version was not built " - "with lobject 64 API support", + "offset out of range (" FORMAT_CODE_PY_SSIZE_T "): " + "this psycopg version was not built with lobject 64 API support", offset); return NULL; } @@ -286,16 +286,16 @@ psyco_lobj_truncate(lobjectObject *self, PyObject *args) #ifdef HAVE_LO64 if (len > INT_MAX && self->conn->server_version < 90300) { PyErr_Format(NotSupportedError, - "len out of range (%ld): server version %d " - "does not support the lobject 64 API", + "len out of range (" FORMAT_CODE_PY_SSIZE_T "): " + "server version %d does not support the lobject 64 API", len, self->conn->server_version); return NULL; } #else if (len > INT_MAX) { PyErr_Format(InterfaceError, - "len out of range (%ld): this psycopg version was not built " - "with lobject 64 API support", + "len out of range (" FORMAT_CODE_PY_SSIZE_T "): " + "this psycopg version was not built with lobject 64 API support", len); return NULL; } From eb687103b4332da3fe1080d3586a18889b472994 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 14 Jun 2015 18:43:58 +0100 Subject: [PATCH 10/34] Skip null array test on Postgres versions not supporting it --- tests/test_types_basic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py index 199dc1b6..4923d820 100755 --- a/tests/test_types_basic.py +++ b/tests/test_types_basic.py @@ -192,6 +192,7 @@ class TypesBasicTests(ConnectingTestCase): self.assertRaises(psycopg2.DataError, psycopg2.extensions.STRINGARRAY, b(s), curs) + @testutils.skip_before_postgres(8, 2) def testArrayOfNulls(self): curs = self.conn.cursor() curs.execute(""" From 244f233e1cb5b8890d5de04b22a4078efdfc5dbd Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 15 Jun 2015 03:43:11 +0100 Subject: [PATCH 11/34] Fixed manifest trying to include Makefiles from build env --- MANIFEST.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 00e4fc32..66fc2656 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,8 +2,8 @@ recursive-include psycopg *.c *.h *.manifest recursive-include lib *.py recursive-include tests *.py recursive-include examples *.py somehackers.jpg whereareyou.jpg -recursive-include doc README SUCCESS COPYING.LESSER pep-0249.txt -recursive-include doc Makefile requirements.txt +include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt +include doc/Makefile doc/requirements.txt recursive-include doc/src *.rst *.py *.css Makefile recursive-include scripts *.py *.sh include scripts/maketypes.sh scripts/buildtypes.py From 8611d91b356dff668089394ba6c8b81bc27261e6 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 15 Jun 2015 10:31:14 +0100 Subject: [PATCH 12/34] Fixed build on Python 2.5 --- psycopg/lobject_type.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index 926c109c..d15eb20e 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -197,7 +197,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args) if ((pos = lobject_seek(self, offset, whence)) < 0) return NULL; - return PyLong_FromSsize_t(pos); + return PyInt_FromSsize_t(pos); } /* tell method - tell current position in the lobject */ @@ -217,7 +217,7 @@ psyco_lobj_tell(lobjectObject *self, PyObject *args) if ((pos = lobject_tell(self)) < 0) return NULL; - return PyLong_FromSsize_t(pos); + return PyInt_FromSsize_t(pos); } /* unlink method - unlink (destroy) the lobject */ From 22fe6e7aad5b55923a8fdbf154a5fe740491257b Mon Sep 17 00:00:00 2001 From: Jason Erickson Date: Fri, 6 Nov 2015 15:01:32 -0700 Subject: [PATCH 13/34] Modify setup.py to support setuptools/wheel To support creation of whl files for PyPI, setuptools need to be imported instead of distutils. Created try/except case to fall back to integrated distutils if setuptools is not installed. --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2de8c5ef..570c5def 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,10 @@ import os import sys import re import subprocess -from distutils.core import setup, Extension +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension from distutils.command.build_ext import build_ext from distutils.sysconfig import get_python_inc from distutils.ccompiler import get_default_compiler From 2d91864977be1fd590305e895402ebd3b655196e Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 7 Mar 2016 10:38:40 +0000 Subject: [PATCH 14/34] setuptools in the news --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 9467fea7..8f085599 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,7 @@ What's new in psycopg 2.6.2 - Raise `!NotSupportedError` on unhandled server response status (:ticket:`#352`). - Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`). +- Added support for setuptools/wheel (:ticket:`#370`). - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). From 006693421d6bc9ef37be506798034433b538fe6e Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 8 Mar 2016 04:34:12 +0000 Subject: [PATCH 15/34] Fixed 'make sdist' to work with setuptools --- MANIFEST.in | 2 +- Makefile | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 66fc2656..0d34fd3d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,4 +8,4 @@ recursive-include doc/src *.rst *.py *.css Makefile recursive-include scripts *.py *.sh include scripts/maketypes.sh scripts/buildtypes.py include AUTHORS README.rst INSTALL LICENSE NEWS -include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile +include MANIFEST.in setup.py setup.cfg Makefile diff --git a/Makefile b/Makefile index 232f0d0b..a8f491e4 100644 --- a/Makefile +++ b/Makefile @@ -92,14 +92,9 @@ $(PACKAGE)/tests/%.py: tests/%.py $(PYTHON) setup.py build_py $(BUILD_OPT) touch $@ -$(SDIST): MANIFEST $(SOURCE) +$(SDIST): $(SOURCE) $(PYTHON) setup.py sdist $(SDIST_OPT) -MANIFEST: MANIFEST.in $(SOURCE) - # Run twice as MANIFEST.in includes MANIFEST - $(PYTHON) setup.py sdist --manifest-only - $(PYTHON) setup.py sdist --manifest-only - # docs depend on the build as it partly use introspection. doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC) $(MAKE) -C doc html @@ -111,5 +106,5 @@ doc/docs.zip: doc/html/genindex.html (cd doc/html && zip -r ../docs.zip *) clean: - rm -rf build MANIFEST + rm -rf build $(MAKE) -C doc clean From 654eeec24cc1ad63f84fcbf22a84994b9796a82e Mon Sep 17 00:00:00 2001 From: Christian Ullrich Date: Wed, 6 Jan 2016 15:04:33 +0100 Subject: [PATCH 16/34] Work around late initialization in distutils._msvccompiler. --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 570c5def..45e76bf0 100644 --- a/setup.py +++ b/setup.py @@ -304,6 +304,10 @@ class psycopg_build_ext(build_ext): except AttributeError: ext_path = os.path.join(self.build_lib, 'psycopg2', '_psycopg.pyd') + # Make sure spawn() will work if compile() was never + # called. https://github.com/psycopg/psycopg2/issues/380 + if not self.compiler.initialized: + self.compiler.initialize() self.compiler.spawn( ['mt.exe', '-nologo', '-manifest', os.path.join('psycopg', manifest), From 48260c64063ba6d1d6045d57822c22d3378a832e Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 8 Mar 2016 00:25:19 +0000 Subject: [PATCH 17/34] Py 3.5 MSVC 2015 build fixed noted in news Close issue #380. --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 8f085599..8a1b477b 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,7 @@ What's new in psycopg 2.6.2 (:ticket:`#352`). - Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`). - Added support for setuptools/wheel (:ticket:`#370`). +- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`). - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). From 65ec7e8bcb5f082bdbf6eddadd7ef6e601098fbd Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 8 Mar 2016 05:12:06 +0000 Subject: [PATCH 18/34] Fixed read() exception propagation in copy_from Close issue #412. --- NEWS | 1 + psycopg/pqpath.c | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 8a1b477b..52307ed2 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,7 @@ What's new in psycopg 2.6.2 - Added support for setuptools/wheel (:ticket:`#370`). - Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`). - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). +- Fixed `!read()` exception propagation in copy_from (:ticket:`#412`). What's new in psycopg 2.6.1 diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index b643512d..99dd40be 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -1393,7 +1393,11 @@ _pq_copy_in_v3(cursorObject *curs) Py_DECREF(str); } } - PyErr_Restore(t, ex, tb); + /* Clear the Py exception: it will be re-raised from the libpq */ + Py_XDECREF(t); + Py_XDECREF(ex); + Py_XDECREF(tb); + PyErr_Clear(); } res = PQputCopyEnd(curs->conn->pgconn, buf); } From 4fb236e68822f69f89c5ea29a7fcdddcaa819e4c Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 8 Mar 2016 05:13:57 +0000 Subject: [PATCH 19/34] Start advertising Py 3.5 support --- doc/src/install.rst | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/src/install.rst b/doc/src/install.rst index ec1eeea8..0c9bbdc0 100644 --- a/doc/src/install.rst +++ b/doc/src/install.rst @@ -18,7 +18,7 @@ The current `!psycopg2` implementation supports: NOTE: keep consistent with setup.py and the /features/ page. - Python 2 versions from 2.5 to 2.7 -- Python 3 versions from 3.1 to 3.4 +- Python 3 versions from 3.1 to 3.5 - PostgreSQL versions from 7.4 to 9.4 .. _PostgreSQL: http://www.postgresql.org/ diff --git a/setup.py b/setup.py index 45e76bf0..d8908fc0 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ Programming Language :: Python :: 3.1 Programming Language :: Python :: 3.2 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 +Programming Language :: Python :: 3.5 Programming Language :: C Programming Language :: SQL Topic :: Database From 5ce00f8e5baa79548122b438c8f4d9f92eec682a Mon Sep 17 00:00:00 2001 From: Gabriel Kihlman Date: Mon, 18 Apr 2016 17:28:50 +0200 Subject: [PATCH 20/34] Avoid a possible null deref, tz might be NULL. Found by clang static analyzer. --- psycopg/adapter_datetime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c index 0571837d..9d04df40 100644 --- a/psycopg/adapter_datetime.c +++ b/psycopg/adapter_datetime.c @@ -451,7 +451,7 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args) tz); exit: - Py_DECREF(tz); + Py_XDECREF(tz); Py_XDECREF(m); return res; } From 3ed2c54790cd377759da5bc5a8b596fe2195685a Mon Sep 17 00:00:00 2001 From: Greg Ward Date: Tue, 28 Jun 2016 18:14:57 -0400 Subject: [PATCH 21/34] Fix scattered grammar/spelling errors in comments, debug output, etc. --- psycopg/cursor_type.c | 2 +- psycopg/pqpath.c | 8 ++++---- tests/testutils.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index cd8d5ca3..fe79bbf9 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -335,7 +335,7 @@ _psyco_curs_merge_query_args(cursorObject *self, PyErr_Fetch(&err, &arg, &trace); if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) { - Dprintf("psyco_curs_execute: TypeError exception catched"); + Dprintf("psyco_curs_execute: TypeError exception caught"); PyErr_NormalizeException(&err, &arg, &trace); if (PyObject_HasAttrString(arg, "args")) { diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index 99dd40be..eb862d3d 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -161,11 +161,11 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres) if (conn == NULL) { PyErr_SetString(DatabaseError, - "psycopg went psycotic and raised a null error"); + "psycopg went psychotic and raised a null error"); return; } - /* if the connection has somehow beed broken, we mark the connection + /* if the connection has somehow been broken, we mark the connection object as closed but requiring cleanup */ if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) conn->closed = 2; @@ -907,7 +907,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn)); return -1; } - Dprintf("curs_execute: pg connection at %p OK", curs->conn->pgconn); + Dprintf("pq_execute: pg connection at %p OK", curs->conn->pgconn); Py_BEGIN_ALLOW_THREADS; pthread_mutex_lock(&(curs->conn->lock)); @@ -932,7 +932,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int Py_UNBLOCK_THREADS; } - /* dont let pgres = NULL go to pq_fetch() */ + /* don't let pgres = NULL go to pq_fetch() */ if (curs->pgres == NULL) { pthread_mutex_unlock(&(curs->conn->lock)); Py_BLOCK_THREADS; diff --git a/tests/testutils.py b/tests/testutils.py index 76671d99..fc2b59d7 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -100,7 +100,7 @@ class ConnectingTestCase(unittest.TestCase): self._conns except AttributeError, e: raise AttributeError( - "%s (did you remember calling ConnectingTestCase.setUp()?)" + "%s (did you forget to call ConnectingTestCase.setUp()?)" % e) if 'dsn' in kwargs: From 52753b23e89b6bb83e724dd6c59fd342427dd54a Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 01:18:27 +0100 Subject: [PATCH 22/34] Document that the libpq must be available at runtime Fix issue #408. --- doc/src/install.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/src/install.rst b/doc/src/install.rst index 4d82c620..a3f7ae4a 100644 --- a/doc/src/install.rst +++ b/doc/src/install.rst @@ -51,6 +51,16 @@ extension packages, *above all if you are a Windows or a Mac OS user*, please use a pre-compiled package and go straight to the :ref:`module usage ` avoid bothering with the gory details. +.. note:: + + Regardless of the way `!psycopg2` is installed, at runtime it will need to + use the libpq_ library. `!psycopg2` relies on the host OS to find the + library file (usually ``libpq.so`` or ``libpq.dll``): if the library is + installed in a standard location there is usually no problem; if the + library is in a non-standard location you will have to tell somehow + psycopg how to find it, which is OS-dependent (for instance setting a + suitable :envvar:`LD_LIBRARY_PATH` on Linux). + .. _install-from-package: From b7330283bc13ee6b2072f333acbf1b84aff0206a Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 01:39:10 +0100 Subject: [PATCH 23/34] Wordsmithing on COPY commands Address somehow issue #397. --- doc/src/cursor.rst | 5 ++++- doc/src/usage.rst | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst index 73bb5375..45e62781 100644 --- a/doc/src/cursor.rst +++ b/doc/src/cursor.rst @@ -494,6 +494,9 @@ The ``cursor`` class .. rubric:: COPY-related methods + Efficiently copy data from file-like objects to the database and back. See + :ref:`copy` for an overview. + .. extension:: The :sql:`COPY` command is a PostgreSQL extension to the SQL standard. @@ -502,7 +505,7 @@ The ``cursor`` class .. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None) Read data *from* the file-like object *file* appending them to - the table named *table*. See :ref:`copy` for an overview. + the table named *table*. :param file: file-like object to read data from. It must have both `!read()` and `!readline()` methods. diff --git a/doc/src/usage.rst b/doc/src/usage.rst index 9dd31df2..3b42aeb9 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -864,11 +864,19 @@ Using COPY TO and COPY FROM Psycopg `cursor` objects provide an interface to the efficient PostgreSQL |COPY|__ command to move data from files to tables and back. + +Currently no adaptation is provided between Python and PostgreSQL types on +|COPY|: the file can be any Python file-like object but its format must be in +the format accepted by `PostgreSQL COPY command`__ (data fromat, escaped +characters, etc). + +.. __: COPY_ + The methods exposed are: `~cursor.copy_from()` Reads data *from* a file-like object appending them to a database table - (:sql:`COPY table FROM file` syntax). The source file must have both + (:sql:`COPY table FROM file` syntax). The source file must provide both `!read()` and `!readline()` method. `~cursor.copy_to()` From 7aedc61d410a551f74cf9b721b794c1540377887 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 02:09:56 +0100 Subject: [PATCH 24/34] Fixed segfault on repr() for uninitialized connections Close #361. --- NEWS | 5 +++-- psycopg/connection_type.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 52307ed2..9056b0f0 100644 --- a/NEWS +++ b/NEWS @@ -24,12 +24,13 @@ What's new in psycopg 2.6.2 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Report the server response status on errors (such as :ticket:`#281`). +- Raise `!NotSupportedError` on unhandled server response status + (:ticket:`#352`). - 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`). - Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`). +- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`). - Added support for setuptools/wheel (:ticket:`#370`). - Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`). - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 2c1dddf2..e1966b37 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -1171,7 +1171,7 @@ connection_repr(connectionObject *self) { return PyString_FromFormat( "", - self, self->dsn, self->closed); + self, (self->dsn ? self->dsn : ""), self->closed); } static int From c29b5cd46a24fd81cff8b3affd9c78d18d53aa69 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 02:23:59 +0100 Subject: [PATCH 25/34] Fixed build on win32 Fix #422. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d8908fc0..65d5fa69 100644 --- a/setup.py +++ b/setup.py @@ -351,7 +351,8 @@ class psycopg_build_ext(build_ext): self.libraries.append("advapi32") if self.compiler_is_msvc(): # MSVC requires an explicit "libpq" - self.libraries.remove("pq") + if "pq" in self.libraries: + self.libraries.remove("pq") self.libraries.append("secur32") self.libraries.append("libpq") self.libraries.append("shfolder") From bada1f1f8e390c7d0ef8eea88cdb16e6f78c1eec Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 11:17:55 +0100 Subject: [PATCH 26/34] Work in progress on writable encoding Would help using adapt(unicode) to quote strings without a connection, see ticket #331. Currently in heisenbug state: if test_connection_wins_anyway and test_encoding_default run (in this order), the latter fail because the returned value is "'\xe8 '", with an extra space. Skipping the first test, the second succeed. The bad value is returned by the libpq: ql = PQescapeString(to+eq+1, from, len); just returns len = 2 and an extra space in the string... meh. --- psycopg/adapter_qstring.c | 73 +++++++++++++++++++++++++++++---------- psycopg/adapter_qstring.h | 3 ++ tests/test_types_basic.py | 52 +++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 27 deletions(-) diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index 2e3ab0ae..1e256cf0 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -36,28 +36,43 @@ static const char *default_encoding = "latin1"; /* qstring_quote - do the quote process on plain and unicode strings */ +const char * +_qstring_get_encoding(qstringObject *self) +{ + /* if the wrapped object is an unicode object we can encode it to match + conn->encoding but if the encoding is not specified we don't know what + to do and we raise an exception */ + if (self->conn) { + return self->conn->codec; + } + else { + return self->encoding ? self->encoding : default_encoding; + } +} + static PyObject * qstring_quote(qstringObject *self) { PyObject *str = NULL; char *s, *buffer = NULL; Py_ssize_t len, qlen; - const char *encoding = default_encoding; + const char *encoding; PyObject *rv = NULL; - /* if the wrapped object is an unicode object we can encode it to match - conn->encoding but if the encoding is not specified we don't know what - to do and we raise an exception */ - if (self->conn) { - encoding = self->conn->codec; - } - + encoding = _qstring_get_encoding(self); Dprintf("qstring_quote: encoding to %s", encoding); - if (PyUnicode_Check(self->wrapped) && encoding) { - str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL); - Dprintf("qstring_quote: got encoded object at %p", str); - if (str == NULL) goto exit; + if (PyUnicode_Check(self->wrapped)) { + if (encoding) { + str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL); + Dprintf("qstring_quote: got encoded object at %p", str); + if (str == NULL) goto exit; + } + else { + PyErr_SetString(PyExc_TypeError, + "missing encoding to encode unicode object"); + goto exit; + } } #if PY_MAJOR_VERSION < 3 @@ -150,15 +165,34 @@ qstring_conform(qstringObject *self, PyObject *args) static PyObject * qstring_get_encoding(qstringObject *self) { - const char *encoding = default_encoding; - - if (self->conn) { - encoding = self->conn->codec; - } - + const char *encoding; + encoding = _qstring_get_encoding(self); return Text_FromUTF8(encoding); } +static int +qstring_set_encoding(qstringObject *self, PyObject *pyenc) +{ + int rv = -1; + const char *tmp; + char *cenc; + + /* get a C copy of the encoding (which may come from unicode) */ + Py_INCREF(pyenc); + if (!(pyenc = psycopg_ensure_bytes(pyenc))) { goto exit; } + if (!(tmp = Bytes_AsString(pyenc))) { goto exit; } + if (0 > psycopg_strdup(&cenc, tmp, 0)) { goto exit; } + + Dprintf("qstring_set_encoding: encoding set to %s", cenc); + PyMem_Free((void *)self->encoding); + self->encoding = cenc; + rv = 0; + +exit: + Py_XDECREF(pyenc); + return rv; +} + /** the QuotedString object **/ /* object member list */ @@ -183,7 +217,7 @@ static PyMethodDef qstringObject_methods[] = { static PyGetSetDef qstringObject_getsets[] = { { "encoding", (getter)qstring_get_encoding, - (setter)NULL, + (setter)qstring_set_encoding, "current encoding of the adapter" }, {NULL} }; @@ -216,6 +250,7 @@ qstring_dealloc(PyObject* obj) Py_CLEAR(self->wrapped); Py_CLEAR(self->buffer); Py_CLEAR(self->conn); + PyMem_Free((void *)self->encoding); Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = " FORMAT_CODE_PY_SSIZE_T, diff --git a/psycopg/adapter_qstring.h b/psycopg/adapter_qstring.h index b7b086f3..8abdc5f2 100644 --- a/psycopg/adapter_qstring.h +++ b/psycopg/adapter_qstring.h @@ -39,6 +39,9 @@ typedef struct { PyObject *buffer; connectionObject *conn; + + const char *encoding; + } qstringObject; #ifdef __cplusplus diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py index 4923d820..baa80c01 100755 --- a/tests/test_types_basic.py +++ b/tests/test_types_basic.py @@ -95,11 +95,11 @@ class TypesBasicTests(ConnectingTestCase): except ValueError: return self.skipTest("inf not available on this platform") s = self.execute("SELECT %s AS foo", (float("inf"),)) - self.failUnless(str(s) == "inf", "wrong float quoting: " + str(s)) + self.failUnless(str(s) == "inf", "wrong float quoting: " + str(s)) self.failUnless(type(s) == float, "wrong float conversion: " + repr(s)) s = self.execute("SELECT %s AS foo", (float("-inf"),)) - self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s)) + self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s)) def testBinary(self): if sys.version_info[0] < 3: @@ -344,6 +344,43 @@ class TypesBasicTests(ConnectingTestCase): self.assertEqual(a, [2,4,'nada']) +class TestStringAdapter(ConnectingTestCase): + def test_encoding_default(self): + from psycopg2.extensions import adapt + a = adapt("hello") + self.assertEqual(a.encoding, 'latin1') + self.assertEqual(a.getquoted(), "'hello'") + + egrave = u'\xe8' + self.assertEqual(adapt(egrave).getquoted(), "'\xe8'") + + def test_encoding_error(self): + from psycopg2.extensions import adapt + snowman = u"\u2603" + a = adapt(snowman) + self.assertRaises(UnicodeEncodeError, a.getquoted) + + def test_set_encoding(self): + from psycopg2.extensions import adapt + snowman = u"\u2603" + a = adapt(snowman) + a.encoding = 'utf8' + self.assertEqual(a.encoding, 'utf8') + self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") + + def test_connection_wins_anyway(self): + from psycopg2.extensions import adapt + snowman = u"\u2603" + a = adapt(snowman) + a.encoding = 'latin9' + + self.conn.set_client_encoding('utf8') + a.prepare(self.conn) + + self.assertEqual(a.encoding, 'utf_8') + self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") + + class AdaptSubclassTest(unittest.TestCase): def test_adapt_subtype(self): from psycopg2.extensions import adapt @@ -364,8 +401,8 @@ class AdaptSubclassTest(unittest.TestCase): try: self.assertEqual(b('b'), adapt(C()).getquoted()) finally: - del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] - del psycopg2.extensions.adapters[B, psycopg2.extensions.ISQLQuote] + del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] + del psycopg2.extensions.adapters[B, psycopg2.extensions.ISQLQuote] @testutils.skip_from_python(3) def test_no_mro_no_joy(self): @@ -378,8 +415,7 @@ class AdaptSubclassTest(unittest.TestCase): try: self.assertRaises(psycopg2.ProgrammingError, adapt, B()) finally: - del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] - + del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] @testutils.skip_before_python(3) def test_adapt_subtype_3(self): @@ -392,7 +428,7 @@ class AdaptSubclassTest(unittest.TestCase): try: self.assertEqual(b("a"), adapt(B()).getquoted()) finally: - del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] + del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] class ByteaParserTest(unittest.TestCase): @@ -480,6 +516,7 @@ class ByteaParserTest(unittest.TestCase): self.assertEqual(rv, tgt) + def skip_if_cant_cast(f): @wraps(f) def skip_if_cant_cast_(self, *args, **kwargs): @@ -499,4 +536,3 @@ def test_suite(): if __name__ == "__main__": unittest.main() - From 2e8e61b8d41144cbb65dfce786335ff7c625b4f7 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 16:57:25 +0100 Subject: [PATCH 27/34] Test moved to the right module, cleanup, but same problem --- psycopg/adapter_qstring.c | 3 +-- tests/test_quote.py | 47 ++++++++++++++++++++++++++++++++++----- tests/test_types_basic.py | 37 ------------------------------ tests/testconfig.py | 2 -- 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index 1e256cf0..110093e5 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -87,8 +87,7 @@ qstring_quote(qstringObject *self) /* if the wrapped object is not a string, this is an error */ else { - PyErr_SetString(PyExc_TypeError, - "can't quote non-string object (or missing encoding)"); + PyErr_SetString(PyExc_TypeError, "can't quote non-string object"); goto exit; } diff --git a/tests/test_quote.py b/tests/test_quote.py index 6e945624..9d00c539 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -29,6 +29,7 @@ import psycopg2 import psycopg2.extensions from psycopg2.extensions import b + class QuotingTestCase(ConnectingTestCase): r"""Checks the correct quoting of strings and binary objects. @@ -51,7 +52,7 @@ class QuotingTestCase(ConnectingTestCase): data = """some data with \t chars to escape into, 'quotes' and \\ a backslash too. """ - data += "".join(map(chr, range(1,127))) + data += "".join(map(chr, range(1, 127))) curs = self.conn.cursor() curs.execute("SELECT %s;", (data,)) @@ -90,13 +91,13 @@ class QuotingTestCase(ConnectingTestCase): if server_encoding != "UTF8": return self.skipTest( "Unicode test skipped since server encoding is %s" - % server_encoding) + % server_encoding) data = u"""some data with \t chars to escape into, 'quotes', \u20ac euro sign and \\ a backslash too. """ - data += u"".join(map(unichr, [ u for u in range(1,65536) - if not 0xD800 <= u <= 0xDFFF ])) # surrogate area + data += u"".join(map(unichr, [u for u in range(1, 65536) + if not 0xD800 <= u <= 0xDFFF])) # surrogate area self.conn.set_client_encoding('UNICODE') psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn) @@ -183,9 +184,45 @@ class TestQuotedIdentifier(ConnectingTestCase): self.assertEqual(quote_ident(snowman, self.conn), quoted) +class TestStringAdapter(ConnectingTestCase): + def test_encoding_default(self): + from psycopg2.extensions import adapt + a = adapt("hello") + self.assertEqual(a.encoding, 'latin1') + self.assertEqual(a.getquoted(), "'hello'") + + egrave = u'\xe8' + self.assertEqual(adapt(egrave).getquoted(), "'\xe8'") + + def test_encoding_error(self): + from psycopg2.extensions import adapt + snowman = u"\u2603" + a = adapt(snowman) + self.assertRaises(UnicodeEncodeError, a.getquoted) + + def test_set_encoding(self): + from psycopg2.extensions import adapt + snowman = u"\u2603" + a = adapt(snowman) + a.encoding = 'utf8' + self.assertEqual(a.encoding, 'utf8') + self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") + + def test_connection_wins_anyway(self): + from psycopg2.extensions import adapt + snowman = u"\u2603" + a = adapt(snowman) + a.encoding = 'latin9' + + self.conn.set_client_encoding('utf8') + a.prepare(self.conn) + + self.assertEqual(a.encoding, 'utf_8') + self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) if __name__ == "__main__": unittest.main() - diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py index baa80c01..248712b0 100755 --- a/tests/test_types_basic.py +++ b/tests/test_types_basic.py @@ -344,43 +344,6 @@ class TypesBasicTests(ConnectingTestCase): self.assertEqual(a, [2,4,'nada']) -class TestStringAdapter(ConnectingTestCase): - def test_encoding_default(self): - from psycopg2.extensions import adapt - a = adapt("hello") - self.assertEqual(a.encoding, 'latin1') - self.assertEqual(a.getquoted(), "'hello'") - - egrave = u'\xe8' - self.assertEqual(adapt(egrave).getquoted(), "'\xe8'") - - def test_encoding_error(self): - from psycopg2.extensions import adapt - snowman = u"\u2603" - a = adapt(snowman) - self.assertRaises(UnicodeEncodeError, a.getquoted) - - def test_set_encoding(self): - from psycopg2.extensions import adapt - snowman = u"\u2603" - a = adapt(snowman) - a.encoding = 'utf8' - self.assertEqual(a.encoding, 'utf8') - self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") - - def test_connection_wins_anyway(self): - from psycopg2.extensions import adapt - snowman = u"\u2603" - a = adapt(snowman) - a.encoding = 'latin9' - - self.conn.set_client_encoding('utf8') - a.prepare(self.conn) - - self.assertEqual(a.encoding, 'utf_8') - self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") - - class AdaptSubclassTest(unittest.TestCase): def test_adapt_subtype(self): from psycopg2.extensions import adapt diff --git a/tests/testconfig.py b/tests/testconfig.py index 0f995fbf..72c533ec 100644 --- a/tests/testconfig.py +++ b/tests/testconfig.py @@ -34,5 +34,3 @@ if dbuser is not None: dsn += ' user=%s' % dbuser if dbpass is not None: dsn += ' password=%s' % dbpass - - From 4a450b63c418bf7e6e62f7b444fd2edd9db246da Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 17:33:12 +0100 Subject: [PATCH 28/34] Don't hope to encode stuff in an arbitrary encoding libpq's PQescapeString will use the same encoding it has seen before in a connection (static_client_encoding). So I think I'll leave this feature here for people who know what is doing, but won't really document it as a feature: it can't really work in a generic way (unless adding some disgusting hack like creating a fake connection with the encoding we want to call PQescapeStringConn instead of PQescapeString). --- NEWS | 1 + tests/test_quote.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 9056b0f0..4571f673 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,7 @@ What's new in psycopg 2.6.2 - Report the server response status on errors (such as :ticket:`#281`). - Raise `!NotSupportedError` on unhandled server response status (:ticket:`#352`). +- Allow overriding string adapter encoding with no connection (:ticket:`#331`). - The `~psycopg2.extras.wait_select` callback allows interrupting a long-running query in an interactive shell using :kbd:`Ctrl-C` (:ticket:`#333`). diff --git a/tests/test_quote.py b/tests/test_quote.py index 9d00c539..0a204c83 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -157,7 +157,7 @@ class QuotingTestCase(ConnectingTestCase): class TestQuotedString(ConnectingTestCase): - def test_encoding(self): + def test_encoding_from_conn(self): q = psycopg2.extensions.QuotedString('hi') self.assertEqual(q.encoding, 'latin1') @@ -191,8 +191,11 @@ class TestStringAdapter(ConnectingTestCase): self.assertEqual(a.encoding, 'latin1') self.assertEqual(a.getquoted(), "'hello'") - egrave = u'\xe8' - self.assertEqual(adapt(egrave).getquoted(), "'\xe8'") + # NOTE: we can't really test an encoding different from utf8, because + # when encoding without connection the libpq will use parameters from + # a previous one, so what would happens depends jn the tests run order. + # egrave = u'\xe8' + # self.assertEqual(adapt(egrave).getquoted(), "'\xe8'") def test_encoding_error(self): from psycopg2.extensions import adapt @@ -201,6 +204,9 @@ class TestStringAdapter(ConnectingTestCase): self.assertRaises(UnicodeEncodeError, a.getquoted) def test_set_encoding(self): + # Note: this works-ish mostly in case when the standard db connection + # we test with is utf8, otherwise the encoding chosen by PQescapeString + # may give bad results. from psycopg2.extensions import adapt snowman = u"\u2603" a = adapt(snowman) From 9c156d41bbd9afe202c74cddb5c95d5ebd8aeb67 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 17:56:29 +0100 Subject: [PATCH 29/34] Docs wrapping --- doc/src/install.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/src/install.rst b/doc/src/install.rst index a3f7ae4a..3a95adc4 100644 --- a/doc/src/install.rst +++ b/doc/src/install.rst @@ -106,13 +106,15 @@ Install from a package **Microsoft Windows** There are two options to install a precompiled `psycopg2` package under windows: - - **Option 1:** Using `pip`__ (Included in python 2.7.9+ and python 3.4+) and a binary wheel package. Launch windows' command prompt (`cmd.exe`) and execute the following command:: + + **Option 1:** Using `pip`__ (Included in python 2.7.9+ and python 3.4+) + and a binary wheel package. Launch windows' command prompt (`cmd.exe`) + and execute the following command:: pip install psycopg2 - + .. __: https://pip.pypa.io/en/stable/installing/ - + **Option 2:** Jason Erickson maintains a packaged `Windows port of Psycopg`__ with installation executable. Download. Double click. Done. From 70af49c0a2a59c516fe89b30019430b8db551833 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 18:50:24 +0100 Subject: [PATCH 30/34] Fixed encoding tests on Py3 --- tests/test_quote.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_quote.py b/tests/test_quote.py index 0a204c83..74b366c9 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -189,7 +189,7 @@ class TestStringAdapter(ConnectingTestCase): from psycopg2.extensions import adapt a = adapt("hello") self.assertEqual(a.encoding, 'latin1') - self.assertEqual(a.getquoted(), "'hello'") + self.assertEqual(a.getquoted(), b("'hello'")) # NOTE: we can't really test an encoding different from utf8, because # when encoding without connection the libpq will use parameters from @@ -212,7 +212,7 @@ class TestStringAdapter(ConnectingTestCase): a = adapt(snowman) a.encoding = 'utf8' self.assertEqual(a.encoding, 'utf8') - self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") + self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'")) def test_connection_wins_anyway(self): from psycopg2.extensions import adapt @@ -224,7 +224,7 @@ class TestStringAdapter(ConnectingTestCase): a.prepare(self.conn) self.assertEqual(a.encoding, 'utf_8') - self.assertEqual(a.getquoted(), "'\xe2\x98\x83'") + self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'")) def test_suite(): From 5bcaf11f9db43f15d52943916647a0cc0dc6ffca Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 19:11:04 +0100 Subject: [PATCH 31/34] Allow adapting bytes using QuotedString on Python 3 too Close #365. --- NEWS | 1 + psycopg/adapter_qstring.c | 6 ++---- tests/test_quote.py | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 4571f673..28a9ac2f 100644 --- a/NEWS +++ b/NEWS @@ -32,6 +32,7 @@ What's new in psycopg 2.6.2 (:ticket:`#333`). - Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`). - Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`). +- Allow adapting bytes using QuotedString on Python 3 too (:ticket:`#365`). - Added support for setuptools/wheel (:ticket:`#370`). - Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`). - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index 110093e5..8c5a8f10 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -75,15 +75,13 @@ qstring_quote(qstringObject *self) } } -#if PY_MAJOR_VERSION < 3 - /* if the wrapped object is a simple string, we don't know how to + /* if the wrapped object is a binary string, we don't know how to (re)encode it, so we pass it as-is */ - else if (PyString_Check(self->wrapped)) { + else if (Bytes_Check(self->wrapped)) { str = self->wrapped; /* INCREF to make it ref-wise identical to unicode one */ Py_INCREF(str); } -#endif /* if the wrapped object is not a string, this is an error */ else { diff --git a/tests/test_quote.py b/tests/test_quote.py index 74b366c9..25d1d31c 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -23,7 +23,8 @@ # License for more details. import sys -from testutils import unittest, ConnectingTestCase, skip_before_libpq +import testutils +from testutils import unittest, ConnectingTestCase import psycopg2 import psycopg2.extensions @@ -167,13 +168,13 @@ class TestQuotedString(ConnectingTestCase): class TestQuotedIdentifier(ConnectingTestCase): - @skip_before_libpq(9, 0) + @testutils.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) + @testutils.skip_before_libpq(9, 0) def test_unicode_ident(self): from psycopg2.extensions import quote_ident snowman = u"\u2603" @@ -226,6 +227,14 @@ class TestStringAdapter(ConnectingTestCase): self.assertEqual(a.encoding, 'utf_8') self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'")) + @testutils.skip_before_python(3) + def test_adapt_bytes(self): + snowman = u"\u2603" + self.conn.set_client_encoding('utf8') + a = psycopg2.extensions.QuotedString(snowman.encode('utf8')) + a.prepare(self.conn) + self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'")) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 80fd14463be54c13600b27e7fd4a9228a3500712 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 19:27:31 +0100 Subject: [PATCH 32/34] Mention closing bug #424 in the news --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 52307ed2..c25e1c38 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,7 @@ What's new in psycopg 2.6.2 - Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`). - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). - Fixed `!read()` exception propagation in copy_from (:ticket:`#412`). +- Fixed possible NULL TZ decref (:ticket:`#424`). What's new in psycopg 2.6.1 From 00de4052d156c5b17dce4fa1539b48e97bb2131c Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 20:04:00 +0100 Subject: [PATCH 33/34] Mention get_dsn_parameters() in news, improved docs metadata --- NEWS | 1 + doc/src/connection.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 5200c4dd..98c309fe 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,7 @@ New features: customized replacing them with any object exposing an `!append()` method (:ticket:`#326`). - Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`). +- Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`). What's new in psycopg 2.6.2 diff --git a/doc/src/connection.rst b/doc/src/connection.rst index 3d38180a..c99c8bd8 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -568,6 +568,9 @@ The ``connection`` class .. versionadded:: 2.0.12 + .. index:: + pair: Connection; Parameters + .. method:: get_dsn_parameters() Get the effective dsn parameters for the connection as a dictionary. @@ -585,6 +588,8 @@ The ``connection`` class .. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFO + .. versionadded:: 2.7 + .. index:: pair: Transaction; Status From 90ee1ebba5d1da4bd9d8c6e12944308074732f08 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 1 Jul 2016 20:08:53 +0100 Subject: [PATCH 34/34] errorcodes map updated to PostgreSQL 9.5. --- NEWS | 1 + lib/errorcodes.py | 4 ++++ scripts/make_errorcodes.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 5d0d448c..49ed56a9 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,7 @@ What's new in psycopg 2.6.2 - Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`). - Fixed `!read()` exception propagation in copy_from (:ticket:`#412`). - Fixed possible NULL TZ decref (:ticket:`#424`). +- `~psycopg2.errorcodes` map updated to PostgreSQL 9.5. What's new in psycopg 2.6.1 diff --git a/lib/errorcodes.py b/lib/errorcodes.py index aa5a723c..60181c1c 100644 --- a/lib/errorcodes.py +++ b/lib/errorcodes.py @@ -199,6 +199,8 @@ INVALID_ESCAPE_SEQUENCE = '22025' STRING_DATA_LENGTH_MISMATCH = '22026' TRIM_ERROR = '22027' ARRAY_SUBSCRIPT_ERROR = '2202E' +INVALID_TABLESAMPLE_REPEAT = '2202G' +INVALID_TABLESAMPLE_ARGUMENT = '2202H' FLOATING_POINT_EXCEPTION = '22P01' INVALID_TEXT_REPRESENTATION = '22P02' INVALID_BINARY_REPRESENTATION = '22P03' @@ -271,6 +273,7 @@ INVALID_SQLSTATE_RETURNED = '39001' NULL_VALUE_NOT_ALLOWED = '39004' TRIGGER_PROTOCOL_VIOLATED = '39P01' SRF_PROTOCOL_VIOLATED = '39P02' +EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03' # Class 3B - Savepoint Exception SAVEPOINT_EXCEPTION = '3B000' @@ -408,6 +411,7 @@ PLPGSQL_ERROR = 'P0000' RAISE_EXCEPTION = 'P0001' NO_DATA_FOUND = 'P0002' TOO_MANY_ROWS = 'P0003' +ASSERT_FAILURE = 'P0004' # Class XX - Internal Error INTERNAL_ERROR = 'XX000' diff --git a/scripts/make_errorcodes.py b/scripts/make_errorcodes.py index 122e0d56..58d05b85 100755 --- a/scripts/make_errorcodes.py +++ b/scripts/make_errorcodes.py @@ -33,7 +33,7 @@ def main(): file_start = read_base_file(filename) # If you add a version to the list fix the docs (errorcodes.rst, err.rst) classes, errors = fetch_errors( - ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4']) + ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5']) f = open(filename, "w") for line in file_start: