pglib 5.7.0__tar.gz → 5.9.1__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 (52) hide show
  1. {pglib-5.7.0/pglib.egg-info → pglib-5.9.1}/PKG-INFO +1 -1
  2. {pglib-5.7.0 → pglib-5.9.1}/pglib/__init__.py +0 -4
  3. {pglib-5.7.0 → pglib-5.9.1/pglib.egg-info}/PKG-INFO +1 -1
  4. {pglib-5.7.0 → pglib-5.9.1}/pglib.egg-info/SOURCES.txt +0 -1
  5. {pglib-5.7.0 → pglib-5.9.1}/setup.py +1 -1
  6. {pglib-5.7.0 → pglib-5.9.1}/src/connection.cpp +184 -11
  7. {pglib-5.7.0 → pglib-5.9.1}/src/getdata.cpp +7 -2
  8. {pglib-5.7.0 → pglib-5.9.1}/src/params.cpp +1 -1
  9. {pglib-5.7.0 → pglib-5.9.1}/src/pgarrays.cpp +172 -59
  10. {pglib-5.7.0 → pglib-5.9.1}/src/pgarrays.h +1 -0
  11. {pglib-5.7.0 → pglib-5.9.1}/src/pglib.h +3 -2
  12. {pglib-5.7.0 → pglib-5.9.1}/src/resultset.cpp +4 -4
  13. {pglib-5.7.0 → pglib-5.9.1}/test/test_sync.py +86 -10
  14. {pglib-5.7.0 → pglib-5.9.1}/test/testutils.py +5 -1
  15. pglib-5.7.0/pglib/_version.py +0 -484
  16. {pglib-5.7.0 → pglib-5.9.1}/LICENSE +0 -0
  17. {pglib-5.7.0 → pglib-5.9.1}/MANIFEST.in +0 -0
  18. {pglib-5.7.0 → pglib-5.9.1}/README.rst +0 -0
  19. {pglib-5.7.0 → pglib-5.9.1}/pglib/asyncpglib.py +0 -0
  20. {pglib-5.7.0 → pglib-5.9.1}/pglib.egg-info/dependency_links.txt +0 -0
  21. {pglib-5.7.0 → pglib-5.9.1}/pglib.egg-info/top_level.txt +0 -0
  22. {pglib-5.7.0 → pglib-5.9.1}/setup.cfg +0 -0
  23. {pglib-5.7.0 → pglib-5.9.1}/src/byteswap.h +0 -0
  24. {pglib-5.7.0 → pglib-5.9.1}/src/connection.h +0 -0
  25. {pglib-5.7.0 → pglib-5.9.1}/src/conninfoopt.cpp +0 -0
  26. {pglib-5.7.0 → pglib-5.9.1}/src/conninfoopt.h +0 -0
  27. {pglib-5.7.0 → pglib-5.9.1}/src/datatypes.cpp +0 -0
  28. {pglib-5.7.0 → pglib-5.9.1}/src/datatypes.h +0 -0
  29. {pglib-5.7.0 → pglib-5.9.1}/src/debug.cpp +0 -0
  30. {pglib-5.7.0 → pglib-5.9.1}/src/debug.h +0 -0
  31. {pglib-5.7.0 → pglib-5.9.1}/src/enums.cpp +0 -0
  32. {pglib-5.7.0 → pglib-5.9.1}/src/enums.h +0 -0
  33. {pglib-5.7.0 → pglib-5.9.1}/src/errors.cpp +0 -0
  34. {pglib-5.7.0 → pglib-5.9.1}/src/errors.h +0 -0
  35. {pglib-5.7.0 → pglib-5.9.1}/src/getdata.h +0 -0
  36. {pglib-5.7.0 → pglib-5.9.1}/src/juliandate.cpp +0 -0
  37. {pglib-5.7.0 → pglib-5.9.1}/src/juliandate.h +0 -0
  38. {pglib-5.7.0 → pglib-5.9.1}/src/params.h +0 -0
  39. {pglib-5.7.0 → pglib-5.9.1}/src/pglib.cpp +0 -0
  40. {pglib-5.7.0 → pglib-5.9.1}/src/pgtypes.h +0 -0
  41. {pglib-5.7.0 → pglib-5.9.1}/src/resultset.h +0 -0
  42. {pglib-5.7.0 → pglib-5.9.1}/src/row.cpp +0 -0
  43. {pglib-5.7.0 → pglib-5.9.1}/src/row.h +0 -0
  44. {pglib-5.7.0 → pglib-5.9.1}/src/runtime.cpp +0 -0
  45. {pglib-5.7.0 → pglib-5.9.1}/src/runtime.h +0 -0
  46. {pglib-5.7.0 → pglib-5.9.1}/src/type_hstore.cpp +0 -0
  47. {pglib-5.7.0 → pglib-5.9.1}/src/type_hstore.h +0 -0
  48. {pglib-5.7.0 → pglib-5.9.1}/src/type_json.cpp +0 -0
  49. {pglib-5.7.0 → pglib-5.9.1}/src/type_json.h +0 -0
  50. {pglib-5.7.0 → pglib-5.9.1}/src/type_ltree.cpp +0 -0
  51. {pglib-5.7.0 → pglib-5.9.1}/src/type_ltree.h +0 -0
  52. {pglib-5.7.0 → pglib-5.9.1}/test/test_async.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pglib
