pglib 5.11.0__tar.gz → 5.13.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.13.0}/PKG-INFO +4 -6
- {pglib-5.11.0 → pglib-5.13.0/pglib.egg-info}/PKG-INFO +4 -6
- {pglib-5.11.0 → pglib-5.13.0}/pyproject.toml +2 -3
- {pglib-5.11.0 → pglib-5.13.0}/src/connection.cpp +130 -16
- {pglib-5.11.0 → pglib-5.13.0}/src/errors.cpp +9 -2
- {pglib-5.11.0 → pglib-5.13.0}/test/test_sync.py +78 -4
- {pglib-5.11.0 → pglib-5.13.0}/LICENSE +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/MANIFEST.in +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/README.rst +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/pglib/__init__.py +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/pglib/asyncpglib.py +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/pglib.egg-info/SOURCES.txt +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/pglib.egg-info/dependency_links.txt +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/pglib.egg-info/requires.txt +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/pglib.egg-info/top_level.txt +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/setup.cfg +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/setup.py +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/byteswap.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/connection.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/conninfoopt.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/conninfoopt.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/datatypes.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/datatypes.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/debug.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/debug.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/enums.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/enums.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/errors.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/getdata.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/getdata.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/juliandate.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/juliandate.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/params.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/params.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/pgarrays.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/pgarrays.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/pglib.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/pglib.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/pgtypes.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/resultset.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/resultset.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/row.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/row.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/runtime.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/runtime.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/type_hstore.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/type_hstore.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/type_json.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/type_json.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/type_ltree.cpp +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/src/type_ltree.h +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/test/test_async.py +0 -0
- {pglib-5.11.0 → pglib-5.13.0}/test/testutils.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: pglib
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.13.0
|
|
4
4
|
Summary: A PostgreSQL interface
|
|
5
5
|
Author-email: Michael Kleehammer <michael@kleehammer.com>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://gitlab.com/mkleehammer/pglib
|
|
8
8
|
Project-URL: Repository, https://gitlab.com/mkleehammer/pglib
|
|
9
9
|
Keywords: postgresql,postgres
|
|
@@ -17,10 +17,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/x-rst
|
|
20
|
-
License-File: LICENSE
|
|
21
20
|
Provides-Extra: test
|
|
22
|
-
|
|
23
|
-
Dynamic: license-file
|
|
21
|
+
License-File: LICENSE
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
pglib is a Python 3.10+ module for working with PostgreSQL databases. It is a C extension that
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: pglib
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.13.0
|
|
4
4
|
Summary: A PostgreSQL interface
|
|
5
5
|
Author-email: Michael Kleehammer <michael@kleehammer.com>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://gitlab.com/mkleehammer/pglib
|
|
8
8
|
Project-URL: Repository, https://gitlab.com/mkleehammer/pglib
|
|
9
9
|
Keywords: postgresql,postgres
|
|
@@ -17,10 +17,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/x-rst
|
|
20
|
-
License-File: LICENSE
|
|
21
20
|
Provides-Extra: test
|
|
22
|
-
|
|
23
|
-
Dynamic: license-file
|
|
21
|
+
License-File: LICENSE
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
pglib is a Python 3.10+ module for working with PostgreSQL databases. It is a C extension that
|
|
@@ -4,11 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pglib"
|
|
7
|
-
version = "5.
|
|
7
|
+
version = "5.13.0"
|
|
8
8
|
description = "A PostgreSQL interface"
|
|
9
9
|
readme = "README.rst"
|
|
10
|
-
license = "MIT"
|
|
11
|
-
license-files = ["LICENSE"]
|
|
10
|
+
license = {text = "MIT"}
|
|
12
11
|
requires-python = ">=3.10"
|
|
13
12
|
authors = [
|
|
14
13
|
{name = "Michael Kleehammer", email = "michael@kleehammer.com"}
|
|
@@ -170,8 +170,8 @@ static PGresult* internal_execute(PyObject* self, PyObject* args)
|
|
|
170
170
|
|
|
171
171
|
if (result == 0)
|
|
172
172
|
{
|
|
173
|
-
//
|
|
174
|
-
|
|
173
|
+
// This happens for out-of-memory or inability to send the command to the server.
|
|
174
|
+
SetConnectionError(cnxn);
|
|
175
175
|
return 0;
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -197,8 +197,8 @@ static PyObject* Connection_script(PyObject* self, PyObject* args)
|
|
|
197
197
|
return 0;
|
|
198
198
|
|
|
199
199
|
ResultHolder result = PQexec(cnxn->pgconn, szScript);
|
|
200
|
-
if (result == 0)
|
|
201
|
-
return
|
|
200
|
+
if (result.p == 0)
|
|
201
|
+
return SetConnectionError(cnxn);
|
|
202
202
|
|
|
203
203
|
switch (PQresultStatus(result)) {
|
|
204
204
|
case PGRES_BAD_RESPONSE:
|
|
@@ -271,7 +271,7 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
|
|
|
271
271
|
Py_END_ALLOW_THREADS
|
|
272
272
|
|
|
273
273
|
if (result == 0)
|
|
274
|
-
return
|
|
274
|
+
return SetConnectionError(cnxn);
|
|
275
275
|
|
|
276
276
|
switch (PQresultStatus(result)) {
|
|
277
277
|
case PGRES_COPY_IN:
|
|
@@ -365,6 +365,112 @@ 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 SetConnectionError(cnxn);
|
|
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 SetConnectionError(cnxn);
|
|
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
|
+
return SetResultError(final_result.Detach());
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const char* sz = PQcmdTuples(final_result);
|
|
468
|
+
if (sz == 0 || *sz == 0)
|
|
469
|
+
Py_RETURN_NONE;
|
|
470
|
+
return PyLong_FromLong(atoi(sz));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
368
474
|
const char* doc_copy_to_csv =
|
|
369
475
|
"Connection.copy_to_csv(table, dest, header=0, delimiter=',', quote='\"')\n"
|
|
370
476
|
"\n"
|
|
@@ -435,7 +541,7 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
435
541
|
Py_END_ALLOW_THREADS
|
|
436
542
|
|
|
437
543
|
if (result == 0)
|
|
438
|
-
return
|
|
544
|
+
return SetConnectionError(cnxn);
|
|
439
545
|
|
|
440
546
|
switch (PQresultStatus(result)) {
|
|
441
547
|
case PGRES_COPY_OUT:
|
|
@@ -460,7 +566,7 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
460
566
|
Py_END_ALLOW_THREADS
|
|
461
567
|
|
|
462
568
|
if (cb == -2) {
|
|
463
|
-
return
|
|
569
|
+
return SetConnectionError(cnxn);
|
|
464
570
|
}
|
|
465
571
|
|
|
466
572
|
if (cb == -1) {
|
|
@@ -473,10 +579,6 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
473
579
|
|
|
474
580
|
int err = PyFile_WriteString(buffer, dest);
|
|
475
581
|
|
|
476
|
-
// while (cb > 0) {
|
|
477
|
-
// PyObject* res = PyObject_CallObject(write_method)
|
|
478
|
-
// }
|
|
479
|
-
|
|
480
582
|
PQfreemem(buffer);
|
|
481
583
|
if (err) {
|
|
482
584
|
return 0;
|
|
@@ -493,7 +595,6 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
493
595
|
Py_END_ALLOW_THREADS
|
|
494
596
|
|
|
495
597
|
if (status != PGRES_COMMAND_OK) {
|
|
496
|
-
// SetResultError will take ownership of `result`.
|
|
497
598
|
return SetResultError(final_result.Detach());
|
|
498
599
|
}
|
|
499
600
|
|
|
@@ -578,7 +679,7 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
|
|
|
578
679
|
Py_END_ALLOW_THREADS
|
|
579
680
|
|
|
580
681
|
if (result == 0)
|
|
581
|
-
return
|
|
682
|
+
return SetConnectionError(cnxn);
|
|
582
683
|
|
|
583
684
|
switch (PQresultStatus(result)) {
|
|
584
685
|
case PGRES_COPY_IN:
|
|
@@ -941,13 +1042,17 @@ static PyObject* Connection_begin(PyObject* self, PyObject* args)
|
|
|
941
1042
|
if (txnstatus == PQTRANS_IDLE)
|
|
942
1043
|
{
|
|
943
1044
|
result = PQexec(cnxn->pgconn, "BEGIN");
|
|
944
|
-
|
|
1045
|
+
if (result)
|
|
1046
|
+
status = PQresultStatus(result);
|
|
945
1047
|
}
|
|
946
1048
|
Py_END_ALLOW_THREADS
|
|
947
1049
|
|
|
948
1050
|
if (txnstatus != PQTRANS_IDLE)
|
|
949
1051
|
return PyErr_Format(Error, "Connection transaction status is not idle: %s", NameFromTxnFlag(txnstatus));
|
|
950
1052
|
|
|
1053
|
+
if (result == 0)
|
|
1054
|
+
return SetConnectionError(cnxn);
|
|
1055
|
+
|
|
951
1056
|
if (status != PGRES_COMMAND_OK)
|
|
952
1057
|
return SetResultError(result);
|
|
953
1058
|
|
|
@@ -974,13 +1079,17 @@ static PyObject* Connection_commit(PyObject* self, PyObject* args)
|
|
|
974
1079
|
if (txnstatus == PQTRANS_INTRANS)
|
|
975
1080
|
{
|
|
976
1081
|
result = PQexec(cnxn->pgconn, "COMMIT");
|
|
977
|
-
|
|
1082
|
+
if (result)
|
|
1083
|
+
status = PQresultStatus(result);
|
|
978
1084
|
}
|
|
979
1085
|
Py_END_ALLOW_THREADS
|
|
980
1086
|
|
|
981
1087
|
if (txnstatus != PQTRANS_IDLE && txnstatus != PQTRANS_INTRANS)
|
|
982
1088
|
return PyErr_Format(Error, "Connection transaction status is invalid: %s", NameFromTxnFlag(txnstatus));
|
|
983
1089
|
|
|
1090
|
+
if (txnstatus == PQTRANS_INTRANS && result == 0)
|
|
1091
|
+
return SetConnectionError(cnxn);
|
|
1092
|
+
|
|
984
1093
|
if (status != PGRES_COMMAND_OK)
|
|
985
1094
|
return SetResultError(result);
|
|
986
1095
|
|
|
@@ -1003,9 +1112,13 @@ static PyObject* Connection_rollback(PyObject* self, PyObject* args)
|
|
|
1003
1112
|
|
|
1004
1113
|
Py_BEGIN_ALLOW_THREADS
|
|
1005
1114
|
result = PQexec(cnxn->pgconn, "ROLLBACK");
|
|
1006
|
-
|
|
1115
|
+
if (result)
|
|
1116
|
+
status = PQresultStatus(result);
|
|
1007
1117
|
Py_END_ALLOW_THREADS
|
|
1008
1118
|
|
|
1119
|
+
if (result == 0)
|
|
1120
|
+
return SetConnectionError(cnxn);
|
|
1121
|
+
|
|
1009
1122
|
if (status != PGRES_COMMAND_OK)
|
|
1010
1123
|
return SetResultError(result);
|
|
1011
1124
|
|
|
@@ -1631,6 +1744,7 @@ static struct PyMethodDef Connection_methods[] =
|
|
|
1631
1744
|
{ "script", Connection_script, METH_VARARGS, doc_script },
|
|
1632
1745
|
{ "copy_from", (PyCFunction) Connection_copy_from, METH_VARARGS | METH_KEYWORDS, doc_copy_from },
|
|
1633
1746
|
{ "copy_from_csv", (PyCFunction) Connection_copy_from_csv, METH_VARARGS | METH_KEYWORDS, doc_copy_from_csv },
|
|
1747
|
+
{ "copy_to", (PyCFunction) Connection_copy_to, METH_VARARGS, doc_copy_to },
|
|
1634
1748
|
{ "copy_to_csv", (PyCFunction) Connection_copy_to_csv, METH_VARARGS | METH_KEYWORDS, doc_copy_to_csv},
|
|
1635
1749
|
{ "begin", Connection_begin, METH_NOARGS, doc_begin },
|
|
1636
1750
|
{ "commit", Connection_commit, METH_NOARGS, doc_commit },
|
|
@@ -29,6 +29,8 @@ static const ResultErrorField errorFields[] =
|
|
|
29
29
|
PyObject* SetConnectionError(PGconn* pgconn)
|
|
30
30
|
{
|
|
31
31
|
const char* szMessage = PQerrorMessage(pgconn);
|
|
32
|
+
if (szMessage == 0 || szMessage[0] == 0)
|
|
33
|
+
szMessage = "connection error (no details available)";
|
|
32
34
|
PyErr_SetString(Error, szMessage);
|
|
33
35
|
return 0;
|
|
34
36
|
}
|
|
@@ -63,8 +65,12 @@ PyObject* SetResultError(PGresult* r)
|
|
|
63
65
|
|
|
64
66
|
const char* szMessage = PQresultErrorMessage(result);
|
|
65
67
|
const char* szSQLSTATE = PQresultErrorField(result, PG_DIAG_SQLSTATE);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
|
|
69
|
+
// Handle cases where error info isn't available (e.g., connection terminated)
|
|
70
|
+
if (!szMessage || szMessage[0] == 0)
|
|
71
|
+
szMessage = "unknown error";
|
|
72
|
+
if (!szSQLSTATE)
|
|
73
|
+
szSQLSTATE = "?????"; // Unknown SQLSTATE
|
|
68
74
|
|
|
69
75
|
Object msg(PyUnicode_FromFormat("[%s] %s", szSQLSTATE, szMessage));
|
|
70
76
|
if (!msg)
|
|
@@ -96,6 +102,7 @@ PyObject* SetResultError(PGresult* r)
|
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
PyErr_SetObject(Error, error);
|
|
105
|
+
Py_DECREF(error);
|
|
99
106
|
|
|
100
107
|
return 0;
|
|
101
108
|
}
|
|
@@ -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
|
#
|
|
@@ -490,16 +523,20 @@ def test_money(cnxn):
|
|
|
490
523
|
# parses the values we pass as strings before we even get to pglib.
|
|
491
524
|
|
|
492
525
|
|
|
493
|
-
@pytest.mark.skip('LC_ALL locale does not exist')
|
|
494
526
|
def test_decimal_german(cnxn):
|
|
495
|
-
|
|
527
|
+
try:
|
|
528
|
+
locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
|
|
529
|
+
except locale.Error:
|
|
530
|
+
pytest.skip('de_DE.utf8 locale not available')
|
|
496
531
|
values = [Decimal(s) for s in ['-3.0000000', '123456.7890']]
|
|
497
532
|
_test_type(cnxn, 'decimal(100,7)', values)
|
|
498
533
|
|
|
499
534
|
|
|
500
|
-
@pytest.mark.skip('LC_ALL locale does not exist')
|
|
501
535
|
def test_money_german(cnxn):
|
|
502
|
-
|
|
536
|
+
try:
|
|
537
|
+
locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
|
|
538
|
+
except locale.Error:
|
|
539
|
+
pytest.skip('de_DE.utf8 locale not available')
|
|
503
540
|
values = [Decimal(s) for s in ['1.23', '0.0', '123.45', '-12.34']]
|
|
504
541
|
_test_type(cnxn, 'money', values, check_comparison=False)
|
|
505
542
|
|
|
@@ -1275,3 +1312,40 @@ def test_large_insert(cnxn):
|
|
|
1275
1312
|
query = (Path(__file__).parent / 'large-insert.sql').read_text(encoding='utf8')
|
|
1276
1313
|
print('-' * 40)
|
|
1277
1314
|
cnxn.execute(query)
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
def test_terminated_connection_error_message():
|
|
1318
|
+
"""
|
|
1319
|
+
Verify that when a connection is terminated, we get a meaningful error
|
|
1320
|
+
message from libpq instead of a generic "Fatal error".
|
|
1321
|
+
|
|
1322
|
+
This tests the fix for the case where PQexecParams returns NULL.
|
|
1323
|
+
"""
|
|
1324
|
+
# Create the connection we'll terminate
|
|
1325
|
+
target = pglib.connect(CONNINFO)
|
|
1326
|
+
target_pid = target.pid
|
|
1327
|
+
|
|
1328
|
+
# Use a separate connection to terminate the target
|
|
1329
|
+
killer = pglib.connect(CONNINFO)
|
|
1330
|
+
killer.execute("select pg_terminate_backend($1)", target_pid)
|
|
1331
|
+
killer.close()
|
|
1332
|
+
|
|
1333
|
+
# Give PostgreSQL a moment to process the termination
|
|
1334
|
+
sleep(0.1)
|
|
1335
|
+
|
|
1336
|
+
# Now try to execute on the terminated connection
|
|
1337
|
+
with pytest.raises(pglib.Error) as exc_info:
|
|
1338
|
+
target.execute("select 1")
|
|
1339
|
+
|
|
1340
|
+
# The error message should NOT be the generic "Fatal error"
|
|
1341
|
+
# It should contain something meaningful from libpq
|
|
1342
|
+
error_msg = str(exc_info.value)
|
|
1343
|
+
assert "Fatal error" not in error_msg, \
|
|
1344
|
+
f"Expected meaningful error message, got generic 'Fatal error': {error_msg}"
|
|
1345
|
+
|
|
1346
|
+
# Should contain some indication of connection issue
|
|
1347
|
+
assert any(phrase in error_msg.lower() for phrase in
|
|
1348
|
+
["terminat", "connection", "server", "closed"]), \
|
|
1349
|
+
f"Error message doesn't indicate connection problem: {error_msg}"
|
|
1350
|
+
|
|
1351
|
+
target.close()
|
|
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
|