On Sat, Jun 20, 2020 at 01:10:14AM +0200, Christoph Zwerschke wrote: > Am 20.06.2020 um 00:54 schrieb Justin Pryzby: > > I think these would be easy to add. > > > > https://github.com/PyGreSQL/PyGreSQL/issues/29 > > The pgquery type should have a method listtypes() #29 > > I sent a patch for this to the list last year > > > > https://github.com/PyGreSQL/PyGreSQL/issues/24 > > Allow inserttable() to take columns as parameter #24 > > This is simple enough I can probably put something together > > Ok, I will look into these two issues tomorrow.
1) This is actually possible if one passes a column list in the "table" argument: |d.inserttable('t(j)', ([2],)) This works by abusing the "table" argument, which isn't being escaped - That should be addressed in a separate commit. 2) I think these should all be factored out of all their callers in the C module: PyBytes_FromStringAndSize() / get_decoded_string(). 3) I implemented an interface like: |d.inserttable('t', ([2],), ['col1', 'col2']) I'm attaching my WIP patch. It will needs to be cleaned up, since (1) and (2) will surely cause rebase conflicts, so this is just a POC. 4) Note that this changelog item is under PGDB but should be under PG: "The `types` parameter of `format_query` can now be passed as a string..." -- Justin
>From 1dfd3ba134d43f3464ddee4b4cde37e06af2acb7 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Fri, 19 Jun 2020 19:51:58 -0500 Subject: [PATCH] WIP/POC: add an argument to inserttable() accepting optional list of column names --- docs/contents/changelog.rst | 10 ++++--- docs/contents/pg/connection.rst | 4 ++- pgconn.c | 51 ++++++++++++++++++++++++++++---- tests/test_classic_connection.py | 13 ++++++++ 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index eaafb88..b821a89 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -14,16 +14,18 @@ Version 5.2 (to be released) Additional connection parameter ``nowait``, and connection methods `send_query()`, `poll()`, `set_non_blocking()`, `is_non_blocking()`. Generously contributed by Patrick TJ McPhee (#19). + - The `types` parameter of `format_query` can now be passed as a string + that will be split on whitespace when values are passed as a sequence, + and the types can now also be specified using actual Python types + instead of type names. Suggested by Justin Pryzby (#38). + - The `inserttable()` now accepts an optional column list to be passed on + to the underlying SQL ``COPY`` command. - Changes to the DB-API 2 module (pgdb): - When using Python 2, errors are now derived from StandardError instead of Exception, as required by the DB-API 2 compliance test. - Connection arguments containing single quotes caused problems (reported and fixed by Tyler Ramer and Jamie McAtamney). - - The `types` parameter of `format_query` can now be passed as a string - that will be split on whitespace when values are passed as a sequence, - and the types can now also be specified using actual Python types - instead of type names. Suggested by Justin Pryzby (#38). Version 5.1.2 (2020-04-19) -------------------------- diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst index f342c02..acf477c 100644 --- a/docs/contents/pg/connection.rst +++ b/docs/contents/pg/connection.rst @@ -487,12 +487,13 @@ first, otherwise :meth:`Connection.getnotify` will always return ``None``. inserttable -- insert a list into a table ----------------------------------------- -.. method:: Connection.inserttable(table, values) +.. method:: Connection.inserttable(table, values, [columns]) Insert a Python list into a database table :param str table: the table name :param list values: list of rows values + :param list columns: list of column names :rtype: None :raises TypeError: invalid connection, bad argument type, or too many arguments :raises MemoryError: insert buffer could not be allocated @@ -503,6 +504,7 @@ It inserts the whole values list into the given table. Internally, it uses the COPY command of the PostgreSQL database. The list is a list of tuples/lists that define the values for each inserted row. The rows values may contain string, integer, long or double (real) values. +columns is an iterable over column names to be passed to the ``COPY`` command. .. warning:: diff --git a/pgconn.c b/pgconn.c index f8c81e2..b7452e4 100644 --- a/pgconn.c +++ b/pgconn.c @@ -686,13 +686,14 @@ conn_is_non_blocking(connObject *self, PyObject *args) /* Insert table */ static char conn_inserttable__doc__[] = -"inserttable(table, data) -- insert list into table\n\n" -"The fields in the list must be in the same order as in the table.\n"; +"inserttable(table, data, [columns]) -- insert list into table\n\n" +"The fields in the list must be in the same order as in the list of columns (if specified) or table.\n"; static PyObject * conn_inserttable(connObject *self, PyObject *args) { PGresult *result; + PyObject *column_obj = NULL; char *table, *buffer, *bufpt; int encoding; size_t bufsiz; @@ -707,7 +708,7 @@ conn_inserttable(connObject *self, PyObject *args) } /* gets arguments */ - if (!PyArg_ParseTuple(args, "sO:filter", &table, &list)) { + if (!PyArg_ParseTuple(args, "sO|O:FiLtEr", &table, &list, &column_obj)) { PyErr_SetString( PyExc_TypeError, "Method inserttable() expects a string and a list as arguments"); @@ -731,12 +732,52 @@ conn_inserttable(connObject *self, PyObject *args) return NULL; } + /* If column_obj is passed, ensure it's a non-empty tuple. */ + if (column_obj) { + if (!PyList_Check(column_obj) && + !PyTuple_Check(list)) { + PyErr_SetString( + PyExc_TypeError, + "Method inserttable() expects a list or a tuple" + " as third argument"); + return NULL; + } + + column_obj = PySequence_Fast( + column_obj, "Method inserttable() expects an iterable as third argument"); + if (!column_obj) { + // XXX Py_XDECREF(); + return NULL; + } + } + /* allocate buffer */ if (!(buffer = PyMem_Malloc(MAX_BUFFER_SIZE))) return PyErr_NoMemory(); + encoding = PQclientEncoding(self->cnx); + + table = PQescapeIdentifier(self->cnx, table, strlen(table)); + /* starts query */ - sprintf(buffer, "copy %s from stdin", table); + if (!column_obj) + sprintf(buffer, "copy %s from stdin", table); + else { + /* Adds a string like '({})'.format(', '.join(columns)) */ + int len = (int) PySequence_Fast_GET_SIZE(column_obj); + sprintf(buffer, "copy %s(", table); + for (int i=0; i<len; ++i) { + // XXX: need to test this is a TUPLE or LIST (and not a STRING) + PyObject *obj = PySequence_Fast_GET_ITEM(column_obj, i); + PyObject *str_obj = get_encoded_string(obj, encoding); + ssize_t slen; + char *col; + PyBytes_AsStringAndSize(str_obj, &col, &slen); + col = PQescapeIdentifier(self->cnx, col, (size_t) slen); + sprintf(buffer+strlen(buffer), "%s%s", col, i<len-1 ? ", " : ""); + } + sprintf(buffer+strlen(buffer), ") from stdin"); + } Py_BEGIN_ALLOW_THREADS result = PQexec(self->cnx, buffer); @@ -748,8 +789,6 @@ conn_inserttable(connObject *self, PyObject *args) return NULL; } - encoding = PQclientEncoding(self->cnx); - PQclear(result); n = 0; /* not strictly necessary but avoids warning */ diff --git a/tests/test_classic_connection.py b/tests/test_classic_connection.py index c1f265e..935f2d1 100755 --- a/tests/test_classic_connection.py +++ b/tests/test_classic_connection.py @@ -1889,6 +1889,19 @@ class TestInserttable(unittest.TestCase): self.c.inserttable('test', data) self.assertEqual(self.get_back(), data) + def testInserttableMissingValues(self): + datain = [(11,)] * 100 + dataout = [tuple([11 if i==2 else None for i in range(1,15)])] * 100 + self.c.inserttable('test', datain, ['i4']) + self.assertEqual(self.get_back(), dataout) + +# This test passes if the table name is not escaped... + def testInserttableMissingValues2(self): + datain = [(11,)] * 100 + dataout = [tuple([11 if i==2 else None for i in range(1,15)])] * 100 + self.c.inserttable('test(i4)', datain) + self.assertEqual(self.get_back(), dataout) + def testInserttableMaxValues(self): data = [(2 ** 15 - 1, int(2 ** 31 - 1), long(2 ** 31 - 1), True, '2999-12-31', '11:59:59', 1e99, -- 2.17.0
_______________________________________________ PyGreSQL mailing list PyGreSQL@Vex.Net https://mail.vex.net/mailman/listinfo/pygresql