3
- Version: 5.7.0
3
+ Version: 5.9.1
4
4
  Summary: A PostgreSQL interface
5
5
  Home-page: https://gitlab.com/mkleehammer/pglib
6
6
  Maintainer: Michael Kleehammer
@@ -4,7 +4,3 @@ from _pglib import *
4
4
 
5
5
  # The asynchronous code is in Python.
6
6
  from .asyncpglib import connect_async
7
-
8
- from ._version import get_versions
9
- __version__ = get_versions()['version']
10
- del get_versions
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pglib
3
- Version: 5.7.0
3
+ Version: 5.9.1
4
4
  Summary: A PostgreSQL interface
5
5
  Home-page: https://gitlab.com/mkleehammer/pglib
6
6
  Maintainer: Michael Kleehammer
@@ -21,7 +21,6 @@ setup.py
21
21
  /home/mkleehammer/dev/pglib/src/type_json.cpp
22
22
  /home/mkleehammer/dev/pglib/src/type_ltree.cpp
23
23
  pglib/__init__.py
24
- pglib/_version.py
25
24
  pglib/asyncpglib.py
26
25
  pglib.egg-info/PKG-INFO
27
26
  pglib.egg-info/SOURCES.txt
@@ -130,7 +130,7 @@ def _get_settings():
130
130
 
131
131
  setup(
132
132
  name='pglib',
133
- version='5.7.0',
133
+ version='5.9.1',
134
134
  description='A PostgreSQL interface',
135
135
  long_description=long_description,
136
136
  maintainer='Michael Kleehammer',
@@ -149,6 +149,9 @@ static PGresult* internal_execute(PyObject* self, PyObject* args)
149
149
  PyErr_SetString(PyExc_TypeError, "The first argument must be a string.");
150
150
  return 0;
151
151
  }
152
+ const char* szSQL = PyUnicode_AsUTF8(pSql);
153
+ if (!szSQL)
154
+ return 0;
152
155
 
153
156
  Params params(cParams);
154
157
  if (!BindParams(cnxn, params, args))
@@ -156,7 +159,7 @@ static PGresult* internal_execute(PyObject* self, PyObject* args)
156
159
 
157
160
  PGresult* result;
158
161
  Py_BEGIN_ALLOW_THREADS
159
- result = PQexecParams(cnxn->pgconn, PyUnicode_AsUTF8(pSql),
162
+ result = PQexecParams(cnxn->pgconn, szSQL,
160
163
  cParams,
161
164
  params.types,
162
165
  params.values,
@@ -175,6 +178,7 @@ static PGresult* internal_execute(PyObject* self, PyObject* args)
175
178
  return result;
176
179
  }
177
180
 
181
+
178
182
  static const char doc_script[] = "Connection.script(sql) --> None\n\n"
179
183
  "Executes a script which can contain multiple statements separated by semicolons.";
180
184
 
@@ -188,7 +192,11 @@ static PyObject* Connection_script(PyObject* self, PyObject* args)
188
192
  if (!PyArg_ParseTuple(args, "U", &pScript))
189
193
  return 0;
190
194
 
191
- ResultHolder result = PQexec(cnxn->pgconn, PyUnicode_AsUTF8(pScript));
195
+ const char* szScript = PyUnicode_AsUTF8(pScript);
196
+ if (!szScript)
197
+ return 0;
198
+
199
+ ResultHolder result = PQexec(cnxn->pgconn, szScript);
192
200
  if (result == 0)
193
201
  return 0;
194
202
 
@@ -203,6 +211,7 @@ static PyObject* Connection_script(PyObject* self, PyObject* args)
203
211
  }
204
212
  }
205
213
 
214
+
206
215
  const char* doc_copy_from =
207
216
  "Connection.copy_from(command, source) --> int\n"
208
217
  "\n"
@@ -233,7 +242,7 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
233
242
  // an object with a read method (e.g. file).
234
243
  const char* buffer = 0;
235
244
  Py_ssize_t buffer_size = 0;
236
- PyObject* read_method = 0;
245
+ Object read_method;
237
246
 
238
247
  if (PyUnicode_Check(source))
239
248
  {
@@ -245,7 +254,7 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
245
254
  {
246
255
  if (!PyObject_HasAttrString(source, "read"))
247
256
  return PyErr_Format(Error, "CSV source must be a string or file-like object.");
248
- read_method = PyObject_GetAttrString(source, "read");
257
+ read_method.Attach(PyObject_GetAttrString(source, "read"));
249
258
  }
250
259
 
251
260
  Connection* cnxn = CastConnection(self, REQUIRE_OPEN);
@@ -253,6 +262,9 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
253
262
  return 0;
254
263
 
255
264
  const char* szSQL = PyUnicode_AsUTF8(command);
265
+ if (!szSQL)
266
+ return 0;
267
+
256
268
  ResultHolder result;
257
269
  Py_BEGIN_ALLOW_THREADS
258
270
  result = PQexec(cnxn->pgconn, szSQL);
@@ -353,6 +365,145 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
353
365
  }
354
366
 
355
367
 
368
+ const char* doc_copy_to_csv =
369
+ "Connection.copy_to_csv(table, dest, header=0, delimiter=',', quote='\"')\n"
370
+ "\n"
371
+ "Execute a COPY TO command and return the number of records copied.\n"
372
+ "\n"
373
+ "table\n"
374
+ " The table to copy from.\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
+ "header\n"
381
+ " If non-zero, a CSV header will be written.\n";
382
+
383
+
384
+ static PyObject* Connection_copy_to_csv(PyObject* self, PyObject* args, PyObject* kwargs)
385
+ {
386
+ // This is not nearly as efficient as I'd like since newer Python versions no longer give
387
+ // us access to underlying file objects. We have to write strings through a write method
388
+ // since there are io layers involved.
389
+ //
390
+ // For maximum performance, we should probably offer an option where we open the file given
391
+ // a filename. We can either check the parameter type here or we could make a separate
392
+ // method with "file" in the name like copy_to_file.
393
+
394
+ static const char* kwlist[] = {"table", "dest", "header", "delimiter", "quote", 0};
395
+
396
+ PyObject* table;
397
+ PyObject* dest;
398
+ int header = 0;
399
+ char* szDelimiter = 0;
400
+ char* szQuote = 0;
401
+
402
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "UO|pzz", (char**)kwlist, &table, &dest, &header,
403
+ &szDelimiter, &szQuote)) {
404
+ return 0;
405
+ }
406
+
407
+ Connection* cnxn = CastConnection(self, REQUIRE_OPEN);
408
+ if (!cnxn)
409
+ return 0;
410
+
411
+ if (!PyObject_HasAttrString(dest, "write"))
412
+ return PyErr_Format(Error, "CSV destination must be a file-like object.");
413
+ Object write_method(PyObject_GetAttrString(dest, "write"));
414
+
415
+ char header_token[] = "header";
416
+ if (header == 0) {
417
+ header_token[0] = 0;
418
+ }
419
+
420
+ const char* pszDelimiter = szDelimiter ? szDelimiter : ",";
421
+ const char* pszQuote = szQuote ? szQuote : "\"";
422
+
423
+ Object sql(PyUnicode_FromFormat("copy %U to stdout with csv %s delimiter '%s' quote '%s'",
424
+ table, header_token, pszDelimiter, pszQuote));
425
+ if (!sql)
426
+ return 0;
427
+
428
+ const char* szSQL = PyUnicode_AsUTF8(sql);
429
+ if (!szSQL)
430
+ return 0;
431
+
432
+ ResultHolder result;
433
+ Py_BEGIN_ALLOW_THREADS
434
+ result = PQexec(cnxn->pgconn, szSQL);
435
+ Py_END_ALLOW_THREADS
436
+
437
+ if (result == 0)
438
+ return 0;
439
+
440
+ switch (PQresultStatus(result)) {
441
+ case PGRES_COPY_OUT:
442
+ // This is what we are expecting.
443
+ break;
444
+
445
+ case PGRES_BAD_RESPONSE:
446
+ case PGRES_NONFATAL_ERROR:
447
+ case PGRES_FATAL_ERROR:
448
+ return SetResultError(result.Detach());
449
+
450
+ default:
451
+ return PyErr_Format(Error, "Result was not PGRES_COPY_IN: %d", (int)PQresultStatus(result));
452
+ }
453
+
454
+
455
+ for (;;) {
456
+ int cb = 0;
457
+ char* buffer;
458
+ Py_BEGIN_ALLOW_THREADS
459
+ cb = PQgetCopyData(cnxn->pgconn, &buffer, 0);
460
+ Py_END_ALLOW_THREADS
461
+
462
+ if (cb == -2) {
463
+ return SetResultError(result.Detach());
464
+ }
465
+
466
+ if (cb == -1) {
467
+ // The copy is complete.
468
+ break;
469
+ }
470
+
471
+ // We have a buffer of byte data. We have the length, but the libpq docs say that the
472
+ // string is also zero terminated, so we're going to try not calling 'write'.
473
+
474
+ int err = PyFile_WriteString(buffer, dest);
475
+
476
+ // while (cb > 0) {
477
+ // PyObject* res = PyObject_CallObject(write_method)
478
+ // }
479
+
480
+ PQfreemem(buffer);
481
+ if (err) {
482
+ return 0;
483
+ }
484
+ }
485
+
486
+ // After a copy, you have to get another result to know if it was successful.
487
+
488
+ ResultHolder final_result;
489
+ ExecStatusType status = PGRES_COMMAND_OK;
490
+ Py_BEGIN_ALLOW_THREADS
491
+ final_result = PQgetResult(cnxn->pgconn);
492
+ status = PQresultStatus(final_result);
493
+ Py_END_ALLOW_THREADS
494
+
495
+ if (status != PGRES_COMMAND_OK) {
496
+ // SetResultError will take ownership of `result`.
497
+ return SetResultError(final_result.Detach());
498
+ }
499
+
500
+ const char* sz = PQcmdTuples(final_result);
501
+ if (sz == 0 || *sz == 0)
502
+ Py_RETURN_NONE;
503
+ return PyLong_FromLong(atoi(sz));
504
+ }
505
+
506
+
356
507
  const char* doc_copy_from_csv =
