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.
- {pglib-5.11.0/pglib.egg-info → pglib-5.12.0}/PKG-INFO +1 -1
- {pglib-5.11.0 → pglib-5.12.0/pglib.egg-info}/PKG-INFO +1 -1
- {pglib-5.11.0 → pglib-5.12.0}/pyproject.toml +1 -1
- {pglib-5.11.0 → pglib-5.12.0}/src/connection.cpp +108 -0
- {pglib-5.11.0 → pglib-5.12.0}/test/test_sync.py +33 -0
- {pglib-5.11.0 → pglib-5.12.0}/LICENSE +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/MANIFEST.in +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/README.rst +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/pglib/__init__.py +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/pglib/asyncpglib.py +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/SOURCES.txt +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/dependency_links.txt +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/requires.txt +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/pglib.egg-info/top_level.txt +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/setup.cfg +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/setup.py +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/byteswap.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/connection.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/conninfoopt.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/conninfoopt.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/datatypes.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/datatypes.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/debug.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/debug.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/enums.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/enums.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/errors.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/errors.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/getdata.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/getdata.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/juliandate.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/juliandate.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/params.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/params.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/pgarrays.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/pgarrays.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/pglib.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/pglib.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/pgtypes.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/resultset.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/resultset.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/row.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/row.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/runtime.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/runtime.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/type_hstore.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/type_hstore.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/type_json.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/type_json.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/type_ltree.cpp +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/src/type_ltree.h +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/test/test_async.py +0 -0
- {pglib-5.11.0 → pglib-5.12.0}/test/testutils.py +0 -0
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|