pglib 5.12.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.12.0/pglib.egg-info → pglib-5.13.0}/PKG-INFO +4 -6
- {pglib-5.12.0 → pglib-5.13.0/pglib.egg-info}/PKG-INFO +4 -6
- {pglib-5.12.0 → pglib-5.13.0}/pyproject.toml +2 -3
- {pglib-5.12.0 → pglib-5.13.0}/src/connection.cpp +25 -19
- {pglib-5.12.0 → pglib-5.13.0}/src/errors.cpp +9 -2
- {pglib-5.12.0 → pglib-5.13.0}/test/test_sync.py +45 -4
- {pglib-5.12.0 → pglib-5.13.0}/LICENSE +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/MANIFEST.in +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/README.rst +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/pglib/__init__.py +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/pglib/asyncpglib.py +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/pglib.egg-info/SOURCES.txt +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/pglib.egg-info/dependency_links.txt +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/pglib.egg-info/requires.txt +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/pglib.egg-info/top_level.txt +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/setup.cfg +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/setup.py +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/byteswap.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/connection.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/conninfoopt.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/conninfoopt.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/datatypes.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/datatypes.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/debug.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/debug.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/enums.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/enums.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/errors.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/getdata.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/getdata.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/juliandate.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/juliandate.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/params.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/params.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/pgarrays.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/pgarrays.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/pglib.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/pglib.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/pgtypes.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/resultset.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/resultset.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/row.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/row.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/runtime.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/runtime.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/type_hstore.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/type_hstore.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/type_json.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/type_json.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/type_ltree.cpp +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/src/type_ltree.h +0 -0
- {pglib-5.12.0 → pglib-5.13.0}/test/test_async.py +0 -0
- {pglib-5.12.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:
|
|
@@ -407,7 +407,7 @@ static PyObject* Connection_copy_to(PyObject* self, PyObject* args)
|
|
|
407
407
|
Py_END_ALLOW_THREADS
|
|
408
408
|
|
|
409
409
|
if (result == 0)
|
|
410
|
-
return
|
|
410
|
+
return SetConnectionError(cnxn);
|
|
411
411
|
|
|
412
412
|
switch (PQresultStatus(result)) {
|
|
413
413
|
case PGRES_COPY_OUT:
|
|
@@ -432,7 +432,7 @@ static PyObject* Connection_copy_to(PyObject* self, PyObject* args)
|
|
|
432
432
|
Py_END_ALLOW_THREADS
|
|
433
433
|
|
|
434
434
|
if (cb == -2) {
|
|
435
|
-
return
|
|
435
|
+
return SetConnectionError(cnxn);
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
if (cb == -1) {
|
|
@@ -461,7 +461,6 @@ static PyObject* Connection_copy_to(PyObject* self, PyObject* args)
|
|
|
461
461
|
Py_END_ALLOW_THREADS
|
|
462
462
|
|
|
463
463
|
if (status != PGRES_COMMAND_OK) {
|
|
464
|
-
// SetResultError will take ownership of `result`.
|
|
465
464
|
return SetResultError(final_result.Detach());
|
|
466
465
|
}
|
|
467
466
|
|
|
@@ -542,7 +541,7 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
542
541
|
Py_END_ALLOW_THREADS
|
|
543
542
|
|
|
544
543
|
if (result == 0)
|
|
545
|
-
return
|
|
544
|
+
return SetConnectionError(cnxn);
|
|
546
545
|
|
|
547
546
|
switch (PQresultStatus(result)) {
|
|
548
547
|
case PGRES_COPY_OUT:
|
|
@@ -567,7 +566,7 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
567
566
|
Py_END_ALLOW_THREADS
|
|
568
567
|
|
|
569
568
|
if (cb == -2) {
|
|
570
|
-
return
|
|
569
|
+
return SetConnectionError(cnxn);
|
|
571
570
|
}
|
|
572
571
|
|
|
573
572
|
if (cb == -1) {
|
|
@@ -580,10 +579,6 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
580
579
|
|
|
581
580
|
int err = PyFile_WriteString(buffer, dest);
|
|
582
581
|
|
|
583
|
-
// while (cb > 0) {
|
|
584
|
-
// PyObject* res = PyObject_CallObject(write_method)
|
|
585
|
-
// }
|
|
586
|
-
|
|
587
582
|
PQfreemem(buffer);
|
|
588
583
|
if (err) {
|
|
589
584
|
return 0;
|
|
@@ -600,7 +595,6 @@ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject
|
|
|
600
595
|
Py_END_ALLOW_THREADS
|
|
601
596
|
|
|
602
597
|
if (status != PGRES_COMMAND_OK) {
|
|
603
|
-
// SetResultError will take ownership of `result`.
|
|
604
598
|
return SetResultError(final_result.Detach());
|
|
605
599
|
}
|
|
606
600
|
|
|
@@ -685,7 +679,7 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
|
|
|
685
679
|
Py_END_ALLOW_THREADS
|
|
686
680
|
|
|
687
681
|
if (result == 0)
|
|
688
|
-
return
|
|
682
|
+
return SetConnectionError(cnxn);
|
|
689
683
|
|
|
690
684
|
switch (PQresultStatus(result)) {
|
|
691
685
|
case PGRES_COPY_IN:
|
|
@@ -1048,13 +1042,17 @@ static PyObject* Connection_begin(PyObject* self, PyObject* args)
|
|
|
1048
1042
|
if (txnstatus == PQTRANS_IDLE)
|
|
1049
1043
|
{
|
|
1050
1044
|
result = PQexec(cnxn->pgconn, "BEGIN");
|
|
1051
|
-
|
|
1045
|
+
if (result)
|
|
1046
|
+
status = PQresultStatus(result);
|
|
1052
1047
|
}
|
|
1053
1048
|
Py_END_ALLOW_THREADS
|
|
1054
1049
|
|
|
1055
1050
|
if (txnstatus != PQTRANS_IDLE)
|
|
1056
1051
|
return PyErr_Format(Error, "Connection transaction status is not idle: %s", NameFromTxnFlag(txnstatus));
|
|
1057
1052
|
|
|
1053
|
+
if (result == 0)
|
|
1054
|
+
return SetConnectionError(cnxn);
|
|
1055
|
+
|
|
1058
1056
|
if (status != PGRES_COMMAND_OK)
|
|
1059
1057
|
return SetResultError(result);
|
|
1060
1058
|
|
|
@@ -1081,13 +1079,17 @@ static PyObject* Connection_commit(PyObject* self, PyObject* args)
|
|
|
1081
1079
|
if (txnstatus == PQTRANS_INTRANS)
|
|
1082
1080
|
{
|
|
1083
1081
|
result = PQexec(cnxn->pgconn, "COMMIT");
|
|
1084
|
-
|
|
1082
|
+
if (result)
|
|
1083
|
+
status = PQresultStatus(result);
|
|
1085
1084
|
}
|
|
1086
1085
|
Py_END_ALLOW_THREADS
|
|
1087
1086
|
|
|
1088
1087
|
if (txnstatus != PQTRANS_IDLE && txnstatus != PQTRANS_INTRANS)
|
|
1089
1088
|
return PyErr_Format(Error, "Connection transaction status is invalid: %s", NameFromTxnFlag(txnstatus));
|
|
1090
1089
|
|
|
1090
|
+
if (txnstatus == PQTRANS_INTRANS && result == 0)
|
|
1091
|
+
return SetConnectionError(cnxn);
|
|
1092
|
+
|
|
1091
1093
|
if (status != PGRES_COMMAND_OK)
|
|
1092
1094
|
return SetResultError(result);
|
|
1093
1095
|
|
|
@@ -1110,9 +1112,13 @@ static PyObject* Connection_rollback(PyObject* self, PyObject* args)
|
|
|
1110
1112
|
|
|
1111
1113
|
Py_BEGIN_ALLOW_THREADS
|
|
1112
1114
|
result = PQexec(cnxn->pgconn, "ROLLBACK");
|
|
1113
|
-
|
|
1115
|
+
if (result)
|
|
1116
|
+
status = PQresultStatus(result);
|
|
1114
1117
|
Py_END_ALLOW_THREADS
|
|
1115
1118
|
|
|
1119
|
+
if (result == 0)
|
|
1120
|
+
return SetConnectionError(cnxn);
|
|
1121
|
+
|
|
1116
1122
|
if (status != PGRES_COMMAND_OK)
|
|
1117
1123
|
return SetResultError(result);
|
|
1118
1124
|
|
|
@@ -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
|
}
|
|
@@ -523,16 +523,20 @@ def test_money(cnxn):
|
|
|
523
523
|
# parses the values we pass as strings before we even get to pglib.
|
|
524
524
|
|
|
525
525
|
|
|
526
|
-
@pytest.mark.skip('LC_ALL locale does not exist')
|
|
527
526
|
def test_decimal_german(cnxn):
|
|
528
|
-
|
|
527
|
+
try:
|
|
528
|
+
locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
|
|
529
|
+
except locale.Error:
|
|
530
|
+
pytest.skip('de_DE.utf8 locale not available')
|
|
529
531
|
values = [Decimal(s) for s in ['-3.0000000', '123456.7890']]
|
|
530
532
|
_test_type(cnxn, 'decimal(100,7)', values)
|
|
531
533
|
|
|
532
534
|
|
|
533
|
-
@pytest.mark.skip('LC_ALL locale does not exist')
|
|
534
535
|
def test_money_german(cnxn):
|
|
535
|
-
|
|
536
|
+
try:
|
|
537
|
+
locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
|
|
538
|
+
except locale.Error:
|
|
539
|
+
pytest.skip('de_DE.utf8 locale not available')
|
|
536
540
|
values = [Decimal(s) for s in ['1.23', '0.0', '123.45', '-12.34']]
|
|
537
541
|
_test_type(cnxn, 'money', values, check_comparison=False)
|
|
538
542
|
|
|
@@ -1308,3 +1312,40 @@ def test_large_insert(cnxn):
|
|
|
1308
1312
|
query = (Path(__file__).parent / 'large-insert.sql').read_text(encoding='utf8')
|
|
1309
1313
|
print('-' * 40)
|
|
1310
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
|