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 <[email protected]>
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
[email protected]
https://mail.vex.net/mailman/listinfo/pygresql