pglib 5.11.0__tar.gz → 5.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {pglib-5.11.0/pglib.egg-info → pglib-5.12.0}/PKG-INFO +1 -1
  2. {pglib-5.11.0 → pglib-5.12.0/pglib.egg-info}/PKG-INFO +1 -1
  3. {pglib-5.11.0 → pglib-5.12.0}/pyproject.toml +1 -1
  4. {pglib-5.11.0 → pglib-5.12.0}/src/connection.cpp +108 -0
  5. {pglib-5.11.0 → pglib-5.12.0}/test/test_sync.py +33 -0
  6. {pglib-5.11.0 → pglib-5.12.0}/LICENSE +0 -0
  7. {pglib-5.11.0 → pglib-5.12.0}/MANIFEST.in +0 -0
  8. {pglib-5.11.0 → pglib-5.12.0}/README.rst +0 -0
  9. {pglib-5.11.0 → pglib-5.12.0}/pglib/__init__.py +0 -0
  10. {pglib-5.11.0 → pglib-5.12.0}/pglib/asyncpglib.py +0 -0
  11. {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/SOURCES.txt +0 -0
  12. {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/dependency_links.txt +0 -0
  13. {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/requires.txt +0 -0
  14. {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/top_level.txt +0 -0
  15. {pglib-5.11.0 → pglib-5.12.0}/setup.cfg +0 -0
  16. {pglib-5.11.0 → pglib-5.12.0}/setup.py +0 -0
  17. {pglib-5.11.0 → pglib-5.12.0}/src/byteswap.h +0 -0
  18. {pglib-5.11.0 → pglib-5.12.0}/src/connection.h +0 -0
  19. {pglib-5.11.0 → pglib-5.12.0}/src/conninfoopt.cpp +0 -0
  20. {pglib-5.11.0 → pglib-5.12.0}/src/conninfoopt.h +0 -0
  21. {pglib-5.11.0 → pglib-5.12.0}/src/datatypes.cpp +0 -0
  22. {pglib-5.11.0 → pglib-5.12.0}/src/datatypes.h +0 -0
  23. {pglib-5.11.0 → pglib-5.12.0}/src/debug.cpp +0 -0
  24. {pglib-5.11.0 → pglib-5.12.0}/src/debug.h +0 -0
  25. {pglib-5.11.0 → pglib-5.12.0}/src/enums.cpp +0 -0
  26. {pglib-5.11.0 → pglib-5.12.0}/src/enums.h +0 -0
  27. {pglib-5.11.0 → pglib-5.12.0}/src/errors.cpp +0 -0
  28. {pglib-5.11.0 → pglib-5.12.0}/src/errors.h +0 -0
  29. {pglib-5.11.0 → pglib-5.12.0}/src/getdata.cpp +0 -0
  30. {pglib-5.11.0 → pglib-5.12.0}/src/getdata.h +0 -0
  31. {pglib-5.11.0 → pglib-5.12.0}/src/juliandate.cpp +0 -0
  32. {pglib-5.11.0 → pglib-5.12.0}/src/juliandate.h +0 -0
  33. {pglib-5.11.0 → pglib-5.12.0}/src/params.cpp +0 -0
  34. {pglib-5.11.0 → pglib-5.12.0}/src/params.h +0 -0
  35. {pglib-5.11.0 → pglib-5.12.0}/src/pgarrays.cpp +0 -0
  36. {pglib-5.11.0 → pglib-5.12.0}/src/pgarrays.h +0 -0
  37. {pglib-5.11.0 → pglib-5.12.0}/src/pglib.cpp +0 -0
  38. {pglib-5.11.0 → pglib-5.12.0}/src/pglib.h +0 -0
  39. {pglib-5.11.0 → pglib-5.12.0}/src/pgtypes.h +0 -0
  40. {pglib-5.11.0 → pglib-5.12.0}/src/resultset.cpp +0 -0
  41. {pglib-5.11.0 → pglib-5.12.0}/src/resultset.h +0 -0
  42. {pglib-5.11.0 → pglib-5.12.0}/src/row.cpp +0 -0
  43. {pglib-5.11.0 → pglib-5.12.0}/src/row.h +0 -0
  44. {pglib-5.11.0 → pglib-5.12.0}/src/runtime.cpp +0 -0
  45. {pglib-5.11.0 → pglib-5.12.0}/src/runtime.h +0 -0
  46. {pglib-5.11.0 → pglib-5.12.0}/src/type_hstore.cpp +0 -0
  47. {pglib-5.11.0 → pglib-5.12.0}/src/type_hstore.h +0 -0
  48. {pglib-5.11.0 → pglib-5.12.0}/src/type_json.cpp +0 -0
  49. {pglib-5.11.0 → pglib-5.12.0}/src/type_json.h +0 -0
  50. {pglib-5.11.0 → pglib-5.12.0}/src/type_ltree.cpp +0 -0
  51. {pglib-5.11.0 → pglib-5.12.0}/src/type_ltree.h +0 -0
  52. {pglib-5.11.0 → pglib-5.12.0}/test/test_async.py +0 -0
  53. {pglib-5.11.0 → pglib-5.12.0}/test/testutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pglib
3
- Version: 5.11.0
3
+ Version: 5.12.0
4
4
  Summary: A PostgreSQL interface
5
5
  Author-email: Michael Kleehammer <michael@kleehammer.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pglib
3
- Version: 5.11.0
3
+ Version: 5.12.0
4
4
  Summary: A PostgreSQL interface
5
5
  Author-email: Michael Kleehammer <michael@kleehammer.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pglib"
7
- version = "5.11.0"
7
+ version = "5.12.0"
8
8
  description = "A PostgreSQL interface"
9
9
  readme = "README.rst"
10
10
  license = "MIT"
@@ -365,6 +365,113 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
365
365
  }
366
366
 
367
367
 
368
+ const char* doc_copy_to =
369
+ "Connection.copy_to(command, dest) --> int\n"
370
+ "\n"
371
+ "Executes the given COPY TO command and returns the number of records copied.\n"
372
+ "\n"
373
+ "command\n"
374
+ " The copy command which must be 'to stdout'.\n"
375
+ "\n"
376
+ "dest\n"
377
+ " The file-like object to write to. Strings will be written, not bytes, so\n"
378
+ " open in text mode.\n"
379
+ "\n"
380
+ "Examples:\n"
381
+ " cnxn.copy_to('copy t1 to stdout csv header', open('test.csv', 'w'))\n"
382
+ " cnxn.copy_to('copy t1(a, b, c) to stdout csv header', open('test.csv', 'w'))\n"
383
+ " cnxn.copy_to('copy (select * from t1 where id > 10) to stdout csv', f)\n";
384
+
385
+ static PyObject* Connection_copy_to(PyObject* self, PyObject* args)
386
+ {
387
+ PyObject* command;
388
+ PyObject* dest;
389
+ if (!PyArg_ParseTuple(args, "UO", &command, &dest))
390
+ return 0;
391
+
392
+ Connection* cnxn = CastConnection(self, REQUIRE_OPEN);
393
+ if (!cnxn)
394
+ return 0;
395
+
396
+ if (!PyObject_HasAttrString(dest, "write"))
397
+ return PyErr_Format(Error, "Destination must be a file-like object.");
398
+ Object write_method(PyObject_GetAttrString(dest, "write"));
399
+
400
+ const char* szSQL = PyUnicode_AsUTF8(command);
401
+ if (!szSQL)
402
+ return 0;
403
+
404
+ ResultHolder result;
405
+ Py_BEGIN_ALLOW_THREADS
406
+ result = PQexec(cnxn->pgconn, szSQL);
407
+ Py_END_ALLOW_THREADS
408
+
409
+ if (result == 0)
410
+ return 0;
411
+
412
+ switch (PQresultStatus(result)) {
413
+ case PGRES_COPY_OUT:
414
+ // This is what we are expecting.
415
+ break;
416
+
417
+ case PGRES_BAD_RESPONSE:
418
+ case PGRES_NONFATAL_ERROR:
419
+ case PGRES_FATAL_ERROR:
420
+ return SetResultError(result.Detach());
421
+
422
+ default:
423
+ return PyErr_Format(Error, "Result was not PGRES_COPY_OUT: %d", (int)PQresultStatus(result));
424
+ }
425
+
426
+
427
+ for (;;) {
428
+ int cb = 0;
429
+ char* buffer;
430
+ Py_BEGIN_ALLOW_THREADS
431
+ cb = PQgetCopyData(cnxn->pgconn, &buffer, 0);
432
+ Py_END_ALLOW_THREADS
433
+
434
+ if (cb == -2) {
435
+ return SetResultError(result.Detach());
436
+ }
437
+
438
+ if (cb == -1) {
439
+ // The copy is complete.
440
+ break;
441
+ }
442
+
443
+ // We have a buffer of byte data. We have the length (`cb`), but the libpq docs say
444
+ // that the string is also zero terminated, so we're going to try not calling 'write'.
445
+
446
+ int err = PyFile_WriteString(buffer, dest);
447
+
448
+ PQfreemem(buffer);
449
+ if (err) {
450
+ return 0;
451
+ }
452
+ }
453
+
454
+ // After a copy, you have to get another result to know if it was successful.
455
+
456
+ ResultHolder final_result;
457
+ ExecStatusType status = PGRES_COMMAND_OK;
458
+ Py_BEGIN_ALLOW_THREADS
459
+ final_result = PQgetResult(cnxn->pgconn);
460
+ status = PQresultStatus(final_result);
461
+ Py_END_ALLOW_THREADS
462
+
463
+ if (status != PGRES_COMMAND_OK) {
464
+ // SetResultError will take ownership of `result`.
465
+ return SetResultError(final_result.Detach());
466
+ }
467
+
468
+ const char* sz = PQcmdTuples(final_result);
469
+ if (sz == 0 || *sz == 0)
470
+ Py_RETURN_NONE;
471
+ return PyLong_FromLong(atoi(sz));
472
+ }
473
+
474
+
368
475
  const char* doc_copy_to_csv =
369
476
  "Connection.copy_to_csv(table, dest, header=0, delimiter=',', quote='\"')\n"
370
477
  "\n"
@@ -1631,6 +1738,7 @@ static struct PyMethodDef Connection_methods[] =
1631
1738
  { "script", Connection_script, METH_VARARGS, doc_script },
1632
1739
  { "copy_from", (PyCFunction) Connection_copy_from, METH_VARARGS | METH_KEYWORDS, doc_copy_from },
1633
1740
  { "copy_from_csv", (PyCFunction) Connection_copy_from_csv, METH_VARARGS | METH_KEYWORDS, doc_copy_from_csv },
1741
+ { "copy_to", (PyCFunction) Connection_copy_to, METH_VARARGS, doc_copy_to },
1634
1742
  { "copy_to_csv", (PyCFunction) Connection_copy_to_csv, METH_VARARGS | METH_KEYWORDS, doc_copy_to_csv},
1635
1743
  { "begin", Connection_begin, METH_NOARGS, doc_begin },
1636
1744
  { "commit", Connection_commit, METH_NOARGS, doc_commit },
@@ -176,6 +176,39 @@ def test_copytocsv(cnxn):
176
176
  assert rows == [['a', 'b'], ['1', 'one'], ['2', 'two'], ['3', 'three']]
177
177
 
178
178
 
179
+ def test_copy_to(cnxn):
180
+ # Test copy_to with a full command, including a subquery.
181
+ cnxn.execute("create table t1(a int, b text)")
182
+ cnxn.execute("insert into t1 values (1, 'one'), (2, 'two'), (3, 'three')")
183
+
184
+ with tempfile.NamedTemporaryFile(mode='w', encoding='utf8') as tf:
185
+ # Use a subquery to only copy rows where a > 1
186
+ count = cnxn.copy_to('copy (select * from t1 where a > 1 order by a) to stdout csv header', tf)
187
+ assert count == 2
188
+
189
+ tf.flush()
190
+ with open(tf.name, mode='r', encoding='utf8') as fd:
191
+ reader = csv.reader(fd)
192
+ rows = list(reader)
193
+ assert rows == [['a', 'b'], ['2', 'two'], ['3', 'three']]
194
+
195
+
196
+ def test_copy_to_table(cnxn):
197
+ # Test copy_to with a simple table copy (no subquery).
198
+ cnxn.execute("create table t1(a int, b text)")
199
+ cnxn.execute("insert into t1 values (1, 'one'), (2, 'two')")
200
+
201
+ with tempfile.NamedTemporaryFile(mode='w', encoding='utf8') as tf:
202
+ count = cnxn.copy_to('copy t1 to stdout csv', tf)
203
+ assert count == 2
204
+
205
+ tf.flush()
206
+ with open(tf.name, mode='r', encoding='utf8') as fd:
207
+ reader = csv.reader(fd)
208
+ rows = list(reader)
209
+ assert rows == [['1', 'one'], ['2', 'two']]
210
+
211
+
179
212
  #
180
213
  # copy from
181
214
  #
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes