pglib 5.7.1__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.1/pglib.egg-info → pglib-5.9.1}/PKG-INFO +1 -1
  2. {pglib-5.7.1 → pglib-5.9.1}/pglib/__init__.py +0 -4
  3. {pglib-5.7.1 → pglib-5.9.1/pglib.egg-info}/PKG-INFO +1 -1
  4. {pglib-5.7.1 → pglib-5.9.1}/pglib.egg-info/SOURCES.txt +0 -1
  5. {pglib-5.7.1 → pglib-5.9.1}/setup.py +1 -1
  6. {pglib-5.7.1 → pglib-5.9.1}/src/connection.cpp +178 -8
  7. {pglib-5.7.1 → pglib-5.9.1}/src/getdata.cpp +7 -2
  8. {pglib-5.7.1 → pglib-5.9.1}/src/params.cpp +1 -1
  9. {pglib-5.7.1 → pglib-5.9.1}/src/pgarrays.cpp +172 -59
  10. {pglib-5.7.1 → pglib-5.9.1}/src/pgarrays.h +1 -0
  11. {pglib-5.7.1 → pglib-5.9.1}/src/pglib.h +3 -2
  12. {pglib-5.7.1 → pglib-5.9.1}/test/test_sync.py +67 -11
  13. pglib-5.7.1/pglib/_version.py +0 -484
  14. {pglib-5.7.1 → pglib-5.9.1}/LICENSE +0 -0
  15. {pglib-5.7.1 → pglib-5.9.1}/MANIFEST.in +0 -0
  16. {pglib-5.7.1 → pglib-5.9.1}/README.rst +0 -0
  17. {pglib-5.7.1 → pglib-5.9.1}/pglib/asyncpglib.py +0 -0
  18. {pglib-5.7.1 → pglib-5.9.1}/pglib.egg-info/dependency_links.txt +0 -0
  19. {pglib-5.7.1 → pglib-5.9.1}/pglib.egg-info/top_level.txt +0 -0
  20. {pglib-5.7.1 → pglib-5.9.1}/setup.cfg +0 -0
  21. {pglib-5.7.1 → pglib-5.9.1}/src/byteswap.h +0 -0
  22. {pglib-5.7.1 → pglib-5.9.1}/src/connection.h +0 -0
  23. {pglib-5.7.1 → pglib-5.9.1}/src/conninfoopt.cpp +0 -0
  24. {pglib-5.7.1 → pglib-5.9.1}/src/conninfoopt.h +0 -0
  25. {pglib-5.7.1 → pglib-5.9.1}/src/datatypes.cpp +0 -0
  26. {pglib-5.7.1 → pglib-5.9.1}/src/datatypes.h +0 -0
  27. {pglib-5.7.1 → pglib-5.9.1}/src/debug.cpp +0 -0
  28. {pglib-5.7.1 → pglib-5.9.1}/src/debug.h +0 -0
  29. {pglib-5.7.1 → pglib-5.9.1}/src/enums.cpp +0 -0
  30. {pglib-5.7.1 → pglib-5.9.1}/src/enums.h +0 -0
  31. {pglib-5.7.1 → pglib-5.9.1}/src/errors.cpp +0 -0
  32. {pglib-5.7.1 → pglib-5.9.1}/src/errors.h +0 -0
  33. {pglib-5.7.1 → pglib-5.9.1}/src/getdata.h +0 -0
  34. {pglib-5.7.1 → pglib-5.9.1}/src/juliandate.cpp +0 -0
  35. {pglib-5.7.1 → pglib-5.9.1}/src/juliandate.h +0 -0
  36. {pglib-5.7.1 → pglib-5.9.1}/src/params.h +0 -0
  37. {pglib-5.7.1 → pglib-5.9.1}/src/pglib.cpp +0 -0
  38. {pglib-5.7.1 → pglib-5.9.1}/src/pgtypes.h +0 -0
  39. {pglib-5.7.1 → pglib-5.9.1}/src/resultset.cpp +0 -0
  40. {pglib-5.7.1 → pglib-5.9.1}/src/resultset.h +0 -0
  41. {pglib-5.7.1 → pglib-5.9.1}/src/row.cpp +0 -0
  42. {pglib-5.7.1 → pglib-5.9.1}/src/row.h +0 -0
  43. {pglib-5.7.1 → pglib-5.9.1}/src/runtime.cpp +0 -0
  44. {pglib-5.7.1 → pglib-5.9.1}/src/runtime.h +0 -0
  45. {pglib-5.7.1 → pglib-5.9.1}/src/type_hstore.cpp +0 -0
  46. {pglib-5.7.1 → pglib-5.9.1}/src/type_hstore.h +0 -0
  47. {pglib-5.7.1 → pglib-5.9.1}/src/type_json.cpp +0 -0
  48. {pglib-5.7.1 → pglib-5.9.1}/src/type_json.h +0 -0
  49. {pglib-5.7.1 → pglib-5.9.1}/src/type_ltree.cpp +0 -0
  50. {pglib-5.7.1 → pglib-5.9.1}/src/type_ltree.h +0 -0
  51. {pglib-5.7.1 → pglib-5.9.1}/test/test_async.py +0 -0
  52. {pglib-5.7.1 → pglib-5.9.1}/test/testutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pglib
3
- Version: 5.7.1
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.1
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.1',
133
+ version='5.9.1',
134
134
  description='A PostgreSQL interface',
135
135
  long_description=long_description,
136
136
  maintainer='Michael Kleehammer',
@@ -150,6 +150,8 @@ static PGresult* internal_execute(PyObject* self, PyObject* args)
150
150
  return 0;
151
151
  }
152
152
  const char* szSQL = PyUnicode_AsUTF8(pSql);
153
+ if (!szSQL)
154
+ return 0;
153
155
 
154
156
  Params params(cParams);
155
157
  if (!BindParams(cnxn, params, args))
@@ -176,6 +178,7 @@ static PGresult* internal_execute(PyObject* self, PyObject* args)
176
178
  return result;
177
179
  }
178
180
 
181
+
179
182
  static const char doc_script[] = "Connection.script(sql) --> None\n\n"
180
183
  "Executes a script which can contain multiple statements separated by semicolons.";
181
184
 
@@ -189,7 +192,11 @@ static PyObject* Connection_script(PyObject* self, PyObject* args)
189
192
  if (!PyArg_ParseTuple(args, "U", &pScript))
190
193
  return 0;
191
194
 
192
- 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);
193
200
  if (result == 0)
194
201
  return 0;
195
202
 
@@ -204,6 +211,7 @@ static PyObject* Connection_script(PyObject* self, PyObject* args)
204
211
  }
205
212
  }
206
213
 
214
+
207
215
  const char* doc_copy_from =
208
216
  "Connection.copy_from(command, source) --> int\n"
209
217
  "\n"
@@ -234,7 +242,7 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
234
242
  // an object with a read method (e.g. file).
235
243
  const char* buffer = 0;
236
244
  Py_ssize_t buffer_size = 0;
237
- PyObject* read_method = 0;
245
+ Object read_method;
238
246
 
239
247
  if (PyUnicode_Check(source))
240
248
  {
@@ -246,7 +254,7 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
246
254
  {
247
255
  if (!PyObject_HasAttrString(source, "read"))
248
256
  return PyErr_Format(Error, "CSV source must be a string or file-like object.");
249
- read_method = PyObject_GetAttrString(source, "read");
257
+ read_method.Attach(PyObject_GetAttrString(source, "read"));
250
258
  }
251
259
 
252
260
  Connection* cnxn = CastConnection(self, REQUIRE_OPEN);
@@ -254,6 +262,9 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
254
262
  return 0;
255
263
 
256
264
  const char* szSQL = PyUnicode_AsUTF8(command);
265
+ if (!szSQL)
266
+ return 0;
267
+
257
268
  ResultHolder result;
258
269
  Py_BEGIN_ALLOW_THREADS
259
270
  result = PQexec(cnxn->pgconn, szSQL);
@@ -354,6 +365,145 @@ static PyObject* Connection_copy_from(PyObject* self, PyObject* args)
354
365
  }
355
366
 
356
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
+
357
507
  const char* doc_copy_from_csv =
358
508
  "Connection.copy_from_csv(table, source, header=0) --> int\n"
359
509
  "\n"
@@ -390,15 +540,16 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
390
540
 
391
541
  const char* pszDelimiter = szDelimiter ? szDelimiter : ",";
392
542
  const char* pszQuote = szQuote ? szQuote : "\"";
393
- PyObject* sql = PyUnicode_FromFormat("copy %U from stdin with csv %s delimiter '%s' quote '%s'",
394
- 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));
395
545
 
396
546
  // If source is a string (Unicode), store the UTF-encoded value in buffer. If a byte
397
547
  // object, store directly in buffer. Otherwise, buffer will be zero and `source` must be
398
548
  // an object with a read method (e.g. file).
399
549
  const char* buffer = 0;
400
550
  Py_ssize_t buffer_size = 0;
401
- PyObject* read_method = 0;
551
+ Object read_method;
552
+ // PyObject* read_method = 0;
402
553
 
403
554
  if (PyUnicode_Check(source))
404
555
  {
@@ -410,7 +561,7 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
410
561
  {
411
562
  if (!PyObject_HasAttrString(source, "read"))
412
563
  return PyErr_Format(Error, "CSV source must be a string or file-like object.");
413
- read_method = PyObject_GetAttrString(source, "read");
564
+ read_method.Attach(PyObject_GetAttrString(source, "read"));
414
565
  }
415
566
 
416
567
  Connection* cnxn = CastConnection(self, REQUIRE_OPEN);
@@ -418,6 +569,9 @@ static PyObject* Connection_copy_from_csv(PyObject* self, PyObject* args, PyObje
418
569
  return 0;
419
570
 
420
571
  const char* szSQL = PyUnicode_AsUTF8(sql);
572
+ if (!szSQL)
573
+ return 0;
574
+
421
575
  ResultHolder result;
422
576
  Py_BEGIN_ALLOW_THREADS
423
577
  result = PQexec(cnxn->pgconn, szSQL);
@@ -1000,6 +1154,9 @@ static PyObject* Connection_sendQuery(PyObject* self, PyObject* args)
1000
1154
 
1001
1155
  int sent;
1002
1156
  const char* szSQL = PyUnicode_AsUTF8(pScript);
1157
+ if (!szSQL)
1158
+ return 0;
1159
+
1003
1160
  Py_BEGIN_ALLOW_THREADS
1004
1161
  sent = PQsendQuery(cnxn->pgconn, szSQL);
1005
1162
  Py_END_ALLOW_THREADS
@@ -1039,7 +1196,10 @@ static PyObject* Connection_sendQueryParams(PyObject* self, PyObject* args)
1039
1196
  PyErr_SetString(PyExc_TypeError, "The first argument must be the SQL string.");
1040
1197
  return 0;
1041
1198
  }
1199
+
1042
1200
  const char* szSQL = PyUnicode_AsUTF8(pSql);
1201
+ if (!szSQL)
1202
+ return 0;
1043
1203
 
1044
1204
  Params params(cParams);
1045
1205
  if (!BindParams(cnxn, params, args))
@@ -1218,7 +1378,16 @@ static PyObject* Connection_notifications(PyObject* self, PyObject* args, PyObje
1218
1378
  Py_END_ALLOW_THREADS
1219
1379
 
1220
1380
  if (retval == -1) {
1221
- 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
+
1222
1391
  return 0;
1223
1392
  }
1224
1393
 
@@ -1462,6 +1631,7 @@ static struct PyMethodDef Connection_methods[] =
1462
1631
  { "script", Connection_script, METH_VARARGS, doc_script },
1463
1632
  { "copy_from", (PyCFunction) Connection_copy_from, METH_VARARGS | METH_KEYWORDS, doc_copy_from },
1464
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},
1465
1635
  { "begin", Connection_begin, METH_NOARGS, doc_begin },
1466
1636
  { "commit", Connection_commit, METH_NOARGS, doc_commit },
1467
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
  {