357
508
  "Connection.copy_from_csv(table, source, header=0) --> int\n"
358
509
  "\n"
@@ -389,15 +540,16 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
389
540
 
390
541
  const char* pszDelimiter = szDelimiter ? szDelimiter : ",";
391
542
  const char* pszQuote = szQuote ? szQuote : "\"";
392
- PyObject* sql = PyUnicode_FromFormat("copy %U from stdin with csv %s delimiter '%s' quote '%s'",
393
- table, header_token, pszDelimiter, pszQuote);
543
+ Object sql(PyUnicode_FromFormat("copy %U from stdin with csv %s delimiter '%s' quote '%s'",
544
+ table, header_token, pszDelimiter, pszQuote));
394
545
 
395
546
  // If source is a string (Unicode), store the UTF-encoded value in buffer. If a byte
396
547
  // object, store directly in buffer. Otherwise, buffer will be zero and `source` must be
397
548
  // an object with a read method (e.g. file).
398
549
  const char* buffer = 0;
399
550
  Py_ssize_t buffer_size = 0;
400
- PyObject* read_method = 0;
551
+ Object read_method;
552
+ // PyObject* read_method = 0;
401
553
 
402
554
  if (PyUnicode_Check(source))
403
555
  {
@@ -409,7 +561,7 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
409
561
  {
410
562
  if (!PyObject_HasAttrString(source, "read"))
411
563
  return PyErr_Format(Error, "CSV source must be a string or file-like object.");
412
- read_method = PyObject_GetAttrString(source, "read");
564
+ read_method.Attach(PyObject_GetAttrString(source, "read"));
413
565
  }
414
566
 
415
567
  Connection* cnxn = CastConnection(self, REQUIRE_OPEN);
@@ -417,6 +569,9 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
417
569
  return 0;
418
570
 
419
571
  const char* szSQL = PyUnicode_AsUTF8(sql);
572
+ if (!szSQL)
573
+ return 0;
574
+
420
575
  ResultHolder result;
421
576
  Py_BEGIN_ALLOW_THREADS
422
577
  result = PQexec(cnxn->pgconn, szSQL);
@@ -998,8 +1153,12 @@ static PyObject* Connection_sendQuery(PyObject* self, PyObject* args)
998
1153
  return 0;
999
1154
 
1000
1155
  int sent;
1156
+ const char* szSQL = PyUnicode_AsUTF8(pScript);
1157
+ if (!szSQL)
1158
+ return 0;
1159
+
1001
1160
  Py_BEGIN_ALLOW_THREADS
1002
- sent = PQsendQuery(cnxn->pgconn, PyUnicode_AsUTF8(pScript));
1161
+ sent = PQsendQuery(cnxn->pgconn, szSQL);
1003
1162
  Py_END_ALLOW_THREADS
1004
1163
 
1005
1164
  if (!sent)
@@ -1038,6 +1197,10 @@ static PyObject* Connection_sendQueryParams(PyObject* self, PyObject* args)
1038
1197
  return 0;
1039
1198
  }
1040
1199
 
1200
+ const char* szSQL = PyUnicode_AsUTF8(pSql);
1201
+ if (!szSQL)
1202
+ return 0;
1203
+
1041
1204
  Params params(cParams);
1042
1205
  if (!BindParams(cnxn, params, args))
1043
1206
  return 0;
@@ -1047,7 +1210,7 @@ static PyObject* Connection_sendQueryParams(PyObject* self, PyObject* args)
1047
1210
  int needs_flush = -1;
1048
1211
 
1049
1212
  Py_BEGIN_ALLOW_THREADS
1050
- error = PQsendQueryParams(cnxn->pgconn, PyUnicode_AsUTF8(pSql),
1213
+ error = PQsendQueryParams(cnxn->pgconn, szSQL,
1051
1214
  cParams,
1052
1215
  params.types,
1053
1216
  params.values,
@@ -1215,7 +1378,16 @@ static PyObject* Connection_notifications(PyObject* self, PyObject* args, PyObje
1215
1378
  Py_END_ALLOW_THREADS
1216
1379
 
1217
1380
  if (retval == -1) {
1218
- SetStringError(Error, "An error occurred waiting for notifications");
1381
+ // Allow the Python (or application) signal handler deal with Ctrl-C. If it raises an
1382
+ // exception, -1 is returned. This only works on the main Python thread, but it is
1383
+ // just a convenience for command line utilities to raise a KeyboardInterrupt instead a
1384
+ // pglib error that *then* raises a KeyboardInterrupt. So it is OK if we call on
1385
+ // another thread and it is a no-op.
1386
+
1387
+ if (PyErr_CheckSignals() != -1) {
1388
+ SetStringError(Error, "An error occurred waiting for notifications");
1389
+ }
1390
+
1219
1391
  return 0;
1220
1392
  }
1221
1393
 
@@ -1459,6 +1631,7 @@ static struct PyMethodDef Connection_methods[] =
1459
1631
  { "script", Connection_script, METH_VARARGS, doc_script },
1460
1632
  { "copy_from", (PyCFunction) Connection_copy_from, METH_VARARGS | METH_KEYWORDS, doc_copy_from },
1461
1633
  { "copy_from_csv", (PyCFunction) Connection_copy_from_csv, METH_VARARGS | METH_KEYWORDS, doc_copy_from_csv },
1634
+ { "copy_to_csv", (PyCFunction) Connection_copy_to_csv, METH_VARARGS | METH_KEYWORDS, doc_copy_to_csv},
1462
1635
  { "begin", Connection_begin, METH_NOARGS, doc_begin },
1463
1636
  { "commit", Connection_commit, METH_NOARGS, doc_commit },
1464
1637
  { "rollback", Connection_rollback, METH_NOARGS, doc_rollback },
@@ -286,8 +286,10 @@ static PyObject* GetInterval(const char* p) // , bool integer_datetimes)
286
286
  uint32_t month = swaps4(pinterval->month);
287
287
  uint32_t year = month / 12;
288
288
 
289
- if (month || year)
290
- return PyErr_Format(Error, "Years and months are not supported in intervals");
289
+ if (month || year) {
290
+ PyErr_SetString(Error, "Years and months are not supported in intervals");
291
+ return 0;
292
+ }
291
293
 
292
294
  int seconds = (second) + (60 * minute) + (3600 * hour);
293
295
  int days = day;
@@ -399,6 +401,9 @@ PyObject* ConvertValue(PGresult* result, int iRow, int iCol, bool integer_dateti
399
401
  case TEXTARRAYOID:
400
402
  return GetTextArray(p);
401
403
 
404
+ case JSONBARRAYOID:
405
+ return GetJSONBArray(p);
406
+
402
407
  case DATEARRAYOID:
403
408
  return GetDateArray(p);
404
409
 
@@ -124,7 +124,7 @@ WriteBuffer Params::Allocate(size_t amount)
124
124
  if (*pp == 0)
125
125
  {
126
126
  size_t total = amount + 1024;
127
- *pp = (Pool*)malloc(total);
127
+ *pp = (Pool*)malloc(total + (sizeof(Pool) - 1));
128
128
 
129
129
  if (*pp == 0)
130
130
  {