Compare commits

...

113 Commits

Author SHA1 Message Date
Daniele Varrazzo
fbaad6c932 Version number bumped to 2.7.6.1 2018-11-10 23:46:17 +00:00
Daniele Varrazzo
74666b4cf3 Merge branch 'fix-binaries-2.7.6' into maint_2_7 2018-11-10 23:44:59 +00:00
Daniele Varrazzo
cbd976cd54 Mentioning binary packages fixes in news file 2018-11-10 18:40:32 +00:00
Daniele Varrazzo
2ef1bcc4af Bump package version again to test building with an older osx image
Test packages to be release to check if #807 is fixed.

According to https://docs.travis-ci.com/user/reference/osx/#os-x-version
we built the packages with OS X 10.13 and Xcode 9.4.1. Because the error
was reported on 10.12, trying to build with Xcode 9.2 (the most recent
available on OSX 10.12).

[skip ci]
2018-11-10 16:36:08 +00:00
Daniele Varrazzo
5e0b109b26 Bump to a package version to test with newer libpq version
Test packages to be release to check if #808 is fixed.

[skip ci]
2018-11-10 12:17:12 +00:00
Daniele Varrazzo
6f664348e0 Fixed NEWS file after I actually re-read it for release. 2018-11-09 11:29:08 +00:00
Daniele Varrazzo
b219f535ce Bump to next dev release 2018-11-09 11:24:54 +00:00
Daniele Varrazzo
3b84bc1b6e Version number bumped to 2.7.6 2018-11-08 17:40:28 +00:00
Daniele Varrazzo
699a4bcb90 Declare Python 3.7 supported
I had problems running the test on travis in a way that make the
long-in-the-tooth Python 3.2 happy, however it was tested in the
test_i686 branch and it works fine.
2018-11-08 17:37:47 +00:00
Daniele Varrazzo
8fec446789 Fixed catching too broad exceptions
Also some flake8 cleanup in the lib dir.
2018-11-08 12:34:18 +00:00
Daniele Varrazzo
fcc4cd6734 Merge branch 'fix-788' into maint_2_7 2018-10-30 01:47:09 +00:00
Daniele Varrazzo
9d15104868 Fixed adaptation of lists of empty lists
...somehow. Postgres doesn't support them and converts them into a
simple empty array. However this is not really our concern: the syntax
we return is valid.

Close #788
2018-10-30 00:29:18 +00:00
Daniele Varrazzo
25e6d011ac Merge branch 'fix-794' into maint_2_7 2018-10-23 01:56:17 +01:00
Daniele Varrazzo
eeef58b581 Don't barf on Composite passed to execute_values()
Close #794
2018-10-23 01:17:15 +01:00
Daniele Varrazzo
1de7d53fb1 errorcodes map update to PostgreSQL 11 2018-10-14 22:57:01 +01:00
Daniele Varrazzo
ba9f37022a Merge branch 'fix-790' into maint_2_7 2018-10-12 23:31:34 +01:00
Daniele Varrazzo
ed6f32999f Fixed refcount in connection's readonly and deferrable getters
Close #790
2018-10-12 22:37:50 +01:00
Daniele Varrazzo
74fd0ec063 Fixed farto in error checking assigning description values
Unnoticed since Feb 2011 \o/
2018-10-11 10:32:42 +01:00
Daniele Varrazzo
d251d51b34 Merge branch 'fix-copy-async-hangs' into maint_2_7 2018-10-10 23:56:05 +01:00
Daniele Varrazzo
5abbcb23ca Fixed infinite loop in pq_get_last_result after COPY
There will be an error downstream but we have to get out of this
function first.

Close #781
2018-10-10 23:23:56 +01:00
Daniele Varrazzo
c97266921c Merge branch 'fix-bsd' into maint_2_7 2018-09-07 23:57:03 +01:00
Daniele Varrazzo
5d52dae286 Mention FreeBSD build fixed in news file 2018-09-07 23:56:15 +01:00
Dmitry Marakasov
41deda1950 Remove obsolete and incorrect FreeBSD version condition
The FreeBSD-related condition which enables custom round() implementation is incorrect: one must include <sys/param.h> to get __FreeBSD_version value, and since it's not included here, the check succeeds while it shouldn't. Before it worked somehow, but since python 3.7 it results in conflicting declarations of round(). The condition is also no longer needed since FreeBSD 5.3 is unsupported for 12 years.
2018-09-07 23:55:08 +01:00
Daniel Hahler
fd6432977d Improve doc for extras.wait_select 2018-09-05 16:45:55 +01:00
Andrew King
48d292c14c Fix typo in install.rst 2018-09-05 16:43:55 +01:00
Jon Dufresne
85926bef5a Update intersphinx URLs to point to Python 3 docs
Python 3 docs are more up to date and reflect the future of Python.

Removed unused py3 marker.
2018-09-05 16:43:54 +01:00
Daniele Varrazzo
3cdc0bc6e5 Merge branch 'fix-746' into maint_2_7 2018-07-24 21:13:36 +01:00
Daniele Varrazzo
84fb389b3b Fixed compile error on windows
Because const int + 1 is not const, right???

Also fixed other occurrences of magic numbers and failed DRY around
PyOS_snprintf() calls.
2018-07-24 19:17:07 +01:00
Daniele Varrazzo
ba54f1194a Bump tests for selective closure of named cursor to pg 8.2
Previous versions don't support the features as they don't have the
pg_cursors view. But they are too old to care.
2018-07-24 19:02:13 +01:00
Daniele Varrazzo
43e35db988 Close named cursor if exist, even if we didn't run execute
Close #746
2018-07-21 18:32:02 +01:00
Daniele Varrazzo
a7f8c7fcde Bump to next dev version number 2018-07-21 12:15:47 +01:00
Daniele Varrazzo
1b07d2e34d Bump to version 2.7.5 2018-05-27 10:08:36 +01:00
Daniele Varrazzo
eafa0f9d55 Note the upgrade in wheel dependencies in NEWS file 2018-05-23 21:51:26 +01:00
Daniele Varrazzo
6bcea5da34 Added note adivising against depending on the -wheels package 2018-05-23 21:42:43 +01:00
Daniele Varrazzo
8195d83a8c Dropped license paragraph applying to removed files 2018-05-23 21:19:06 +01:00
Jon Dufresne
4292da9b31 Update all pypi.python.org URLs to pypi.org
For details on the new PyPI, see the blog post:

https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html

(with my addition after cherry-picking from master to maint_2_7)
2018-05-20 17:25:28 +01:00
Daniele Varrazzo
dbae84ee29 Added license to the docs
Includes other docs improvements, such as the ones proposed in #711.
2018-05-20 17:05:31 +01:00
Daniele Varrazzo
85ec17a313 Merge branch 'fix-716' into maint_2_7 2018-05-20 17:05:25 +01:00
Daniele Varrazzo
6a3521badb Merge branch 'fix-707' into maint_2_7 2018-05-20 17:05:14 +01:00
Daniele Varrazzo
7aea6b846e Don't raise an exception closing an unused named cursor
Close #716
2018-05-20 14:05:58 +01:00
Daniele Varrazzo
f2896fe512 Set minimal postgres version for intervalstyle test 2018-05-20 13:42:30 +01:00
Daniele Varrazzo
51027dbd5f Raise NotSupportedError fetching iso_8601 intervals
Previously it would have failed parsing and resulted in ValueError

Close #707
2018-05-20 13:24:40 +01:00
Jon Dufresne
42efb739d8 Fix use of "async" in test_cursor.py
"async" will be a keyword starting with Python 3.7. On Python 3.6, use
of "async" causes a deprecation warning. Use the alias "async_" instead.
2018-05-18 11:18:24 +01:00
Daniele Varrazzo
dcadc4c15a Merge remote-tracking branch 'nested-array-nulls' into maint_2_7 2018-05-18 11:18:07 +01:00
Daniele Varrazzo
11122159c6 Merge remote-tracking branch 'fix-211' into maint_2_7 2018-05-18 11:16:36 +01:00
Daniele Varrazzo
72e695ba1f Skipped test on db version not supporting unicode identifiers 2018-05-14 03:11:11 +01:00
Daniele Varrazzo
799f8bded4 Hstore test fixed after adapting arrays dropped space after commas 2018-05-14 03:04:57 +01:00
Daniele Varrazzo
2bc1a81dba Test databases from newest to oldest
This way we can spot when a feature was not supported yet by the first
test failing.
2018-05-14 02:41:32 +01:00
Daniele Varrazzo
451dc8c5bf Fixed adaptation of arrays of arrays of nulls
Close #325, close #706.
2018-05-14 02:38:44 +01:00
Daniele Varrazzo
8ad39c2712 Allow non-ascii chars in namedtuple fields
They can be valid chars in Python 3. Or maybe not? In which case Python
will throw an exception, but that's fine.

Fix regression introduced fixing #211
2018-05-13 23:51:21 +01:00
Daniele Varrazzo
667079b209 Command to upload docs on pythonhosted dropped altogether 2018-02-25 18:13:29 +00:00
Daniele Varrazzo
4e53cb4a02 Intersphinx urls to generate Python links updated
Previous urls warn about a redirect, so they are probably to go.
2018-02-25 18:11:17 +00:00
Daniele Varrazzo
806cc5e610 Merge branch 'fix-679' into maint_2_7 2018-02-21 10:26:51 +00:00
Daniele Varrazzo
214600fcc0 Allow strings subclasses in ensure_bytes
Fix #679
2018-02-20 17:33:12 +00:00
Daniele Varrazzo
19c74cc654 Merge branch 'expand-version' into maint_2_7 2018-02-19 13:52:36 +00:00
Daniele Varrazzo
3772f88680 Report MSYS2 build probably fixed 2018-02-19 13:51:45 +00:00
Daniele Varrazzo
9cae89c4e4 Avoid quoting the string in the psycopg version macro
Use a macro trick to add the quotes. This seems more portable than
passing the quotes to the command line (see #658).

https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
2018-02-19 13:49:14 +00:00
Daniele Varrazzo
5ffabd2886 Merge branch 'fix-solaris-11' into maint_2_7 2018-02-19 13:48:46 +00:00
Daniele Varrazzo
657c1ce6a9 Added Solaris 11 fix to news file 2018-02-19 11:47:12 +00:00
Mike Gerdts
317917866e Fixed building on SmartOS
timeradd is missing on Solaris 10, but is present as a macro in
<sys/time.h> on SmartOS, illumos, and likely Solaris 11.
2018-02-19 11:44:58 +00:00
Daniele Varrazzo
8f18681723 Fixed pip invocation example to skip binary packages
Close #673
2018-02-09 16:10:03 +00:00
Daniele Varrazzo
3be66e59b3 Bump to next dev version number 2018-02-09 16:09:33 +00:00
Daniele Varrazzo
5afb2ce803 Version number bumped to 2.7.4 2018-02-06 01:24:25 +00:00
Daniele Varrazzo
41137e4e11 Autocommit shouldn't change deferrable on servers not supporting it
Regression on unsupported Postgres versions after fixing bug #580
2018-02-06 01:24:25 +00:00
Daniele Varrazzo
8dc26b86fa Report Python 2.6 wheels no more available
[skip ci]
2018-01-29 12:56:50 +00:00
Daniele Varrazzo
a4fcbfed0f Dropped recipe to install namedtuple on Python < 2.6
We don't support such Python versions anymore.
2018-01-29 03:10:56 +00:00
Daniele Varrazzo
e42e07258c Link psycopg2-binary package to PyPI in readme
Note: the package doesn't exist yet...
2018-01-29 03:09:27 +00:00
Daniele Varrazzo
6ba6cf27ed Merge branch 'openssl-1.0.2n' into maint_2_7 2018-01-29 02:57:14 +00:00
Daniele Varrazzo
485649a3d1 Merge branch 'namedtuple-invalid-identifiers' into maint_2_7 2018-01-29 02:56:53 +00:00
Daniele Varrazzo
e5da79fcc8 Convert fields names into valid Python identifiers in NamedTupleCursor
Close #211.
2018-01-29 02:41:44 +00:00
Daniele Varrazzo
a5762a4e4b Build using OpenSSL 1.0.2n 2018-01-29 01:58:37 +00:00
Daniele Varrazzo
8f6a26b911 Define openssl and libpq versions in vars in appveyor build 2018-01-29 01:57:17 +00:00
Daniele Varrazzo
4460166961 Build env vars on windows less verbose
Copied from psycopg2-wheels
2018-01-29 01:56:10 +00:00
Daniele Varrazzo
a84c9723b4 Merge branch 'separate-binary' into maint_2_7 2018-01-29 01:38:17 +00:00
Daniele Varrazzo
06ea808599 Mention new wheel packages in news file
Close #543.
2018-01-29 01:38:03 +00:00
Daniele Varrazzo
0a148dbe01 Silence warning on import failing a test 2018-01-29 00:51:05 +00:00
Daniele Varrazzo
5eb4185afc Dropped warning about unsafe cursor names
It was long made secure
2018-01-25 21:58:05 +00:00
Daniele Varrazzo
7e16a37476 Document the psycopg2-binary package 2018-01-16 18:28:27 +00:00
Daniele Varrazzo
76765aeb39 Print info about the binary package on build failed
The idea is to release a package 'psycopg2-binary' to allow installing
binary, and leave the psycopg2 package to be source only, to avoid
pushing the unreliability of the wheel pacakge by default (see issue #543).

Version number bumped to test with new packages.
2018-01-13 20:11:18 +00:00
Daniele Varrazzo
ac114d2188 Merge branch 'fix-idempotence-check' into maint_2_7 2018-01-11 02:23:06 +00:00
Daniele Varrazzo
8aaf8f1902 Fixed idempotence check changing connection characteristics 2018-01-11 02:08:27 +00:00
Daniele Varrazzo
ba50548b30 'key' docs in getconn() improved
Fix #569.
2018-01-11 00:16:49 +00:00
Daniele Varrazzo
f3685fe5ac Fixed test in asian time zones
Fix #652
2018-01-11 08:53:57 +09:00
Daniele Varrazzo
7be099bd80 Merge branch 'mogrify-on-closed-cursor' into maint_2_7 2018-01-11 08:53:52 +09:00
Daniele Varrazzo
bf58f3b194 Merge branch 'datetime-macro-accessors' into maint_2_7 2018-01-10 23:26:48 +00:00
Daniele Varrazzo
9c5bf36791 'cursor.mogrify()' can be called on closed cursors
Fix #579.
2018-01-10 23:26:11 +00:00
Daniele Varrazzo
1477482e59 Fixed pydatetime macros for Python 3.2 2018-01-10 23:12:01 +00:00
Daniele Varrazzo
54cd0c66af pypi3 fix noted in the news file 2018-01-10 22:54:03 +00:00
Daniele Varrazzo
b99c8ab2de Moved datatime compatibility macros with others 2018-01-10 22:53:56 +00:00
Glyph
632cfe0617 define a "polyfill" inline for python 2 compatibility 2018-01-10 22:53:43 +00:00
Glyph
ae227effe2 use accessor macros for pypy3 compatibility 2018-01-10 22:53:37 +00:00
Daniele Varrazzo
72ed62dc8c Fixed NEWS file entries
- 2.6.3 has not been released (yet). Fixes for bug #420, bug #462 were
  relased in 2.7.
- Added missing report for bug #489 fixed in 2.7.
2017-12-01 16:24:59 +00:00
Daniele Varrazzo
b8a9469f45 Merge branch 'bug-633' into maint_2_7 2017-11-29 15:38:32 +00:00
Daniele Varrazzo
a5fd594ea4 Collect rowcount in executemany even when discarding results
Closes #633.
2017-11-29 15:38:02 +00:00
Daniele Varrazzo
ad7fd52f30 Build and test packages with libpq 10.1 and OpenSSL 1.0.2m 2017-11-28 17:16:53 +00:00
Daniele Varrazzo
5f0d411d11 Merge branch 'fix-libpq-version' into maint_2_7 2017-11-28 17:16:49 +00:00
Daniele Varrazzo
a5f0036a8e Fixed __libpq_version__ for Postgres >= 10.1
The version should be considered as 10.0.1; the number was generated as
10.1.0 instead.

Version number bumped to test building new wheels packages.

Fix #632.
2017-11-28 16:53:23 +00:00
Daniele Varrazzo
1619bae1e3 Merge branch 'python3_mintimeloggingconnection' into maint_2_7 2017-11-28 03:24:09 +00:00
Daniele Varrazzo
74059a0dbe Merge branch 'solaris-support' into maint_2_7 2017-11-28 03:14:58 +00:00
Daniele Varrazzo
a1831ef498 Mention solaris support in NEWS
Close #532.
2017-11-28 03:13:25 +00:00
Daniele Varrazzo
afb42e7625 Merge remote-tracking branch 'jdufresne/py2' 2017-11-28 02:59:17 +00:00
Daniele Varrazzo
b4e658d29b Merge remote-tracking branch 'jdufresne/license' 2017-11-28 02:39:54 +00:00
Jon Dufresne
296abf735e Add some missing trove classifiers for general Python support 2017-11-27 07:30:50 -08:00
Jon Dufresne
5ddac80cec Include license file in the generated wheel package
The wheel package format supports including the license file. This is
done using the [metadata] section in the setup.cfg file. For additional
information on this feature, see:

https://wheel.readthedocs.io/en/stable/index.html#including-the-license-in-the-generated-wheel-file
2017-11-26 10:42:30 -08:00
Daniele Varrazzo
858bc3d42a
Merge pull request #616 from jdufresne/modern-exceptions
Use modern except syntax throughout project
2017-11-21 11:45:34 +00:00
Jon Dufresne
390e43fcb1 Use modern except syntax throughout project
The syntax "except Exception, exc:" is deprecated. All Python versions
supported by psycopg2 support the newer, modern syntax. Forward
compatible with future Python versions.
2017-11-20 20:00:35 -08:00
Daniele Varrazzo
7a2dd85caa NEWS updated after last bugfix. 2017-11-16 16:07:34 +00:00
Federico Di Gregorio
13b0b9d3e3
Merge pull request #614 from fogzot/fix-613
Don't cast point arrays to float arrays (fixes: #613)
2017-11-16 10:09:28 +01:00
Federico Di Gregorio
5983b96c55 Don't cast point arrays to float arrays (fixes: #613) 2017-11-16 10:07:27 +01:00
Daniele Varrazzo
d88d8f9619 Added PostgreSQL 10 in the list of supported servers 2017-11-06 18:38:22 +00:00
Daniele Varrazzo
9614e7241b Further docs cleanup
Recent Sphinx versions seem overly aggressive in autodetecting python,
or I just didn't notice the errors, so be explicit in what language to
use with code examples.
2017-11-06 18:34:23 +00:00
Daniele Varrazzo
4f1505857b Parameters passing docs improved
Every point has an example and all the example show wrong/correct. Nice
rhythm.

Among the improvements, added point saying explicitly "thou shall not
quote placeholders".  Quoted placeholders will just fail except in the
most contrived cases (a statement raising an exception with all the
strings except with the attack ones...), and an example in the following
section explicitly notes "no quotes", but apparenty someone still thinks
this is not documented enough? (see issue #611) so let's just write it
plain and clear into the list of commandments.
2017-11-06 17:31:35 +00:00
My Karlsson
f54783ae6e Emulate timeradd and timersub on Solaris
Solaris does not have timeradd and timersub. Add solaris_support.c which
provides emulated versions of them on Solaris.
2017-10-26 20:21:30 +02:00
60 changed files with 974 additions and 423 deletions

View File

@ -13,52 +13,19 @@ environment:
matrix: matrix:
# For Python versions available on Appveyor, see # For Python versions available on Appveyor, see
# http://www.appveyor.com/docs/installed-software#python # http://www.appveyor.com/docs/installed-software#python
- {PYVER: "27", PYTHON_ARCH: "32"}
- {PYVER: "27", PYTHON_ARCH: "64"}
- {PYVER: "33", PYTHON_ARCH: "32"}
- {PYVER: "33", PYTHON_ARCH: "64"}
- {PYVER: "34", PYTHON_ARCH: "32"}
- {PYVER: "34", PYTHON_ARCH: "64"}
- {PYVER: "35", PYTHON_ARCH: "32"}
- {PYVER: "35", PYTHON_ARCH: "64"}
- {PYVER: "36", PYTHON_ARCH: "32"}
- {PYVER: "36", PYTHON_ARCH: "64"}
# Py 2.7 = VS Ver. 9.0 (VS 2008) OPENSSL_VERSION: "1_0_2n"
# Py 3.3, 3.4 = VS Ver. 10.0 (VS 2010) POSTGRES_VERSION: "10_1"
# Py 3.5, 3.6 = VS Ver. 14.0 (VS 2015)
- PYTHON: C:\Python27-x64
PYTHON_ARCH: 64
VS_VER: 9.0
- PYTHON: C:\Python27
PYTHON_ARCH: 32
VS_VER: 9.0
- PYTHON: C:\Python36-x64
PYTHON_ARCH: 64
VS_VER: 14.0
- PYTHON: C:\Python36
PYTHON_ARCH: 32
VS_VER: 14.0
- PYTHON: C:\Python35-x64
PYTHON_ARCH: 64
VS_VER: 14.0
- PYTHON: C:\Python35
PYTHON_ARCH: 32
VS_VER: 14.0
- PYTHON: C:\Python34-x64
DISTUTILS_USE_SDK: '1'
PYTHON_ARCH: 64
VS_VER: 10.0
- PYTHON: C:\Python34
PYTHON_ARCH: 32
VS_VER: 10.0
- PYTHON: C:\Python33-x64
DISTUTILS_USE_SDK: '1'
PYTHON_ARCH: 64
VS_VER: 10.0
- PYTHON: C:\Python33
PYTHON_ARCH: 32
VS_VER: 10.0
PSYCOPG2_TESTDB: psycopg2_test PSYCOPG2_TESTDB: psycopg2_test
PSYCOPG2_TESTDB_USER: postgres PSYCOPG2_TESTDB_USER: postgres
@ -86,7 +53,22 @@ cache:
init: init:
# Uncomment next line to get RDP access during the build. # Uncomment next line to get RDP access during the build.
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) #- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
#
# Set env variable according to the build environment
- SET PYTHON=C:\Python%PYVER%
- IF "%PYTHON_ARCH%"=="64" SET PYTHON=%PYTHON%-x64
# Py 2.7 = VS Ver. 9.0 (VS 2008)
# Py 3.3, 3.4 = VS Ver. 10.0 (VS 2010)
# Py 3.5, 3.6 = VS Ver. 14.0 (VS 2015)
- IF "%PYVER%"=="27" SET VS_VER=9.0
- IF "%PYVER%"=="33" SET VS_VER=10.0
- IF "%PYVER%"=="34" SET VS_VER=10.0
- IF "%PYVER%"=="35" SET VS_VER=14.0
- IF "%PYVER%"=="36" SET VS_VER=14.0
- IF "%VS_VER%"=="10.0" IF "%PYTHON_ARCH%"=="64" SET DISTUTILS_USE_SDK=1
# Set Python to the path # Set Python to the path
- SET PATH=%PYTHON%;%PYTHON%\Scripts;C:\Program Files\Git\mingw64\bin;%PATH% - SET PATH=%PYTHON%;%PYTHON%\Scripts;C:\Program Files\Git\mingw64\bin;%PATH%
@ -162,8 +144,8 @@ install:
} }
# Download OpenSSL source # Download OpenSSL source
- CD C:\Others - CD C:\Others
- IF NOT EXIST OpenSSL_1_0_2l.zip ( - IF NOT EXIST OpenSSL_%OPENSSL_VERSION%.zip (
curl -fsSL -o OpenSSL_1_0_2l.zip https://github.com/openssl/openssl/archive/OpenSSL_1_0_2l.zip curl -fsSL -o OpenSSL_%OPENSSL_VERSION%.zip https://github.com/openssl/openssl/archive/OpenSSL_%OPENSSL_VERSION%.zip
) )
# To use OpenSSL >= 1.1.0, both libpq and psycopg build environments have # To use OpenSSL >= 1.1.0, both libpq and psycopg build environments have
@ -175,15 +157,15 @@ install:
# - nmake build_libs install_dev # - nmake build_libs install_dev
- IF NOT EXIST %OPENSSLTOP%\lib\ssleay32.lib ( - IF NOT EXIST %OPENSSLTOP%\lib\ssleay32.lib (
CD %BUILD_DIR% && CD %BUILD_DIR% &&
7z x C:\Others\OpenSSL_1_0_2l.zip && 7z x C:\Others\OpenSSL_%OPENSSL_VERSION%.zip &&
CD openssl-OpenSSL_1_0_2l && CD openssl-OpenSSL_%OPENSSL_VERSION% &&
perl Configure %TARGET% no-asm no-shared no-zlib --prefix=%OPENSSLTOP% --openssldir=%OPENSSLTOP% && perl Configure %TARGET% no-asm no-shared no-zlib --prefix=%OPENSSLTOP% --openssldir=%OPENSSLTOP% &&
CALL ms\%DO% && CALL ms\%DO% &&
nmake -f ms\nt.mak init headers lib && nmake -f ms\nt.mak init headers lib &&
COPY inc32\openssl\*.h %OPENSSLTOP%\include\openssl && COPY inc32\openssl\*.h %OPENSSLTOP%\include\openssl &&
COPY out32\*.lib %OPENSSLTOP%\lib && COPY out32\*.lib %OPENSSLTOP%\lib &&
CD %BASE_DIR% && CD %BASE_DIR% &&
RMDIR /S /Q %BUILD_DIR%\openssl-OpenSSL_1_0_2l RMDIR /S /Q %BUILD_DIR%\openssl-OpenSSL_%OPENSSL_VERSION%
) )
# Setup directories for building PostgreSQL librarires # Setup directories for building PostgreSQL librarires
@ -197,8 +179,8 @@ install:
# Download PostgreSQL source # Download PostgreSQL source
- CD C:\Others - CD C:\Others
- IF NOT EXIST postgres-REL_10_0.zip ( - IF NOT EXIST postgres-REL_%POSTGRES_VERSION%.zip (
curl -fsSL -o postgres-REL_10_0.zip https://github.com/postgres/postgres/archive/REL_10_0.zip curl -fsSL -o postgres-REL_%POSTGRES_VERSION%.zip https://github.com/postgres/postgres/archive/REL_%POSTGRES_VERSION%.zip
) )
# Setup build config file (config.pl) # Setup build config file (config.pl)
@ -209,11 +191,11 @@ install:
# Prepare local include directory for building from # Prepare local include directory for building from
# Build pg_config in place # Build pg_config in place
# NOTE: Cannot set and use the same variable inside an IF # NOTE: Cannot set and use the same variable inside an IF
- SET PGBUILD=%BUILD_DIR%\postgres-REL_10_0 - SET PGBUILD=%BUILD_DIR%\postgres-REL_%POSTGRES_VERSION%
- IF NOT EXIST %PGTOP%\lib\libpq.lib ( - IF NOT EXIST %PGTOP%\lib\libpq.lib (
CD %BUILD_DIR% && CD %BUILD_DIR% &&
7z x C:\Others\postgres-REL_10_0.zip && 7z x C:\Others\postgres-REL_%POSTGRES_VERSION%.zip &&
CD postgres-REL_10_0\src\tools\msvc && CD postgres-REL_%POSTGRES_VERSION%\src\tools\msvc &&
ECHO $config-^>{ldap} = 0; > config.pl && ECHO $config-^>{ldap} = 0; > config.pl &&
ECHO $config-^>{openssl} = "%OPENSSLTOP:\=\\%"; >> config.pl && ECHO $config-^>{openssl} = "%OPENSSLTOP:\=\\%"; >> config.pl &&
ECHO.>> config.pl && ECHO.>> config.pl &&

13
LICENSE
View File

@ -1,5 +1,5 @@
psycopg2 and the LGPL psycopg2 and the LGPL
===================== ---------------------
psycopg2 is free software: you can redistribute it and/or modify it psycopg2 is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published under the terms of the GNU Lesser General Public License as published
@ -29,15 +29,10 @@ If not, see <http://www.gnu.org/licenses/>.
Alternative licenses Alternative licenses
==================== --------------------
If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e., The following BSD-like license applies (at your option) to the files following
every file inside the ZPsycopgDA directory) user the ZPL license as the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``:
published on the Zope web site, http://www.zope.org/Resources/ZPL.
Also, the following BSD-like license applies (at your option) to the
files following the pattern psycopg/adapter*.{h,c} and
psycopg/microprotocol*.{h,c}:
Permission is granted to anyone to use this software for any purpose, Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it including commercial applications, and to alter it and redistribute it

74
NEWS
View File

@ -1,24 +1,80 @@
Current release Current release
--------------- ---------------
What's new in psycopg 2.7.6.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed binary package broken on OS X 10.12 (:ticket:`#807`).
- Wheel package compiled against PostgreSQL 11.1 libpq.
What's new in psycopg 2.7.6
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Close named cursors if exist, even if `~cursor.execute()` wasn't called
(:ticket:`#746`).
- Fixed building on modern FreeBSD versions with Python 3.7 (:ticket:`#755`).
- Fixed hang trying to :sql:`COPY` via `~cursor.execute()` in asynchronous
connections (:ticket:`#781`).
- Fixed adaptation of arrays of empty arrays (:ticket:`#788`).
- Fixed segfault accessing the connection's `~connection.readonly` and
`~connection.deferrable` attributes repeatedly (:ticket:`#790`).
- `~psycopg2.extras.execute_values()` accepts `~psycopg2.sql.Composable`
objects (:ticket:`#794`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 11.
- Wheel package compiled against PostgreSQL 10.5 libpq and OpenSSL 1.0.2p.
What's new in psycopg 2.7.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Allow non-ascii chars in namedtuple fields (regression introduced fixing
:ticket:`#211`).
- Fixed adaptation of arrays of arrays of nulls (:ticket:`#325`).
- Fixed building on Solaris 11 and derivatives such as SmartOS and illumos
(:ticket:`#677`).
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
- Allow string subclasses in connection and other places (:ticket:`#679`).
- Don't raise an exception closing an unused named cursor (:ticket:`#716`).
- Wheel package compiled against PostgreSQL 10.4 libpq and OpenSSL 1.0.2o.
What's new in psycopg 2.7.4 What's new in psycopg 2.7.4
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed `MinTimeLoggingCursor` on Python 3 (:ticket:`#609`). - Moving away from installing the wheel package by default.
Packages installed from wheel raise a warning on import. Added package
``psycopg2-binary`` to install from wheel instead (:ticket:`#543`).
- Convert fields names into valid Python identifiers in
`~psycopg2.extras.NamedTupleCursor` (:ticket:`#211`).
- Fixed Solaris 10 support (:ticket:`#532`).
- `cursor.mogrify()` can be called on closed cursors (:ticket:`#579`).
- Fixed setting session characteristics in corner cases on autocommit
connections (:ticket:`#580`).
- Fixed `~psycopg2.extras.MinTimeLoggingCursor` on Python 3 (:ticket:`#609`).
- Fixed parsing of array of points as floats (:ticket:`#613`).
- Fixed `~psycopg2.__libpq_version__` building with libpq >= 10.1
(:ticket:`632`).
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING`
statements (:ticket:`633`).
- Fixed compatibility problem with pypy3 (:ticket:`#649`).
- Wheel packages compiled against PostgreSQL 10.1 libpq and OpenSSL 1.0.2n.
- Wheel packages for Python 2.6 no more available (support dropped from
wheel building infrastructure).
What's new in psycopg 2.7.3.2 What's new in psycopg 2.7.3.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Wheel package compiled against PostgreSQL 10.0 libpq and OpenSSL 1.0.2l - Wheel package compiled against PostgreSQL 10.0 libpq and OpenSSL 1.0.2l
(:tickets:`#601, #602`) (:tickets:`#601, #602`).
What's new in psycopg 2.7.3.1 What's new in psycopg 2.7.3.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Dropped libresolv from wheel package to avoid incompatibility with - Dropped libresolv from wheel package to avoid incompatibility with
glibc 2.26 (wheels ticket #2) glibc 2.26 (wheels ticket #2).
What's new in psycopg 2.7.3 What's new in psycopg 2.7.3
@ -113,9 +169,13 @@ New features:
Bug fixes: Bug fixes:
- Throw an exception trying to pass ``NULL`` chars as parameters
(:ticket:`#420`).
- Fixed error caused by missing decoding `~psycopg2.extras.LoggingConnection` - Fixed error caused by missing decoding `~psycopg2.extras.LoggingConnection`
(:ticket:`#483`). (:ticket:`#483`).
- Fixed integer overflow in :sql:`interval` seconds (:ticket:`#512`). - Fixed integer overflow in :sql:`interval` seconds (:ticket:`#512`).
- Make `~psycopg2.extras.Range` objects picklable (:ticket:`#462`).
- Fixed version parsing and building with PostgreSQL 10 (:ticket:`#489`).
Other changes: Other changes:
@ -129,14 +189,6 @@ Other changes:
(:ticket:`#506`) (:ticket:`#506`)
What's new in psycopg 2.6.3
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Throw an exception trying to pass ``NULL`` chars as parameters
(:ticket:`#420`).
- Make `~psycopg2.extras.Range` objects picklable (:ticket:`#462`).
What's new in psycopg 2.6.2 What's new in psycopg 2.6.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -25,29 +25,40 @@ Documentation is included in the ``doc`` directory and is `available online`__.
.. __: http://initd.org/psycopg/docs/ .. __: http://initd.org/psycopg/docs/
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
Installation Installation
------------ ------------
If your ``pip`` version supports wheel_ packages it should be possible to Building Psycopg requires a few prerequisites (a C compiler, some development
install a binary version of Psycopg including all the dependencies from PyPI_. packages): please check the install_ and the faq_ documents in the ``doc`` dir
Just run:: or online for the details.
If prerequisites are met, you can install psycopg like any other Python
package, using ``pip`` to download it from PyPI_::
$ pip install -U pip # make sure your pip is up-to-date
$ pip install psycopg2 $ pip install psycopg2
If you want to build Psycopg from source you will need some prerequisites (a C or using ``setup.py`` if you have downloaded the source package locally::
compiler, development packages): please check the install_ and the faq_
documents in the ``doc`` dir for the details.
.. _wheel: http://pythonwheels.com/ $ python setup.py build
.. _PyPI: https://pypi.python.org/pypi/psycopg2 $ sudo python setup.py install
You can also obtain a stand-alone package, not requiring a compiler or
external libraries, by installing the `psycopg2-binary`_ package from PyPI::
$ pip install psycopg2-binary
The binary package is a practical choice for development and testing but in
production it is advised to use the package built from sources.
.. _PyPI: https://pypi.org/project/psycopg2/
.. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/
.. _install: http://initd.org/psycopg/docs/install.html#install-from-source .. _install: http://initd.org/psycopg/docs/install.html#install-from-source
.. _faq: http://initd.org/psycopg/docs/faq.html#faq-compile .. _faq: http://initd.org/psycopg/docs/faq.html#faq-compile
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
.. __: http://initd.org/psycopg/ .. __: http://initd.org/psycopg/

View File

@ -29,8 +29,6 @@ doctest:
upload: upload:
# this command requires ssh configured to the proper target # this command requires ssh configured to the proper target
tar czf - -C html . | ssh psycoweb tar xzvf - -C docs/current tar czf - -C html . | ssh psycoweb tar xzvf - -C docs/current
# this command requires a .pypirc with the right privileges
# python src/tools/pypi_docs_upload.py psycopg2 $$(pwd)/html
clean: clean:
$(MAKE) $(SPHOPTS) -C src $@ $(MAKE) $(SPHOPTS) -C src $@

View File

@ -100,5 +100,5 @@ Test packages may be uploaded on the `PyPI testing site`__ using::
assuming `proper configuration`__ of ``~/.pypirc``. assuming `proper configuration`__ of ``~/.pypirc``.
.. __: https://testpypi.python.org/pypi/psycopg2 .. __: https://test.pypi.org/project/psycopg2/
.. __: https://wiki.python.org/moin/TestPyPI .. __: https://wiki.python.org/moin/TestPyPI

View File

@ -295,7 +295,9 @@ something to read::
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'` Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
in a separate :program:`psql` shell, the output may look similar to:: in a separate :program:`psql` shell, the output may look similar to:
.. code-block:: none
Waiting for notifications on channel 'test' Waiting for notifications on channel 'test'
Timeout Timeout
@ -483,7 +485,7 @@ details. You can check the `psycogreen`_ project for further informations and
resources about the topic. resources about the topic.
.. _coroutine: http://en.wikipedia.org/wiki/Coroutine .. _coroutine: http://en.wikipedia.org/wiki/Coroutine
.. _greenlet: http://pypi.python.org/pypi/greenlet .. _greenlet: https://pypi.org/project/greenlet/
.. _green threads: http://en.wikipedia.org/wiki/Green_threads .. _green threads: http://en.wikipedia.org/wiki/Green_threads
.. _Eventlet: http://eventlet.net/ .. _Eventlet: http://eventlet.net/
.. _gevent: http://www.gevent.org/ .. _gevent: http://www.gevent.org/

View File

@ -61,9 +61,8 @@ except ImportError:
release = version release = version
intersphinx_mapping = { intersphinx_mapping = {
'py': ('http://docs.python.org/2', None), 'py': ('https://docs.python.org/3', None),
'py3': ('http://docs.python.org/3', None), }
}
# Pattern to generate links to the bug tracker # Pattern to generate links to the bug tracker
ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s' ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s'
@ -101,6 +100,10 @@ default_role = 'obj'
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False #show_authors = False
# Using 'python' instead of the default gives warnings if parsing an example
# fails, instead of defaulting to none
highlight_language = 'python'
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'

View File

@ -41,11 +41,6 @@ The ``connection`` class
previously only valid PostgreSQL identifiers were accepted as previously only valid PostgreSQL identifiers were accepted as
cursor name. cursor name.
.. warning::
It is unsafe to expose the *name* to an untrusted source, for
instance you shouldn't allow *name* to be read from a HTML form.
Consider it as part of the query, not as a query parameter.
The *cursor_factory* argument can be used to create non-standard The *cursor_factory* argument can be used to create non-standard
cursors. The class returned must be a subclass of cursors. The class returned must be a subclass of
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for `psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for

View File

@ -50,7 +50,7 @@ An example of the available constants defined in the module:
'42P01' '42P01'
Constants representing all the error values defined by PostgreSQL versions Constants representing all the error values defined by PostgreSQL versions
between 8.1 and 10 are included in the module. between 8.1 and 11 are included in the module.
.. autofunction:: lookup(code) .. autofunction:: lookup(code)

View File

@ -99,20 +99,6 @@ Real dictionary cursor
.. versionadded:: 2.3 .. versionadded:: 2.3
These objects require :py:func:`collections.namedtuple` to be found, so it is
available out-of-the-box only from Python 2.6. Anyway, the namedtuple
implementation is compatible with previous Python versions, so all you
have to do is to `download it`__ and make it available where we
expect it to be... ::
from somewhere import namedtuple
import collections
collections.namedtuple = namedtuple
from psycopg.extras import NamedTupleConnection
# ...
.. __: http://code.activestate.com/recipes/500261-named-tuples/
.. autoclass:: NamedTupleCursor .. autoclass:: NamedTupleCursor
.. autoclass:: NamedTupleConnection .. autoclass:: NamedTupleConnection
@ -403,7 +389,7 @@ The individual messages in the replication stream are represented by
class LogicalStreamConsumer(object): class LogicalStreamConsumer(object):
... # ...
def __call__(self, msg): def __call__(self, msg):
self.process_message(msg.payload) self.process_message(msg.payload)
@ -501,7 +487,7 @@ The individual messages in the replication stream are represented by
from datetime import datetime from datetime import datetime
def consume(msg): def consume(msg):
... # ...
keepalive_interval = 10.0 keepalive_interval = 10.0
while True: while True:
@ -563,7 +549,7 @@ that the last `!simplejson` version supporting Python 2.4 is the 2.0.9.
.. |pgjson| replace:: :sql:`json` .. |pgjson| replace:: :sql:`json`
.. |jsonb| replace:: :sql:`jsonb` .. |jsonb| replace:: :sql:`jsonb`
.. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html .. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html
.. _simplejson: http://pypi.python.org/pypi/simplejson/ .. _simplejson: https://pypi.org/project/simplejson/
In order to pass a Python object to the database as query argument you can use In order to pass a Python object to the database as query argument you can use
the `Json` adapter:: the `Json` adapter::

View File

@ -306,7 +306,9 @@ I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file
API support (*i.e.* the libpq used at compile time was at least 9.3) but API support (*i.e.* the libpq used at compile time was at least 9.3) but
at runtime an older libpq dynamic library is found. at runtime an older libpq dynamic library is found.
You can use:: You can use:
.. code-block:: shell
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq $ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq

View File

@ -48,6 +48,7 @@ Psycopg 2 is both Unicode and Python 3 friendly.
errorcodes errorcodes
faq faq
news news
license
.. ifconfig:: builder != 'text' .. ifconfig:: builder != 'text'

View File

@ -12,16 +12,6 @@ to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
there is an experimental `porting of Psycopg for Ctypes`__, but it is not as there is an experimental `porting of Psycopg for Ctypes`__, but it is not as
mature as the C implementation yet. mature as the C implementation yet.
The current `!psycopg2` implementation supports:
..
NOTE: keep consistent with setup.py and the /features/ page.
- Python 2 versions from 2.6 to 2.7
- Python 3 versions from 3.2 to 3.6
- PostgreSQL server versions from 7.4 to 9.6
- PostgreSQL client library version from 9.1
.. _PostgreSQL: http://www.postgresql.org/ .. _PostgreSQL: http://www.postgresql.org/
.. _Python: http://www.python.org/ .. _Python: http://www.python.org/
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html .. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
@ -32,94 +22,20 @@ The current `!psycopg2` implementation supports:
.. index:: .. index::
single: Install; from PyPI single: Prerequisites
single: Install; wheel
single: Wheel
Binary install from PyPI Prerequisites
------------------------ -------------
`!psycopg2` is `available on PyPI`__ in the form of wheel_ packages for the The current `!psycopg2` implementation supports:
most common platform (Linux, OSX, Windows): this should make you able to
install a binary version of the module including all the dependencies simply
using:
.. code-block:: console ..
NOTE: keep consistent with setup.py and the /features/ page.
$ pip install psycopg2 - Python 2 versions from 2.6 to 2.7
- Python 3 versions from 3.2 to 3.7
Make sure to use an up-to-date version of :program:`pip` (you can upgrade it - PostgreSQL server versions from 7.4 to 10
using something like ``pip install -U pip``) - PostgreSQL client library version from 9.1
.. __: PyPI_
.. _PyPI: https://pypi.python.org/pypi/psycopg2/
.. _wheel: http://pythonwheels.com/
.. note::
The binary packages come with their own versions of a few C libraries,
among which ``libpq`` and ``libssl``, which will be used regardless of other
libraries available on the client: upgrading the system libraries will not
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
source if you want to maintain binary upgradeability.
.. warning::
Because the `!psycopg` wheel package uses its own ``libssl`` binary, it is
incompatible with other extension modules binding with ``libssl`` as well,
for instance the Python `ssl` module: the result will likely be a
segfault. If you need using both `!psycopg2` and other libraries using
``libssl`` please :ref:`disable the use of wheel packages for Psycopg
<disable-wheel>`.
.. index::
single: Install; disable wheel
single: Wheel; disable
.. _disable-wheel:
Disabling wheel packages
^^^^^^^^^^^^^^^^^^^^^^^^
If you want to disable the use of wheel binary packages and use the system
system libraries available on your client you can use the :command:`pip`
|--no-binary option|__:
.. code-block:: console
$ pip install --no-binary psycopg2
.. |--no-binary option| replace:: ``--no-binary`` option
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
which can be specified in your :file:`requirements.txt` files too, e.g. use:
.. code-block:: none
psycopg2>=2.7,<2.8 --no-binary psycopg2
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
always compile it from source. Of course in this case you will have to meet
the :ref:`build prerequisites <build-prerequisites>`.
.. index::
single: Install; from source
.. _install-from-source:
Install from source
-------------------
.. _source-package:
You can download a copy of Psycopg source files from the `Psycopg download
page`__ or from PyPI_.
.. __: http://initd.org/psycopg/download/
@ -128,8 +44,10 @@ page`__ or from PyPI_.
Build prerequisites Build prerequisites
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
These notes illustrate how to compile Psycopg on Linux. If you want to compile The build prerequisites are to be met in order to install Psycopg from source
Psycopg on other platforms you may have to adjust some details accordingly. code, from a source distribution package, GitHub_ or from PyPI.
.. _GitHub: https://github.com/psycopg/psycopg2
Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install
it from sources you will need: it from sources you will need:
@ -161,6 +79,12 @@ it from sources you will need:
Once everything is in place it's just a matter of running the standard: Once everything is in place it's just a matter of running the standard:
.. code-block:: console
$ pip install psycopg2
or, from the directory containing the source code:
.. code-block:: console .. code-block:: console
$ python setup.py build $ python setup.py build
@ -197,12 +121,101 @@ which is OS-dependent (for instance setting a suitable
.. index::
single: Install; from PyPI
single: Install; wheel
single: Wheel
Binary install from PyPI
------------------------
`!psycopg2` is also `available on PyPI`__ in the form of wheel_ packages for
the most common platform (Linux, OSX, Windows): this should make you able to
install a binary version of the module, not requiring the above build or
runtime prerequisites.
.. note::
The ``-binary`` package is meant for beginners to start playing with
Python and PostgreSQL without the need to meet the build requirements.
If you are the maintainer of a publish package depending on `!psycopg2`
you shouldn't use ``psycopg2-binary`` as a module dependency. For
production use you are advised to use the source distribution.
Make sure to use an up-to-date version of :program:`pip` (you can upgrade it
using something like ``pip install -U pip``), then you can run:
.. code-block:: console
$ pip install psycopg2-binary
.. __: PyPI-binary_
.. _PyPI-binary: https://pypi.org/project/psycopg2-binary/
.. _wheel: http://pythonwheels.com/
.. note::
The binary packages come with their own versions of a few C libraries,
among which ``libpq`` and ``libssl``, which will be used regardless of other
libraries available on the client: upgrading the system libraries will not
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
source if you want to maintain binary upgradeability.
.. warning::
The `!psycopg2` wheel package comes packaged, among the others, with its
own ``libssl`` binary. This may create conflicts with other extension
modules binding with ``libssl`` as well, for instance with the Python
`ssl` module: in some cases, under concurrency, the interaction between
the two libraries may result in a segfault. In case of doubts you are
advised to use a package built from source.
.. index::
single: Install; disable wheel
single: Wheel; disable
.. _disable-wheel:
Disabling wheel packages for Psycopg 2.7
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In version 2.7.x, `pip install psycopg2` would have tried to install the wheel
binary package of Psycopg. Because of the problems the wheel package have
displayed, `psycopg2-binary` has become a separate package, and from 2.8 it
has become the only way to install the binary package.
If you are using psycopg 2.7 and you want to disable the use of wheel binary
packages, relying on the system libraries available on your client, you
can use the :command:`pip` |--no-binary option|__, e.g.:
.. code-block:: console
$ pip install --no-binary :all: psycopg2
.. |--no-binary option| replace:: ``--no-binary`` option
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
which can be specified in your :file:`requirements.txt` files too, e.g. use:
.. code-block:: none
psycopg2>=2.7,<2.8 --no-binary psycopg2
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
always compile it from source. Of course in this case you will have to meet
the :ref:`build prerequisites <build-prerequisites>`.
.. index:: .. index::
single: setup.py single: setup.py
single: setup.cfg single: setup.cfg
Non-standard builds Non-standard builds
^^^^^^^^^^^^^^^^^^^ -------------------
If you have less standard requirements such as: If you have less standard requirements such as:
@ -242,7 +255,7 @@ order to create a debug package:
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the - Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
``define`` option. ``define`` option.
- :ref:`Compile and install <source-package>` the package. - :ref:`Compile and install <build-prerequisites>` the package.
- Set the :envvar:`PSYCOPG_DEBUG` environment variable: - Set the :envvar:`PSYCOPG_DEBUG` environment variable:
@ -300,10 +313,14 @@ Try the following. *In order:*
- Google for `!psycopg2` *your error message*. Especially useful the week - Google for `!psycopg2` *your error message*. Especially useful the week
after the release of a new OS X version. after the release of a new OS X version.
- Write to the `Mailing List`__. - Write to the `Mailing List`_.
- If you think that you have discovered a bug, test failure or missing feature
please raise a ticket in the `bug tracker`_.
- Complain on your blog or on Twitter that `!psycopg2` is the worst package - Complain on your blog or on Twitter that `!psycopg2` is the worst package
ever and about the quality time you have wasted figuring out the correct ever and about the quality time you have wasted figuring out the correct
:envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you. :envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you.
.. __: https://lists.postgresql.org/mj/mj_wwwusr?func=lists-long-full&extra=psycopg .. _mailing list: https://lists.postgresql.org/mj/mj_wwwusr?func=lists-long-full&extra=psycopg
.. _bug tracker: https://github.com/psycopg/psycopg2/issues

7
doc/src/license.rst Normal file
View File

@ -0,0 +1,7 @@
.. index::
single: License
License
=======
.. include:: ../../LICENSE

View File

@ -1,3 +1,7 @@
.. index::
single: Release notes
single: News
Release notes Release notes
============= =============

View File

@ -24,13 +24,18 @@ directly in the client application.
.. method:: getconn(key=None) .. method:: getconn(key=None)
Get a free connection and assign it to *key* if not `!None`. Get a free connection from the pool.
The *key* parameter is optional: if used, the connection will be
associated to the key and calling `!getconn()` with the same key again
will return the same connection.
.. method:: putconn(conn, key=None, close=False) .. method:: putconn(conn, key=None, close=False)
Put away a connection. Put away a connection.
If *close* is `!True`, discard the connection from the pool. If *close* is `!True`, discard the connection from the pool.
*key* should be used consistently with `getconn()`.
.. method:: closeall .. method:: closeall

View File

@ -48,7 +48,7 @@ The main entry points of Psycopg are:
- The class `connection` encapsulates a database session. It allows to: - The class `connection` encapsulates a database session. It allows to:
- create new `cursor`\s using the `~connection.cursor()` method to - create new `cursor` instances using the `~connection.cursor()` method to
execute database commands and queries, execute database commands and queries,
- terminate transactions using the methods `~connection.commit()` or - terminate transactions using the methods `~connection.commit()` or
@ -73,70 +73,97 @@ The main entry points of Psycopg are:
Passing parameters to SQL queries Passing parameters to SQL queries
--------------------------------- ---------------------------------
Psycopg casts Python variables to SQL literals by type. Many standard Python types Psycopg converts Python variables to SQL values using their types: the Python
are already `adapted to the correct SQL representation`__. type determines the function used to convert the object into a string
representation suitable for PostgreSQL. Many standard Python types are
already `adapted to the correct SQL representation`__.
.. __: python-types-adaptation_ .. __: python-types-adaptation_
Example: the Python function call:: Passing parameters to an SQL statement happens in functions such as
`cursor.execute()` by using ``%s`` placeholders in the SQL statement, and
passing a sequence of values as the second argument of the function. For
example the Python function call::
>>> cur.execute( >>> cur.execute("""
... """INSERT INTO some_table (an_int, a_date, a_string) ... INSERT INTO some_table (an_int, a_date, a_string)
... VALUES (%s, %s, %s);""", ... VALUES (%s, %s, %s);
... """,
... (10, datetime.date(2005, 11, 18), "O'Reilly")) ... (10, datetime.date(2005, 11, 18), "O'Reilly"))
is converted into the SQL command:: is converted into a SQL command similar to:
.. code-block:: sql
INSERT INTO some_table (an_int, a_date, a_string) INSERT INTO some_table (an_int, a_date, a_string)
VALUES (10, '2005-11-18', 'O''Reilly'); VALUES (10, '2005-11-18', 'O''Reilly');
Named arguments are supported too using :samp:`%({name})s` placeholders. Named arguments are supported too using :samp:`%({name})s` placeholders in the
Using named arguments the values can be passed to the query in any order and query and specifying the values into a mapping. Using named arguments allows
many placeholders can use the same values:: to specify the values in any order and to repeat the same value in several
places in the query::
>>> cur.execute( >>> cur.execute("""
... """INSERT INTO some_table (an_int, a_date, another_date, a_string) ... INSERT INTO some_table (an_int, a_date, another_date, a_string)
... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);""", ... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);
... """,
... {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)}) ... {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)})
Using characters ``%``, ``(``, ``)`` in the argument names is not supported.
When parameters are used, in order to include a literal ``%`` in the query you When parameters are used, in order to include a literal ``%`` in the query you
can use the ``%%`` string. Using characters ``%``, ``(``, ``)`` in the can use the ``%%`` string::
argument names is not supported.
>>> cur.execute("SELECT (%s % 2) = 0 AS even", (10,)) # WRONG
>>> cur.execute("SELECT (%s %% 2) = 0 AS even", (10,)) # correct
While the mechanism resembles regular Python strings manipulation, there are a While the mechanism resembles regular Python strings manipulation, there are a
few subtle differences you should care about when passing parameters to a few subtle differences you should care about when passing parameters to a
query: query.
- The Python string operator ``%`` is not used: the `~cursor.execute()` - The Python string operator ``%`` *must not be used*: the `~cursor.execute()`
method accepts a tuple or dictionary of values as second parameter. method accepts a tuple or dictionary of values as second parameter.
|sql-warn|__. |sql-warn|__:
.. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values .. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values
into queries into queries
.. __: sql-injection_ .. __: sql-injection_
- The variables placeholder must *always be a* ``%s``, even if a different >>> cur.execute("INSERT INTO numbers VALUES (%s, %s)" % (10, 20)) # WRONG
placeholder (such as a ``%d`` for integers or ``%f`` for floats) may look >>> cur.execute("INSERT INTO numbers VALUES (%s, %s)", (10, 20)) # correct
more appropriate::
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (42,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct
- For positional variables binding, *the second argument must always be a - For positional variables binding, *the second argument must always be a
sequence*, even if it contains a single variable. And remember that Python sequence*, even if it contains a single variable (remember that Python
requires a comma to create a single element tuple:: requires a comma to create a single element tuple)::
>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG >>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG >>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct >>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct >>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct
- Only query values should be bound via this method: it shouldn't be used to - The placeholder *must not be quoted*. Psycopg will add quotes where needed::
merge table or field names to the query. If you need to generate dynamically
an SQL query (for instance choosing dynamically a table name) you can use
the facilities provided by the `psycopg2.sql` module.
>>> cur.execute("INSERT INTO numbers VALUES ('%s')", (10,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (10,)) # correct
- The variables placeholder *must always be a* ``%s``, even if a different
placeholder (such as a ``%d`` for integers or ``%f`` for floats) may look
more appropriate::
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (10,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (10,)) # correct
- Only query values should be bound via this method: it shouldn't be used to
merge table or field names to the query (Psycopg will try quoting the table
name as a string value, generating invalid SQL). If you need to generate
dynamically SQL queries (for instance choosing dynamically a table name)
you can use the facilities provided by the `psycopg2.sql` module::
>>> cur.execute("INSERT INTO %s VALUES (%s)", ('numbers', 10)) # WRONG
>>> cur.execute( # correct
... SQL("INSERT INTO {} VALUES (%s)").format(Identifier('numbers')),
... (10,))
.. index:: Security, SQL injection .. index:: Security, SQL injection
@ -792,7 +819,9 @@ lifetime extends well after `~connection.commit()`, calling
It is also possible to use a named cursor to consume a cursor created It is also possible to use a named cursor to consume a cursor created
in some other way than using the |DECLARE| executed by in some other way than using the |DECLARE| executed by
`~cursor.execute()`. For example, you may have a PL/pgSQL function `~cursor.execute()`. For example, you may have a PL/pgSQL function
returning a cursor:: returning a cursor:
.. code-block:: postgres
CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS $$ CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS $$
BEGIN BEGIN

View File

@ -491,6 +491,7 @@ class NumberRangeAdapter(RangeAdapter):
return ("'%s%s,%s%s'" % ( return ("'%s%s,%s%s'" % (
r._bounds[0], lower, upper, r._bounds[1])).encode('ascii') r._bounds[0], lower, upper, r._bounds[1])).encode('ascii')
# TODO: probably won't work with infs, nans and other tricky cases. # TODO: probably won't work with infs, nans and other tricky cases.
register_adapter(NumericRange, NumberRangeAdapter) register_adapter(NumericRange, NumberRangeAdapter)

View File

@ -182,6 +182,7 @@ INVALID_XML_PROCESSING_INSTRUCTION = '2200T'
INVALID_INDICATOR_PARAMETER_VALUE = '22010' INVALID_INDICATOR_PARAMETER_VALUE = '22010'
SUBSTRING_ERROR = '22011' SUBSTRING_ERROR = '22011'
DIVISION_BY_ZERO = '22012' DIVISION_BY_ZERO = '22012'
INVALID_PRECEDING_OR_FOLLOWING_SIZE = '22013'
INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014' INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014'
INTERVAL_FIELD_OVERFLOW = '22015' INTERVAL_FIELD_OVERFLOW = '22015'
INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016' INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016'

View File

@ -30,10 +30,7 @@ import sys as _sys
import time as _time import time as _time
import re as _re import re as _re
try: import logging as _logging
import logging as _logging
except:
_logging = None
import psycopg2 import psycopg2
from psycopg2 import extensions as _ext from psycopg2 import extensions as _ext
@ -189,7 +186,7 @@ class DictRow(list):
def get(self, x, default=None): def get(self, x, default=None):
try: try:
return self[x] return self[x]
except: except Exception:
return default return default
def iteritems(self): def iteritems(self):
@ -363,12 +360,26 @@ class NamedTupleCursor(_cursor):
try: try:
from collections import namedtuple from collections import namedtuple
except ImportError, _exc: except ImportError as _exc:
def _make_nt(self): def _make_nt(self):
raise self._exc raise self._exc
else: else:
def _make_nt(self, namedtuple=namedtuple): def _make_nt(self, namedtuple=namedtuple):
return namedtuple("Record", [d[0] for d in self.description or ()]) # ascii except alnum and underscore
nochars = ' !"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
re_clean = _re.compile('[' + _re.escape(nochars) + ']')
def f(s):
s = re_clean.sub('_', s)
# Python identifier cannot start with numbers, namedtuple fields
# cannot start with underscore. So...
if s[0] == '_' or '0' <= s[0] <= '9':
s = 'f' + s
return s
return namedtuple(
"Record", [f(d[0]) for d in self.description or ()])
class LoggingConnection(_connection): class LoggingConnection(_connection):
@ -741,8 +752,8 @@ def wait_select(conn):
The function is an example of a wait callback to be registered with The function is an example of a wait callback to be registered with
`~psycopg2.extensions.set_wait_callback()`. This function uses `~psycopg2.extensions.set_wait_callback()`. This function uses
:py:func:`~select.select()` to wait for data available. :py:func:`~select.select()` to wait for data to become available, and
therefore is able to handle/receive SIGINT/KeyboardInterrupt.
""" """
import select import select
from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE
@ -1245,6 +1256,10 @@ def execute_values(cur, sql, argslist, template=None, page_size=100):
[(1, 20, 3), (4, 50, 6), (7, 8, 9)]) [(1, 20, 3), (4, 50, 6), (7, 8, 9)])
''' '''
from psycopg2.sql import Composable
if isinstance(sql, Composable):
sql = sql.as_string(cur)
# we can't just use sql % vals because vals is bytes: if sql is bytes # we can't just use sql % vals because vals is bytes: if sql is bytes
# there will be some decoding error because of stupid codec used, and Py3 # there will be some decoding error because of stupid codec used, and Py3
# doesn't implement % on bytes. # doesn't implement % on bytes.

View File

@ -138,7 +138,7 @@ class AbstractConnectionPool(object):
for conn in self._pool + list(self._used.values()): for conn in self._pool + list(self._used.values()):
try: try:
conn.close() conn.close()
except: except Exception:
pass pass
self.closed = True self.closed = True

View File

@ -132,6 +132,7 @@ class LocalTimezone(datetime.tzinfo):
tt = time.localtime(stamp) tt = time.localtime(stamp)
return tt.tm_isdst > 0 return tt.tm_isdst > 0
LOCAL = LocalTimezone() LOCAL = LocalTimezone()
# TODO: pre-generate some interesting time zones? # TODO: pre-generate some interesting time zones?

View File

@ -100,7 +100,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
char buffer[8]; char buffer[8];
int i; int i;
int a = obj->microseconds; int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
for (i=0; i < 6 ; i++) { for (i=0; i < 6 ; i++) {
buffer[5-i] = '0' + (a % 10); buffer[5-i] = '0' + (a % 10);
@ -109,7 +109,9 @@ _pydatetime_string_delta(pydatetimeObject *self)
buffer[6] = '\0'; buffer[6] = '\0';
return Bytes_FromFormat("'%d days %d.%s seconds'::interval", return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
obj->days, obj->seconds, buffer); PyDateTime_DELTA_GET_DAYS(obj),
PyDateTime_DELTA_GET_SECONDS(obj),
buffer);
} }
static PyObject * static PyObject *

View File

@ -38,13 +38,14 @@ list_quote(listObject *self)
{ {
/* adapt the list by calling adapt() recursively and then wrapping /* adapt the list by calling adapt() recursively and then wrapping
everything into "ARRAY[]" */ everything into "ARRAY[]" */
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL; PyObject *res = NULL;
PyObject **qs = NULL;
Py_ssize_t bufsize = 0;
char *buf = NULL, *ptr;
/* list consisting of only NULL don't work with the ARRAY[] construct /* list consisting of only NULL don't work with the ARRAY[] construct
* so we use the {NULL,...} syntax. Note however that list of lists where * so we use the {NULL,...} syntax. The same syntax is also necessary
* some element is a list of only null still fails: for that we should use * to convert array of arrays containing only nulls. */
* the '{...}' syntax uniformly but we cannot do it in the current
* infrastructure. TODO in psycopg3 */
int all_nulls = 1; int all_nulls = 1;
Py_ssize_t i, len; Py_ssize_t i, len;
@ -53,47 +54,109 @@ list_quote(listObject *self)
/* empty arrays are converted to NULLs (still searching for a way to /* empty arrays are converted to NULLs (still searching for a way to
insert an empty array in postgresql */ insert an empty array in postgresql */
if (len == 0) return Bytes_FromString("'{}'"); if (len == 0) {
/* it cannot be ARRAY[] because it would make empty lists unusable
* in any() without a cast. But we may convert it into ARRAY[] below */
res = Bytes_FromString("'{}'");
goto exit;
}
tmp = PyTuple_New(len); if (!(qs = PyMem_New(PyObject *, len))) {
PyErr_NoMemory();
goto exit;
}
memset(qs, 0, len * sizeof(PyObject *));
for (i=0; i<len; i++) { for (i = 0; i < len; i++) {
PyObject *quoted;
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i); PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
if (wrapped == Py_None) { if (wrapped == Py_None) {
Py_INCREF(psyco_null); Py_INCREF(psyco_null);
quoted = psyco_null; qs[i] = psyco_null;
} }
else { else {
quoted = microprotocol_getquoted(wrapped, if (!(qs[i] = microprotocol_getquoted(
(connectionObject*)self->connection); wrapped, (connectionObject*)self->connection))) {
if (quoted == NULL) goto error; goto exit;
all_nulls = 0; }
/* Lists of arrays containing only nulls are also not supported
* by the ARRAY construct so we should do some special casing */
if (PyList_Check(wrapped)) {
if (Bytes_AS_STRING(qs[i])[0] == 'A') {
all_nulls = 0;
}
else if (0 == strcmp(Bytes_AS_STRING(qs[i]), "'{}'")) {
/* case of issue #788: '{{}}' is not supported but
* array[array[]] is */
all_nulls = 0;
Py_CLEAR(qs[i]);
qs[i] = Bytes_FromString("ARRAY[]");
}
}
else {
all_nulls = 0;
}
} }
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
/* here we don't loose a refcnt: SET_ITEM does not change the
reference count and we are just transferring ownership of the tmp
object to the tuple */
PyTuple_SET_ITEM(tmp, i, quoted);
} }
/* now that we have a tuple of adapted objects we just need to join them /* Create an array literal, usually ARRAY[...] but if the contents are
and put "ARRAY[] around the result */ * all NULL or array of NULL we must use the '{...}' syntax
str = Bytes_FromString(", "); */
joined = PyObject_CallMethod(str, "join", "(O)", tmp); if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
if (joined == NULL) goto error; PyErr_NoMemory();
goto exit;
}
/* PG doesn't like ARRAY[NULL..] */
if (!all_nulls) { if (!all_nulls) {
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined)); strcpy(ptr, "ARRAY[");
} else { ptr += 6;
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined)); for (i = 0; i < len; i++) {
Py_ssize_t sl;
sl = Bytes_GET_SIZE(qs[i]);
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
ptr += sl;
*ptr++ = ',';
}
*(ptr - 1) = ']';
}
else {
*ptr++ = '\'';
*ptr++ = '{';
for (i = 0; i < len; i++) {
/* in case all the adapted things are nulls (or array of nulls),
* the quoted string is either NULL or an array of the form
* '{NULL,...}', in which case we have to strip the extra quotes */
char *s;
Py_ssize_t sl;
s = Bytes_AS_STRING(qs[i]);
sl = Bytes_GET_SIZE(qs[i]);
if (s[0] != '\'') {
memcpy(ptr, s, sl);
ptr += sl;
}
else {
memcpy(ptr, s + 1, sl - 2);
ptr += sl - 2;
}
*ptr++ = ',';
}
*(ptr - 1) = '}';
*ptr++ = '\'';
} }
error: res = Bytes_FromStringAndSize(buf, ptr - buf);
Py_XDECREF(tmp);
Py_XDECREF(str); exit:
Py_XDECREF(joined); if (qs) {
for (i = 0; i < len; i++) {
PyObject *q = qs[i];
Py_XDECREF(q);
}
PyMem_Free(qs);
}
PyMem_Free(buf);
return res; return res;
} }

View File

@ -154,8 +154,7 @@ typedef unsigned __int64 uint64_t;
#endif #endif
/* what's this, we have no round function either? */ /* what's this, we have no round function either? */
#if (defined(__FreeBSD__) && __FreeBSD_version < 503000) \ #if (defined(_WIN32) && !defined(__GNUC__)) \
|| (defined(_WIN32) && !defined(__GNUC__)) \
|| (defined(sun) || defined(__sun__)) \ || (defined(sun) || defined(__sun__)) \
&& (defined(__SunOS_5_8) || defined(__SunOS_5_9)) && (defined(__SunOS_5_8) || defined(__SunOS_5_9))

View File

@ -67,6 +67,9 @@ const char *srv_state_guc[] = {
}; };
const int SRV_STATE_UNCHANGED = -1;
/* Return a new "string" from a char* from the database. /* Return a new "string" from a char* from the database.
* *
* On Py2 just get a string, on Py3 decode it in the connection codec. * On Py2 just get a string, on Py3 decode it in the connection codec.
@ -1188,8 +1191,10 @@ conn_set_session(connectionObject *self, int autocommit,
int rv = -1; int rv = -1;
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
int want_autocommit = autocommit == SRV_STATE_UNCHANGED ?
self->autocommit : autocommit;
if (deferrable != self->deferrable && self->server_version < 90100) { if (deferrable != SRV_STATE_UNCHANGED && self->server_version < 90100) {
PyErr_SetString(ProgrammingError, PyErr_SetString(ProgrammingError,
"the 'deferrable' setting is only available" "the 'deferrable' setting is only available"
" from PostgreSQL 9.1"); " from PostgreSQL 9.1");
@ -1209,24 +1214,24 @@ conn_set_session(connectionObject *self, int autocommit,
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock); pthread_mutex_lock(&self->lock);
if (autocommit) { if (want_autocommit) {
/* we are in autocommit state, so no BEGIN will be issued: /* we are or are going in autocommit state, so no BEGIN will be issued:
* configure the session with the characteristics requested */ * configure the session with the characteristics requested */
if (isolevel != self->isolevel) { if (isolevel != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self, if (0 > pq_set_guc_locked(self,
"default_transaction_isolation", srv_isolevels[isolevel], "default_transaction_isolation", srv_isolevels[isolevel],
&pgres, &error, &_save)) { &pgres, &error, &_save)) {
goto endlock; goto endlock;
} }
} }
if (readonly != self->readonly) { if (readonly != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self, if (0 > pq_set_guc_locked(self,
"default_transaction_read_only", srv_state_guc[readonly], "default_transaction_read_only", srv_state_guc[readonly],
&pgres, &error, &_save)) { &pgres, &error, &_save)) {
goto endlock; goto endlock;
} }
} }
if (deferrable != self->deferrable) { if (deferrable != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self, if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", srv_state_guc[deferrable], "default_transaction_deferrable", srv_state_guc[deferrable],
&pgres, &error, &_save)) { &pgres, &error, &_save)) {
@ -1251,7 +1256,7 @@ conn_set_session(connectionObject *self, int autocommit,
goto endlock; goto endlock;
} }
} }
if (self->deferrable != STATE_DEFAULT) { if (self->server_version >= 90100 && self->deferrable != STATE_DEFAULT) {
if (0 > pq_set_guc_locked(self, if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", "default", "default_transaction_deferrable", "default",
&pgres, &error, &_save)) { &pgres, &error, &_save)) {
@ -1260,10 +1265,18 @@ conn_set_session(connectionObject *self, int autocommit,
} }
} }
self->autocommit = autocommit; if (autocommit != SRV_STATE_UNCHANGED) {
self->isolevel = isolevel; self->autocommit = autocommit;
self->readonly = readonly; }
self->deferrable = deferrable; if (isolevel != SRV_STATE_UNCHANGED) {
self->isolevel = isolevel;
}
if (readonly != SRV_STATE_UNCHANGED) {
self->readonly = readonly;
}
if (deferrable != SRV_STATE_UNCHANGED) {
self->deferrable = deferrable;
}
rv = 0; rv = 0;
endlock: endlock:

View File

@ -39,6 +39,7 @@
extern HIDDEN const char *srv_isolevels[]; extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[]; extern HIDDEN const char *srv_readonly[];
extern HIDDEN const char *srv_deferrable[]; extern HIDDEN const char *srv_deferrable[];
extern HIDDEN const int SRV_STATE_UNCHANGED;
/** DBAPI methods **/ /** DBAPI methods **/
@ -561,10 +562,10 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
PyObject *deferrable = Py_None; PyObject *deferrable = Py_None;
PyObject *autocommit = Py_None; PyObject *autocommit = Py_None;
int c_isolevel = self->isolevel; int c_isolevel = SRV_STATE_UNCHANGED;
int c_readonly = self->readonly; int c_readonly = SRV_STATE_UNCHANGED;
int c_deferrable = self->deferrable; int c_deferrable = SRV_STATE_UNCHANGED;
int c_autocommit = self->autocommit; int c_autocommit = SRV_STATE_UNCHANGED;
static char *kwlist[] = static char *kwlist[] =
{"isolation_level", "readonly", "deferrable", "autocommit", NULL}; {"isolation_level", "readonly", "deferrable", "autocommit", NULL};
@ -637,7 +638,7 @@ psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; } if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
if (0 > conn_set_session(self, value, if (0 > conn_set_session(self, value,
self->isolevel, self->readonly, self->deferrable)) { SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return -1; return -1;
} }
@ -668,8 +669,8 @@ psyco_conn_isolation_level_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; } if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit, if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
value, self->readonly, self->deferrable)) { value, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return -1; return -1;
} }
@ -715,13 +716,13 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
if (level == 0) { if (level == 0) {
if (0 > conn_set_session(self, 1, if (0 > conn_set_session(self, 1,
self->isolevel, self->readonly, self->deferrable)) { SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return NULL; return NULL;
} }
} }
else { else {
if (0 > conn_set_session(self, 0, if (0 > conn_set_session(self, 0,
level, self->readonly, self->deferrable)) { level, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return NULL; return NULL;
} }
} }
@ -756,6 +757,7 @@ psyco_conn_readonly_get(connectionObject *self)
break; break;
} }
Py_XINCREF(rv);
return rv; return rv;
} }
@ -767,8 +769,8 @@ psyco_conn_readonly_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; } if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit, if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
self->isolevel, value, self->deferrable)) { SRV_STATE_UNCHANGED, value, SRV_STATE_UNCHANGED)) {
return -1; return -1;
} }
@ -802,6 +804,7 @@ psyco_conn_deferrable_get(connectionObject *self)
break; break;
} }
Py_XINCREF(rv);
return rv; return rv;
} }
@ -813,8 +816,8 @@ psyco_conn_deferrable_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; } if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit, if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
self->isolevel, self->readonly, value)) { SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, value)) {
return -1; return -1;
} }

View File

@ -49,14 +49,19 @@
static PyObject * static PyObject *
psyco_curs_close(cursorObject *self) psyco_curs_close(cursorObject *self)
{ {
PyObject *rv = NULL;
char *lname = NULL;
EXC_IF_ASYNC_IN_PROGRESS(self, close); EXC_IF_ASYNC_IN_PROGRESS(self, close);
if (self->closed) { if (self->closed) {
rv = Py_None;
Py_INCREF(rv);
goto exit; goto exit;
} }
if (self->qname != NULL) { if (self->qname != NULL) {
char buffer[128]; char buffer[256];
PGTransactionStatusType status; PGTransactionStatusType status;
if (self->conn) { if (self->conn) {
@ -66,22 +71,51 @@ psyco_curs_close(cursorObject *self)
status = PQTRANS_UNKNOWN; status = PQTRANS_UNKNOWN;
} }
if (!(status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR)) { if (status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR) {
EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
}
else {
Dprintf("skipping named curs close because tx status %d", Dprintf("skipping named curs close because tx status %d",
(int)status); (int)status);
goto close;
} }
/* We should close a server-side cursor only if exists, or we get an
* error (#716). If we execute()d the cursor should exist alright, but
* if we didn't there is still the expectation that the cursor is
* closed (#746).
*
* So if we didn't execute() check for the cursor existence before
* closing it (the view exists since PG 8.2 according to docs).
*/
if (!self->query && self->conn->server_version >= 80200) {
if (!(lname = psycopg_escape_string(
self->conn, self->name, -1, NULL, NULL))) {
goto exit;
}
PyOS_snprintf(buffer, sizeof(buffer),
"SELECT 1 FROM pg_catalog.pg_cursors where name = %s",
lname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) { goto exit; }
if (self->rowcount == 0) {
Dprintf("skipping named cursor close because not existing");
goto close;
}
}
EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, sizeof(buffer), "CLOSE %s", self->qname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) { goto exit; }
} }
close:
self->closed = 1; self->closed = 1;
Dprintf("psyco_curs_close: cursor at %p closed", self); Dprintf("psyco_curs_close: cursor at %p closed", self);
rv = Py_None;
Py_INCREF(rv);
exit: exit:
Py_RETURN_NONE; PyMem_Free(lname);
return rv;
} }
@ -592,8 +626,6 @@ psyco_curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs)
return NULL; return NULL;
} }
EXC_IF_CURS_CLOSED(self);
return _psyco_curs_mogrify(self, operation, vars); return _psyco_curs_mogrify(self, operation, vars);
} }
@ -738,7 +770,7 @@ psyco_curs_fetchone(cursorObject *self)
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchone); EXC_IF_ASYNC_IN_PROGRESS(self, fetchone);
EXC_IF_TPC_PREPARED(self->conn, fetchone); EXC_IF_TPC_PREPARED(self->conn, fetchone);
PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM %s", self->qname); PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD 1 FROM %s", self->qname);
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
if (_psyco_curs_prefetch(self) < 0) return NULL; if (_psyco_curs_prefetch(self) < 0) return NULL;
} }
@ -787,7 +819,7 @@ psyco_curs_next_named(cursorObject *self)
if (self->row >= self->rowcount) { if (self->row >= self->rowcount) {
char buffer[128]; char buffer[128];
PyOS_snprintf(buffer, 127, "FETCH FORWARD %ld FROM %s", PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD %ld FROM %s",
self->itersize, self->qname); self->itersize, self->qname);
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
if (_psyco_curs_prefetch(self) < 0) return NULL; if (_psyco_curs_prefetch(self) < 0) return NULL;
@ -856,7 +888,7 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords)
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchmany); EXC_IF_ASYNC_IN_PROGRESS(self, fetchmany);
EXC_IF_TPC_PREPARED(self->conn, fetchone); EXC_IF_TPC_PREPARED(self->conn, fetchone);
PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM %s", PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD %d FROM %s",
(int)size, self->qname); (int)size, self->qname);
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; } if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
if (_psyco_curs_prefetch(self) < 0) { goto exit; } if (_psyco_curs_prefetch(self) < 0) { goto exit; }
@ -932,7 +964,7 @@ psyco_curs_fetchall(cursorObject *self)
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, fetchall); EXC_IF_ASYNC_IN_PROGRESS(self, fetchall);
EXC_IF_TPC_PREPARED(self->conn, fetchall); EXC_IF_TPC_PREPARED(self->conn, fetchall);
PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM %s", self->qname); PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD ALL FROM %s", self->qname);
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; } if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
if (_psyco_curs_prefetch(self) < 0) { goto exit; } if (_psyco_curs_prefetch(self) < 0) { goto exit; }
} }
@ -1229,11 +1261,11 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
EXC_IF_TPC_PREPARED(self->conn, scroll); EXC_IF_TPC_PREPARED(self->conn, scroll);
if (strcmp(mode, "absolute") == 0) { if (strcmp(mode, "absolute") == 0) {
PyOS_snprintf(buffer, 127, "MOVE ABSOLUTE %d FROM %s", PyOS_snprintf(buffer, sizeof(buffer), "MOVE ABSOLUTE %d FROM %s",
value, self->qname); value, self->qname);
} }
else { else {
PyOS_snprintf(buffer, 127, "MOVE %d FROM %s", value, self->qname); PyOS_snprintf(buffer, sizeof(buffer), "MOVE %d FROM %s", value, self->qname);
} }
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
if (_psyco_curs_prefetch(self) < 0) return NULL; if (_psyco_curs_prefetch(self) < 0) return NULL;

View File

@ -52,6 +52,10 @@
#include "win32_support.h" #include "win32_support.h"
#endif #endif
#if defined(__sun) && defined(__SVR4)
#include "solaris_support.h"
#endif
extern HIDDEN PyObject *psyco_DescriptionType; extern HIDDEN PyObject *psyco_DescriptionType;
extern HIDDEN const char *srv_isolevels[]; extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[]; extern HIDDEN const char *srv_readonly[];
@ -1102,12 +1106,13 @@ pq_send_query(connectionObject *conn, const char *query)
* The function will block only if a command is active and the * The function will block only if a command is active and the
* necessary response data has not yet been read by PQconsumeInput. * necessary response data has not yet been read by PQconsumeInput.
* *
* The result should be disposed using PQclear() * The result should be disposed of using PQclear()
*/ */
PGresult * PGresult *
pq_get_last_result(connectionObject *conn) pq_get_last_result(connectionObject *conn)
{ {
PGresult *result = NULL, *res; PGresult *result = NULL, *res;
ExecStatusType status;
/* Read until PQgetResult gives a NULL */ /* Read until PQgetResult gives a NULL */
while (NULL != (res = PQgetResult(conn->pgconn))) { while (NULL != (res = PQgetResult(conn->pgconn))) {
@ -1120,11 +1125,15 @@ pq_get_last_result(connectionObject *conn)
PQclear(result); PQclear(result);
} }
result = res; result = res;
status = PQresultStatus(result);
Dprintf("pq_get_last_result: got result %s", PQresStatus(status));
/* After entering copy both mode, libpq will make a phony /* After entering copy mode, libpq will make a phony
* PGresult for us every time we query for it, so we need to * PGresult for us every time we query for it, so we need to
* break out of this endless loop. */ * break out of this endless loop. */
if (PQresultStatus(result) == PGRES_COPY_BOTH) { if (status == PGRES_COPY_BOTH
|| status == PGRES_COPY_OUT
|| status == PGRES_COPY_IN) {
break; break;
} }
} }
@ -1285,7 +1294,7 @@ _pq_fetch_tuples(cursorObject *curs)
PyTuple_SET_ITEM(dtitem, 4, tmp); PyTuple_SET_ITEM(dtitem, 4, tmp);
if (!(tmp = PyInt_FromLong(fmod & 0xFFFF))) { if (!(tmp = PyInt_FromLong(fmod & 0xFFFF))) {
PyTuple_SET_ITEM(dtitem, 5, tmp); goto err_for;
} }
PyTuple_SET_ITEM(dtitem, 5, tmp); PyTuple_SET_ITEM(dtitem, 5, tmp);
} }
@ -1948,8 +1957,9 @@ pq_fetch(cursorObject *curs, int no_result)
} }
else { else {
Dprintf("pq_fetch: got tuples, discarding them"); Dprintf("pq_fetch: got tuples, discarding them");
/* TODO: is there any case in which PQntuples == PQcmdTuples? */
_read_rowcount(curs);
CLEARPGRES(curs->pgres); CLEARPGRES(curs->pgres);
curs->rowcount = -1;
ex = 0; ex = 0;
} }
break; break;

View File

@ -72,6 +72,10 @@ HIDDEN PyObject *psyco_null = NULL;
/* The type of the cursor.description items */ /* The type of the cursor.description items */
HIDDEN PyObject *psyco_DescriptionType = NULL; HIDDEN PyObject *psyco_DescriptionType = NULL;
/* macro trick to stringify a macro expansion */
#define xstr(s) str(s)
#define str(s) #s
/** connect module-level function **/ /** connect module-level function **/
#define psyco_connect_doc \ #define psyco_connect_doc \
"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n" "_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n"
@ -885,7 +889,7 @@ INIT_MODULE(_psycopg)(void)
psycopg_debug_enabled = 1; psycopg_debug_enabled = 1;
#endif #endif
Dprintf("initpsycopg: initializing psycopg %s", PSYCOPG_VERSION); Dprintf("initpsycopg: initializing psycopg %s", xstr(PSYCOPG_VERSION));
/* initialize all the new types and then the module */ /* initialize all the new types and then the module */
Py_TYPE(&connectionType) = &PyType_Type; Py_TYPE(&connectionType) = &PyType_Type;
@ -1017,7 +1021,7 @@ INIT_MODULE(_psycopg)(void)
if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; } if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; }
/* set some module's parameters */ /* set some module's parameters */
PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION); PyModule_AddStringConstant(module, "__version__", xstr(PSYCOPG_VERSION));
PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver"); PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver");
PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM); PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
PyModule_AddIntMacro(module, REPLICATION_PHYSICAL); PyModule_AddIntMacro(module, REPLICATION_PHYSICAL);

View File

@ -93,6 +93,7 @@ typedef unsigned long Py_uhash_t;
#ifndef PyNumber_Int #ifndef PyNumber_Int
#define PyNumber_Int PyNumber_Long #define PyNumber_Int PyNumber_Long
#endif #endif
#endif /* PY_MAJOR_VERSION > 2 */ #endif /* PY_MAJOR_VERSION > 2 */
#if PY_MAJOR_VERSION < 3 #if PY_MAJOR_VERSION < 3
@ -128,6 +129,12 @@ typedef unsigned long Py_uhash_t;
#endif #endif
#ifndef PyDateTime_DELTA_GET_DAYS
#define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta*)o)->days)
#define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta*)o)->seconds)
#define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds)
#endif
HIDDEN PyObject *Bytes_Format(PyObject *format, PyObject *args); HIDDEN PyObject *Bytes_Format(PyObject *format, PyObject *args);
/* Mangle the module name into the name of the module init function */ /* Mangle the module name into the name of the module init function */

57
psycopg/solaris_support.c Normal file
View File

@ -0,0 +1,57 @@
/* solaris_support.c - emulate functions missing on Solaris
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018, Joyent, Inc.
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"
#include "psycopg/solaris_support.h"
#if defined(__sun) && defined(__SVR4)
/* timeradd is missing on Solaris 10 */
#ifndef timeradd
void
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec + b->tv_sec;
c->tv_usec = a->tv_usec + b->tv_usec;
if (c->tv_usec >= 1000000) {
c->tv_usec -= 1000000;
c->tv_sec += 1;
}
}
/* timersub is missing on Solaris */
void
timersub(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec - b->tv_sec;
c->tv_usec = a->tv_usec - b->tv_usec;
if (c->tv_usec < 0) {
c->tv_usec += 1000000;
c->tv_sec -= 1;
}
}
#endif /* timeradd */
#endif /* defined(__sun) && defined(__SVR4) */

40
psycopg/solaris_support.h Normal file
View File

@ -0,0 +1,40 @@
/* solaris_support.h - definitions for solaris_support.c
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018, Joyent, Inc.
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#ifndef PSYCOPG_SOLARIS_SUPPORT_H
#define PSYCOPG_SOLARIS_SUPPORT_H
#include "psycopg/config.h"
#if defined(__sun) && defined(__SVR4)
#include <sys/time.h>
#ifndef timeradd
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
#endif
#endif
#endif /* !defined(PSYCOPG_SOLARIS_SUPPORT_H) */

View File

@ -15,7 +15,7 @@ static long int typecast_BINARY_types[] = {17, 0};
static long int typecast_ROWID_types[] = {26, 0}; static long int typecast_ROWID_types[] = {26, 0};
static long int typecast_LONGINTEGERARRAY_types[] = {1016, 0}; static long int typecast_LONGINTEGERARRAY_types[] = {1016, 0};
static long int typecast_INTEGERARRAY_types[] = {1005, 1006, 1007, 0}; static long int typecast_INTEGERARRAY_types[] = {1005, 1006, 1007, 0};
static long int typecast_FLOATARRAY_types[] = {1017, 1021, 1022, 0}; static long int typecast_FLOATARRAY_types[] = {1021, 1022, 0};
static long int typecast_DECIMALARRAY_types[] = {1231, 0}; static long int typecast_DECIMALARRAY_types[] = {1231, 0};
static long int typecast_UNICODEARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0}; static long int typecast_UNICODEARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0};
static long int typecast_STRINGARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0}; static long int typecast_STRINGARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0};

View File

@ -406,6 +406,11 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
} }
break; break;
case 'P':
PyErr_SetString(NotSupportedError,
"iso_8601 intervalstyle currently not supported");
return NULL;
default: default:
break; break;
} }

View File

@ -168,11 +168,11 @@ psycopg_ensure_bytes(PyObject *obj)
PyObject *rv = NULL; PyObject *rv = NULL;
if (!obj) { return NULL; } if (!obj) { return NULL; }
if (PyUnicode_CheckExact(obj)) { if (PyUnicode_Check(obj)) {
rv = PyUnicode_AsUTF8String(obj); rv = PyUnicode_AsUTF8String(obj);
Py_DECREF(obj); Py_DECREF(obj);
} }
else if (Bytes_CheckExact(obj)) { else if (Bytes_Check(obj)) {
rv = obj; rv = obj;
} }
else { else {

View File

@ -9,10 +9,10 @@ To invalidate the cache, update this file and check it into git.
Currently used modules built in the cache: Currently used modules built in the cache:
OpenSSL OpenSSL
Version: 1.0.2l Version: 1.0.2o
PostgreSQL PostgreSQL
Version: 10.0 Version: 10.4
NOTE: to zap the cache manually you can also use: NOTE: to zap the cache manually you can also use:

View File

@ -35,7 +35,7 @@ def main():
# If you add a version to the list fix the docs (in errorcodes.rst) # If you add a version to the list fix the docs (in errorcodes.rst)
classes, errors = fetch_errors( classes, errors = fetch_errors(
['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5', ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5',
'9.6', '10']) '9.6', '10', '11'])
f = open(filename, "w") f = open(filename, "w")
for line in file_start: for line in file_start:
@ -154,7 +154,7 @@ def fetch_errors(versions):
# TODO: this error was added in PG 10 beta 1 but dropped in the # TODO: this error was added in PG 10 beta 1 but dropped in the
# final release. It doesn't harm leaving it in the file. Check if it # final release. It doesn't harm leaving it in the file. Check if it
# will be added back in PG 11. # will be added back in PG 12.
# https://github.com/postgres/postgres/commit/28e0727076 # https://github.com/postgres/postgres/commit/28e0727076
errors['55']['55P04'] = 'UNSAFE_NEW_ENUM_VALUE_USAGE' errors['55']['55P04'] = 'UNSAFE_NEW_ENUM_VALUE_USAGE'

View File

@ -56,15 +56,15 @@ fi
# Unsupported postgres versions that we still support # Unsupported postgres versions that we still support
# Images built by https://github.com/psycopg/psycopg2-wheels/tree/build-dinosaurs # Images built by https://github.com/psycopg/psycopg2-wheels/tree/build-dinosaurs
if [[ -n "$TEST_PAST" ]]; then if [[ -n "$TEST_PAST" ]]; then
run_test 7.4
run_test 8.0
run_test 8.1
run_test 8.2
run_test 8.3
run_test 8.4
run_test 9.0
run_test 9.1
run_test 9.2 run_test 9.2
run_test 9.1
run_test 9.0
run_test 8.4
run_test 8.3
run_test 8.2
run_test 8.1
run_test 8.0
run_test 7.4
fi fi
# Postgres built from master # Postgres built from master

View File

@ -27,3 +27,6 @@ static_libpq=0
# Add here eventual extra libraries required to link the module. # Add here eventual extra libraries required to link the module.
libraries= libraries=
[metadata]
license_file = LICENSE

View File

@ -39,6 +39,7 @@ except ImportError:
from distutils.command.build_ext import build_ext from distutils.command.build_ext import build_ext
from distutils.sysconfig import get_python_inc from distutils.sysconfig import get_python_inc
from distutils.ccompiler import get_default_compiler from distutils.ccompiler import get_default_compiler
from distutils.errors import CompileError
from distutils.util import get_platform from distutils.util import get_platform
try: try:
@ -64,7 +65,7 @@ except ImportError:
# Take a look at http://www.python.org/dev/peps/pep-0440/ # Take a look at http://www.python.org/dev/peps/pep-0440/
# for a consistent versioning pattern. # for a consistent versioning pattern.
PSYCOPG_VERSION = '2.7.4.dev0' PSYCOPG_VERSION = '2.7.6.1'
# note: if you are changing the list of supported Python version please fix # note: if you are changing the list of supported Python version please fix
@ -75,6 +76,7 @@ Intended Audience :: Developers
License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
License :: OSI Approved :: Zope Public License License :: OSI Approved :: Zope Public License
Programming Language :: Python Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 Programming Language :: Python :: 3
@ -83,6 +85,8 @@ Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: Implementation :: CPython
Programming Language :: C Programming Language :: C
Programming Language :: SQL Programming Language :: SQL
Topic :: Database Topic :: Database
@ -105,15 +109,23 @@ class PostgresConfig:
if not self.pg_config_exe: if not self.pg_config_exe:
self.pg_config_exe = self.autodetect_pg_config_path() self.pg_config_exe = self.autodetect_pg_config_path()
if self.pg_config_exe is None: if self.pg_config_exe is None:
sys.stderr.write("""\ sys.stderr.write("""
Error: pg_config executable not found. Error: pg_config executable not found.
Please add the directory containing pg_config to the PATH pg_config is required to build psycopg2 from source. Please add the directory
or specify the full executable path with the option: containing pg_config to the $PATH or specify the full executable path with the
option:
python setup.py build_ext --pg-config /path/to/pg_config build ... python setup.py build_ext --pg-config /path/to/pg_config build ...
or with the pg_config option in 'setup.cfg'. or with the pg_config option in 'setup.cfg'.
If you prefer to avoid building psycopg2 from source, please install the PyPI
'psycopg2-binary' package instead.
For further information please check the 'doc/src/install.rst' file (also at
<http://initd.org/psycopg/docs/install.html>).
""") """)
sys.exit(1) sys.exit(1)
@ -195,8 +207,7 @@ or with the pg_config option in 'setup.cfg'.
return None return None
pg_first_inst_key = winreg.OpenKey(reg, pg_first_inst_key = winreg.OpenKey(reg,
'SOFTWARE\\PostgreSQL\\Installations\\' 'SOFTWARE\\PostgreSQL\\Installations\\' + first_sub_key_name)
+ first_sub_key_name)
try: try:
pg_inst_base_dir = winreg.QueryValueEx( pg_inst_base_dir = winreg.QueryValueEx(
pg_first_inst_key, 'Base Directory')[0] pg_first_inst_key, 'Base Directory')[0]
@ -289,13 +300,43 @@ class psycopg_build_ext(build_ext):
else: else:
return build_ext.get_export_symbols(self, extension) return build_ext.get_export_symbols(self, extension)
built_files = 0
def build_extension(self, extension): def build_extension(self, extension):
build_ext.build_extension(self, extension) # Count files compiled to print the binary blurb only if the first fails
compile_orig = getattr(self.compiler, '_compile', None)
if compile_orig is not None:
def _compile(*args, **kwargs):
rv = compile_orig(*args, **kwargs)
psycopg_build_ext.built_files += 1
return rv
self.compiler._compile = _compile
try:
build_ext.build_extension(self, extension)
psycopg_build_ext.built_files += 1
except CompileError:
if self.built_files == 0:
sys.stderr.write("""
It appears you are missing some prerequisite to build the package from source.
You may install a binary package by installing 'psycopg2-binary' from PyPI.
If you want to install psycopg2 from source, please install the packages
required for the build and try again.
For further information please check the 'doc/src/install.rst' file (also at
<http://initd.org/psycopg/docs/install.html>).
""")
raise
sysVer = sys.version_info[:2] sysVer = sys.version_info[:2]
# For Python versions that use MSVC compiler 2008, re-insert the # For Python versions that use MSVC compiler 2008, re-insert the
# manifest into the resulting .pyd file. # manifest into the resulting .pyd file.
if self.compiler_is_msvc() and sysVer in ((2, 6), (2, 7), (3, 0), (3, 1), (3, 2)): if self.compiler_is_msvc() and sysVer in (
(2, 6), (2, 7), (3, 0), (3, 1), (3, 2)):
platform = get_platform() platform = get_platform()
# Default to the x86 manifest # Default to the x86 manifest
manifest = '_psycopg.vc9.x86.manifest' manifest = '_psycopg.vc9.x86.manifest'
@ -412,7 +453,7 @@ class psycopg_build_ext(build_ext):
# *at least* PostgreSQL 7.4 is available (this is the only # *at least* PostgreSQL 7.4 is available (this is the only
# 7.x series supported by psycopg 2) # 7.x series supported by psycopg 2)
pgversion = pg_config_helper.query("version").split()[1] pgversion = pg_config_helper.query("version").split()[1]
except: except Exception:
pgversion = "7.4.0" pgversion = "7.4.0"
verre = re.compile( verre = re.compile(
@ -420,11 +461,14 @@ class psycopg_build_ext(build_ext):
m = verre.match(pgversion) m = verre.match(pgversion)
if m: if m:
pgmajor, pgminor, pgpatch = m.group(1, 2, 3) pgmajor, pgminor, pgpatch = m.group(1, 2, 3)
# Postgres >= 10 doesn't have pgminor anymore.
pgmajor = int(pgmajor)
if pgmajor >= 10:
pgminor, pgpatch = None, pgminor
if pgminor is None or not pgminor.isdigit(): if pgminor is None or not pgminor.isdigit():
pgminor = 0 pgminor = 0
if pgpatch is None or not pgpatch.isdigit(): if pgpatch is None or not pgpatch.isdigit():
pgpatch = 0 pgpatch = 0
pgmajor = int(pgmajor)
pgminor = int(pgminor) pgminor = int(pgminor)
pgpatch = int(pgpatch) pgpatch = int(pgpatch)
else: else:
@ -480,7 +524,7 @@ data_files = []
sources = [ sources = [
'psycopgmodule.c', 'psycopgmodule.c',
'green.c', 'pqpath.c', 'utils.c', 'bytes_format.c', 'green.c', 'pqpath.c', 'utils.c', 'bytes_format.c',
'libpq_support.c', 'win32_support.c', 'libpq_support.c', 'win32_support.c', 'solaris_support.c',
'connection_int.c', 'connection_type.c', 'connection_int.c', 'connection_type.c',
'cursor_int.c', 'cursor_type.c', 'cursor_int.c', 'cursor_type.c',
@ -570,10 +614,7 @@ if version_flags:
else: else:
PSYCOPG_VERSION_EX = PSYCOPG_VERSION PSYCOPG_VERSION_EX = PSYCOPG_VERSION
if not PLATFORM_IS_WINDOWS: define_macros.append(('PSYCOPG_VERSION', PSYCOPG_VERSION_EX))
define_macros.append(('PSYCOPG_VERSION', '"' + PSYCOPG_VERSION_EX + '"'))
else:
define_macros.append(('PSYCOPG_VERSION', '\\"' + PSYCOPG_VERSION_EX + '\\"'))
if parser.has_option('build_ext', 'have_ssl'): if parser.has_option('build_ext', 'have_ssl'):
have_ssl = int(parser.get('build_ext', 'have_ssl')) have_ssl = int(parser.get('build_ext', 'have_ssl'))
@ -617,7 +658,7 @@ try:
f = open("README.rst") f = open("README.rst")
readme = f.read() readme = f.read()
f.close() f.close()
except: except Exception:
print("failed to read readme: ignoring...") print("failed to read readme: ignoring...")
readme = __doc__ readme = __doc__

View File

@ -65,7 +65,7 @@ def test_suite():
import psycopg2 import psycopg2
try: try:
cnn = psycopg2.connect(dsn) cnn = psycopg2.connect(dsn)
except Exception, e: except Exception as e:
print "Failed connection to test db:", e.__class__.__name__, e print "Failed connection to test db:", e.__class__.__name__, e
print "Please set env vars 'PSYCOPG2_TESTDB*' to valid values." print "Please set env vars 'PSYCOPG2_TESTDB*' to valid values."
sys.exit(1) sys.exit(1)

View File

@ -444,12 +444,18 @@ class AsyncTests(ConnectingTestCase):
try: try:
cnn = psycopg2.connect('dbname=thisdatabasedoesntexist', async_=True) cnn = psycopg2.connect('dbname=thisdatabasedoesntexist', async_=True)
self.wait(cnn) self.wait(cnn)
except psycopg2.Error, e: except psycopg2.Error as e:
self.assertNotEqual(str(e), "asynchronous connection failed", self.assertNotEqual(str(e), "asynchronous connection failed",
"connection error reason lost") "connection error reason lost")
else: else:
self.fail("no exception raised") self.fail("no exception raised")
@skip_before_postgres(8, 2)
def test_copy_no_hang(self):
cur = self.conn.cursor()
cur.execute("copy (select 1) to stdout")
self.assertRaises(psycopg2.ProgrammingError, self.wait, self.conn)
def test_suite(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -81,7 +81,7 @@ class AsyncTests(ConnectingTestCase):
try: try:
cnn = psycopg2.connect('dbname=thisdatabasedoesntexist', async=True) cnn = psycopg2.connect('dbname=thisdatabasedoesntexist', async=True)
self.wait(cnn) self.wait(cnn)
except psycopg2.Error, e: except psycopg2.Error as e:
self.assertNotEqual(str(e), "asynchronous connection failed", self.assertNotEqual(str(e), "asynchronous connection failed",
"connection error reason lost") "connection error reason lost")
else: else:

View File

@ -63,7 +63,7 @@ class CancelTests(ConnectingTestCase):
conn.rollback() conn.rollback()
cur.execute("select 1") cur.execute("select 1")
self.assertEqual(cur.fetchall(), [(1, )]) self.assertEqual(cur.fetchall(), [(1, )])
except Exception, e: except Exception as e:
errors.append(e) errors.append(e)
raise raise
@ -71,7 +71,7 @@ class CancelTests(ConnectingTestCase):
cur = conn.cursor() cur = conn.cursor()
try: try:
conn.cancel() conn.cancel()
except Exception, e: except Exception as e:
errors.append(e) errors.append(e)
raise raise
del cur del cur

View File

@ -246,6 +246,13 @@ class ConnectionTests(ConnectingTestCase):
else: else:
del os.environ['PGCLIENTENCODING'] del os.environ['PGCLIENTENCODING']
def test_connect_no_string(self):
class MyString(str):
pass
conn = psycopg2.connect(MyString(dsn))
conn.close()
def test_weakref(self): def test_weakref(self):
from weakref import ref from weakref import ref
import gc import gc
@ -358,7 +365,7 @@ class ParseDsnTestCase(ConnectingTestCase):
try: try:
# unterminated quote after dbname: # unterminated quote after dbname:
ext.parse_dsn("dbname='test 2 user=tester password=secret") ext.parse_dsn("dbname='test 2 user=tester password=secret")
except ProgrammingError, e: except ProgrammingError as e:
raised = True raised = True
self.assertTrue(str(e).find('secret') < 0, self.assertTrue(str(e).find('secret') < 0,
"DSN was not exposed in error message") "DSN was not exposed in error message")
@ -376,7 +383,7 @@ class ParseDsnTestCase(ConnectingTestCase):
try: try:
# extra '=' after port value # extra '=' after port value
ext.parse_dsn(dsn='postgresql://tester:secret@/test?port=1111=x') ext.parse_dsn(dsn='postgresql://tester:secret@/test?port=1111=x')
except psycopg2.ProgrammingError, e: except psycopg2.ProgrammingError as e:
raised = True raised = True
self.assertTrue(str(e).find('secret') < 0, self.assertTrue(str(e).find('secret') < 0,
"URI was not exposed in error message") "URI was not exposed in error message")
@ -401,6 +408,13 @@ class ParseDsnTestCase(ConnectingTestCase):
self.assertRaises(TypeError, ext.parse_dsn, None) self.assertRaises(TypeError, ext.parse_dsn, None)
self.assertRaises(TypeError, ext.parse_dsn, 42) self.assertRaises(TypeError, ext.parse_dsn, 42)
def test_str_subclass(self):
class MyString(str):
pass
res = ext.parse_dsn(MyString("dbname=test"))
self.assertEqual(res, {'dbname': 'test'})
class MakeDsnTestCase(ConnectingTestCase): class MakeDsnTestCase(ConnectingTestCase):
def test_empty_arguments(self): def test_empty_arguments(self):
@ -790,6 +804,14 @@ class IsolationLevelsTestCase(ConnectingTestCase):
self.assertRaises(ValueError, setattr, self.conn, 'isolation_level', 5) self.assertRaises(ValueError, setattr, self.conn, 'isolation_level', 5)
self.assertRaises(ValueError, setattr, self.conn, 'isolation_level', 'bah') self.assertRaises(ValueError, setattr, self.conn, 'isolation_level', 'bah')
def test_attribs_segfault(self):
# bug #790
for i in range(10000):
self.conn.autocommit
self.conn.readonly
self.conn.deferrable
self.conn.isolation_level
class ConnectionTwoPhaseTests(ConnectingTestCase): class ConnectionTwoPhaseTests(ConnectingTestCase):
def setUp(self): def setUp(self):
@ -1382,6 +1404,16 @@ class TransactionControlTests(ConnectingTestCase):
cur.execute("SHOW default_transaction_read_only;") cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'off') self.assertEqual(cur.fetchone()[0], 'off')
def test_idempotence_check(self):
self.conn.autocommit = False
self.conn.readonly = True
self.conn.autocommit = True
self.conn.readonly = True
cur = self.conn.cursor()
cur.execute("SHOW transaction_read_only")
self.assertEqual(cur.fetchone()[0], 'on')
class AutocommitTests(ConnectingTestCase): class AutocommitTests(ConnectingTestCase):
def test_closed(self): def test_closed(self):
@ -1540,9 +1572,13 @@ import os
import sys import sys
import time import time
import signal import signal
import warnings
import threading import threading
import psycopg2 # ignore wheel deprecation warning
with warnings.catch_warnings():
warnings.simplefilter('ignore')
import psycopg2
def handle_sigabort(sig, frame): def handle_sigabort(sig, frame):
sys.exit(1) sys.exit(1)

View File

@ -364,7 +364,7 @@ conn.close()
# curs.copy_from, BrokenRead(), "tcopy") # curs.copy_from, BrokenRead(), "tcopy")
try: try:
curs.copy_from(BrokenRead(), "tcopy") curs.copy_from(BrokenRead(), "tcopy")
except Exception, e: except Exception as e:
self.assert_('ZeroDivisionError' in str(e)) self.assert_('ZeroDivisionError' in str(e))
def test_copy_to_propagate_error(self): def test_copy_to_propagate_error(self):

View File

@ -121,6 +121,12 @@ class CursorTests(ConnectingTestCase):
nref2 = sys.getrefcount(foo) nref2 = sys.getrefcount(foo)
self.assertEqual(nref1, nref2) self.assertEqual(nref1, nref2)
def test_modify_closed(self):
cur = self.conn.cursor()
cur.close()
sql = cur.mogrify("select %s", (10,))
self.assertEqual(sql, b"select 10")
def test_bad_placeholder(self): def test_bad_placeholder(self):
cur = self.conn.cursor() cur = self.conn.cursor()
self.assertRaises(psycopg2.ProgrammingError, self.assertRaises(psycopg2.ProgrammingError,
@ -437,6 +443,24 @@ class CursorTests(ConnectingTestCase):
self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3)) self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3))
self.assertEqual([(5,), (6,), (7,)], cur2.fetchall()) self.assertEqual([(5,), (6,), (7,)], cur2.fetchall())
@skip_before_postgres(8, 2)
def test_named_noop_close(self):
cur = self.conn.cursor('test')
cur.close()
@skip_before_postgres(8, 2)
def test_stolen_named_cursor_close(self):
cur1 = self.conn.cursor()
cur1.execute("DECLARE test CURSOR WITHOUT HOLD "
" FOR SELECT generate_series(1,7)")
cur2 = self.conn.cursor('test')
cur2.close()
cur1.execute("DECLARE test CURSOR WITHOUT HOLD "
" FOR SELECT generate_series(1,7)")
cur2 = self.conn.cursor('test')
cur2.close()
@skip_before_postgres(8, 0) @skip_before_postgres(8, 0)
def test_scroll(self): def test_scroll(self):
cur = self.conn.cursor() cur = self.conn.cursor()
@ -561,7 +585,7 @@ class CursorTests(ConnectingTestCase):
# Issue #443 is in the async code too. Since the fix is duplicated, # Issue #443 is in the async code too. Since the fix is duplicated,
# so is the test. # so is the test.
control_conn = self.conn control_conn = self.conn
connect_func = lambda: self.connect(async=True) connect_func = lambda: self.connect(async_=True)
wait_func = psycopg2.extras.wait_select wait_func = psycopg2.extras.wait_select
self._test_external_close(control_conn, connect_func, wait_func) self._test_external_close(control_conn, connect_func, wait_func)
@ -592,6 +616,20 @@ class CursorTests(ConnectingTestCase):
self.assertEqual(victim_conn.closed, 2) self.assertEqual(victim_conn.closed, 2)
@skip_before_postgres(8, 2)
def test_rowcount_on_executemany_returning(self):
cur = self.conn.cursor()
cur.execute("create table execmany(id serial primary key, data int)")
cur.executemany(
"insert into execmany (data) values (%s)",
[(i,) for i in range(4)])
self.assertEqual(cur.rowcount, 4)
cur.executemany(
"insert into execmany (data) values (%s) returning data",
[(i,) for i in range(5)])
self.assertEqual(cur.rowcount, 5)
def test_suite(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -437,6 +437,14 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
r = cur.fetchone()[0] r = cur.fetchone()[0]
self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v)) self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v))
@skip_before_postgres(8, 4)
def test_interval_iso_8601_not_supported(self):
# We may end up supporting, but no pressure for it
cur = self.conn.cursor()
cur.execute("set local intervalstyle to iso_8601")
cur.execute("select '1 day 2 hours'::interval")
self.assertRaises(psycopg2.NotSupportedError, cur.fetchone)
# Only run the datetime tests if psycopg was compiled with support. # Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'): if not hasattr(psycopg2.extensions, 'PYDATETIME'):
@ -638,7 +646,8 @@ class FromTicksTestCase(unittest.TestCase):
def test_date_value_error_sec_59_99(self): def test_date_value_error_sec_59_99(self):
from datetime import date from datetime import date
s = psycopg2.DateFromTicks(1273173119.99992) s = psycopg2.DateFromTicks(1273173119.99992)
self.assertEqual(s.adapted, date(2010, 5, 6)) # The returned date is local
self.assert_(s.adapted in [date(2010, 5, 6), date(2010, 5, 7)])
def test_time_value_error_sec_59_99(self): def test_time_value_error_sec_59_99(self):
from datetime import time from datetime import time

View File

@ -48,7 +48,7 @@ class ErrocodeTests(ConnectingTestCase):
def f(pg_code='40001'): def f(pg_code='40001'):
try: try:
errorcodes.lookup(pg_code) errorcodes.lookup(pg_code)
except Exception, e: except Exception as e:
errs.append(e) errs.append(e)
for __ in xrange(MAX_CYCLES): for __ in xrange(MAX_CYCLES):

View File

@ -19,7 +19,7 @@ from datetime import timedelta
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
from testutils import unittest, ConnectingTestCase, skip_before_postgres from testutils import unittest, ConnectingTestCase, skip_before_postgres
from testutils import skip_if_no_namedtuple from testutils import skip_before_python, skip_if_no_namedtuple
class ExtrasDictCursorTests(ConnectingTestCase): class ExtrasDictCursorTests(ConnectingTestCase):
@ -382,6 +382,24 @@ class NamedTupleCursorTest(ConnectingTestCase):
curs.execute("update nttest set s = s") curs.execute("update nttest set s = s")
self.assertRaises(psycopg2.ProgrammingError, curs.fetchall) self.assertRaises(psycopg2.ProgrammingError, curs.fetchall)
@skip_if_no_namedtuple
def test_bad_col_names(self):
curs = self.conn.cursor()
curs.execute('select 1 as "foo.bar_baz", 2 as "?column?", 3 as "3"')
rv = curs.fetchone()
self.assertEqual(rv.foo_bar_baz, 1)
self.assertEqual(rv.f_column_, 2)
self.assertEqual(rv.f3, 3)
@skip_before_python(3)
@skip_before_postgres(8)
@skip_if_no_namedtuple
def test_nonascii_name(self):
curs = self.conn.cursor()
curs.execute('select 1 as \xe5h\xe9')
rv = curs.fetchone()
self.assertEqual(getattr(rv, '\xe5h\xe9'), 1)
@skip_if_no_namedtuple @skip_if_no_namedtuple
def test_minimal_generation(self): def test_minimal_generation(self):
# Instrument the class to verify it gets called the minimum number of times. # Instrument the class to verify it gets called the minimum number of times.

View File

@ -83,6 +83,16 @@ class TestExecuteBatch(FastExecuteTestMixin, testutils.ConnectingTestCase):
cur.execute("select id, val from testfast order by id") cur.execute("select id, val from testfast order by id")
self.assertEqual(cur.fetchall(), [(i, i * 10) for i in range(1000)]) self.assertEqual(cur.fetchall(), [(i, i * 10) for i in range(1000)])
def test_composed(self):
from psycopg2 import sql
cur = self.conn.cursor()
psycopg2.extras.execute_batch(cur,
sql.SQL("insert into {0} (id, val) values (%s, %s)")
.format(sql.Identifier('testfast')),
((i, i * 10) for i in range(1000)))
cur.execute("select id, val from testfast order by id")
self.assertEqual(cur.fetchall(), [(i, i * 10) for i in range(1000)])
def test_pages(self): def test_pages(self):
cur = self.conn.cursor() cur = self.conn.cursor()
psycopg2.extras.execute_batch(cur, psycopg2.extras.execute_batch(cur,
@ -169,6 +179,16 @@ class TestExecuteValues(FastExecuteTestMixin, testutils.ConnectingTestCase):
cur.execute("select id, val from testfast order by id") cur.execute("select id, val from testfast order by id")
self.assertEqual(cur.fetchall(), [(i, i * 10) for i in range(1000)]) self.assertEqual(cur.fetchall(), [(i, i * 10) for i in range(1000)])
def test_composed(self):
from psycopg2 import sql
cur = self.conn.cursor()
psycopg2.extras.execute_values(cur,
sql.SQL("insert into {0} (id, val) values %s")
.format(sql.Identifier('testfast')),
((i, i * 10) for i in range(1000)))
cur.execute("select id, val from testfast order by id")
self.assertEqual(cur.fetchall(), [(i, i * 10) for i in range(1000)])
def test_pages(self): def test_pages(self):
cur = self.conn.cursor() cur = self.conn.cursor()
psycopg2.extras.execute_values(cur, psycopg2.extras.execute_values(cur,

View File

@ -27,7 +27,7 @@ import psycopg2
import psycopg2.extensions import psycopg2.extensions
import psycopg2.extras import psycopg2.extras
from testutils import ConnectingTestCase, slow from testutils import ConnectingTestCase, skip_before_postgres, slow
class ConnectionStub(object): class ConnectionStub(object):
@ -111,6 +111,12 @@ class GreenTestCase(ConnectingTestCase):
curs.execute("select 1") curs.execute("select 1")
self.assertEqual(curs.fetchone()[0], 1) self.assertEqual(curs.fetchone()[0], 1)
@skip_before_postgres(8, 2)
def test_copy_no_hang(self):
cur = self.conn.cursor()
self.assertRaises(psycopg2.ProgrammingError,
cur.execute, "copy (select 1) to stdout")
class CallbackErrorTestCase(ConnectingTestCase): class CallbackErrorTestCase(ConnectingTestCase):
def setUp(self): def setUp(self):

View File

@ -152,7 +152,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.execute("select * from nonexist") cur.execute("select * from nonexist")
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
self.assertEqual(e.pgcode, '42P01') self.assertEqual(e.pgcode, '42P01')
@ -163,7 +163,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.execute("select * from nonexist") cur.execute("select * from nonexist")
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
diag = e.diag diag = e.diag
@ -182,7 +182,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.execute("select * from nonexist") cur.execute("select * from nonexist")
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
self.assertEqual(e.diag.sqlstate, '42P01') self.assertEqual(e.diag.sqlstate, '42P01')
@ -196,7 +196,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.execute("select * from nonexist") cur.execute("select * from nonexist")
except psycopg2.Error, exc: except psycopg2.Error as exc:
return cur, exc return cur, exc
cur, e = tmp() cur, e = tmp()
@ -221,7 +221,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.copy_to(f, 'nonexist') cur.copy_to(f, 'nonexist')
except psycopg2.Error, exc: except psycopg2.Error as exc:
diag = exc.diag diag = exc.diag
self.assertEqual(diag.sqlstate, '42P01') self.assertEqual(diag.sqlstate, '42P01')
@ -230,14 +230,14 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.execute("l'acqua e' poca e 'a papera nun galleggia") cur.execute("l'acqua e' poca e 'a papera nun galleggia")
except Exception, exc: except Exception as exc:
diag1 = exc.diag diag1 = exc.diag
self.conn.rollback() self.conn.rollback()
try: try:
cur.execute("select level from water where ducks > 1") cur.execute("select level from water where ducks > 1")
except psycopg2.Error, exc: except psycopg2.Error as exc:
diag2 = exc.diag diag2 = exc.diag
self.assertEqual(diag1.sqlstate, '42601') self.assertEqual(diag1.sqlstate, '42601')
@ -254,7 +254,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur.execute("insert into test_deferred values (1,2)") cur.execute("insert into test_deferred values (1,2)")
try: try:
self.conn.commit() self.conn.commit()
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
self.assertEqual(e.diag.sqlstate, '23503') self.assertEqual(e.diag.sqlstate, '23503')
@ -267,7 +267,7 @@ class ExceptionsTestCase(ConnectingTestCase):
)""") )""")
try: try:
cur.execute("insert into test_exc values(2)") cur.execute("insert into test_exc values(2)")
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
self.assertEqual(e.pgcode, '23514') self.assertEqual(e.pgcode, '23514')
self.assertEqual(e.diag.schema_name[:7], "pg_temp") self.assertEqual(e.diag.schema_name[:7], "pg_temp")
@ -282,7 +282,7 @@ class ExceptionsTestCase(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
cur.execute("select * from nonexist") cur.execute("select * from nonexist")
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
e1 = pickle.loads(pickle.dumps(e)) e1 = pickle.loads(pickle.dumps(e))
@ -297,7 +297,7 @@ class ExceptionsTestCase(ConnectingTestCase):
import pickle import pickle
try: try:
psycopg2.connect('dbname=nosuchdatabasemate') psycopg2.connect('dbname=nosuchdatabasemate')
except psycopg2.Error, exc: except psycopg2.Error as exc:
e = exc e = exc
e1 = pickle.loads(pickle.dumps(e)) e1 = pickle.loads(pickle.dumps(e))

View File

@ -145,7 +145,7 @@ class DeadlockSerializationTests(ConnectingTestCase):
step1.set() step1.set()
step2.wait() step2.wait()
curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE") curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE")
except psycopg2.DatabaseError, exc: except psycopg2.DatabaseError as exc:
self.thread1_error = exc self.thread1_error = exc
step1.set() step1.set()
conn.close() conn.close()
@ -158,7 +158,7 @@ class DeadlockSerializationTests(ConnectingTestCase):
curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE") curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE")
step2.set() step2.set()
curs.execute("LOCK table1 IN ACCESS EXCLUSIVE MODE") curs.execute("LOCK table1 IN ACCESS EXCLUSIVE MODE")
except psycopg2.DatabaseError, exc: except psycopg2.DatabaseError as exc:
self.thread2_error = exc self.thread2_error = exc
step2.set() step2.set()
conn.close() conn.close()
@ -195,7 +195,7 @@ class DeadlockSerializationTests(ConnectingTestCase):
step2.wait() step2.wait()
curs.execute("UPDATE table1 SET name='task1' WHERE id = 1") curs.execute("UPDATE table1 SET name='task1' WHERE id = 1")
conn.commit() conn.commit()
except psycopg2.DatabaseError, exc: except psycopg2.DatabaseError as exc:
self.thread1_error = exc self.thread1_error = exc
step1.set() step1.set()
conn.close() conn.close()
@ -207,7 +207,7 @@ class DeadlockSerializationTests(ConnectingTestCase):
step1.wait() step1.wait()
curs.execute("UPDATE table1 SET name='task2' WHERE id = 1") curs.execute("UPDATE table1 SET name='task2' WHERE id = 1")
conn.commit() conn.commit()
except psycopg2.DatabaseError, exc: except psycopg2.DatabaseError as exc:
self.thread2_error = exc self.thread2_error = exc
step2.set() step2.set()
conn.close() conn.close()

View File

@ -166,6 +166,13 @@ class TypesBasicTests(ConnectingTestCase):
curs.execute("select col from array_test where id = 2") curs.execute("select col from array_test where id = 2")
self.assertEqual(curs.fetchone()[0], []) self.assertEqual(curs.fetchone()[0], [])
@testutils.skip_before_postgres(8, 4)
def testNestedEmptyArray(self):
# issue #788
curs = self.conn.cursor()
curs.execute("select 10 = any(%s::int[])", ([[]], ))
self.assertFalse(curs.fetchone()[0])
def testEmptyArrayNoCast(self): def testEmptyArrayNoCast(self):
s = self.execute("SELECT '{}' AS foo") s = self.execute("SELECT '{}' AS foo")
self.assertEqual(s, '{}') self.assertEqual(s, '{}')
@ -223,16 +230,31 @@ class TypesBasicTests(ConnectingTestCase):
curs.execute("insert into na (boola) values (%s)", ([True, None],)) curs.execute("insert into na (boola) values (%s)", ([True, None],))
curs.execute("insert into na (boola) values (%s)", ([None, None],)) curs.execute("insert into na (boola) values (%s)", ([None, None],))
# TODO: array of array of nulls are not supported yet curs.execute("insert into na (textaa) values (%s)", ([[None]],))
# curs.execute("insert into na (textaa) values (%s)", ([[None]],))
curs.execute("insert into na (textaa) values (%s)", ([['a', None]],)) curs.execute("insert into na (textaa) values (%s)", ([['a', None]],))
# curs.execute("insert into na (textaa) values (%s)", ([[None, None]],)) curs.execute("insert into na (textaa) values (%s)", ([[None, None]],))
# curs.execute("insert into na (intaa) values (%s)", ([[None]],))
curs.execute("insert into na (intaa) values (%s)", ([[None]],))
curs.execute("insert into na (intaa) values (%s)", ([[42, None]],)) curs.execute("insert into na (intaa) values (%s)", ([[42, None]],))
# curs.execute("insert into na (intaa) values (%s)", ([[None, None]],)) curs.execute("insert into na (intaa) values (%s)", ([[None, None]],))
# curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],)) curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],))
# curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],)) curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
@testutils.skip_before_postgres(8, 2)
def testNestedArrays(self):
curs = self.conn.cursor()
for a in [
[[1]],
[[None]],
[[None, None, None]],
[[None, None], [1, None]],
[[None, None], [None, None]],
[[[None, None], [None, None]]],
]:
curs.execute("select %s::int[]", (a,))
self.assertEqual(curs.fetchone()[0], a)
@testutils.skip_from_python(3) @testutils.skip_from_python(3)
def testTypeRoundtripBuffer(self): def testTypeRoundtripBuffer(self):
@ -443,7 +465,7 @@ class ByteaParserTest(unittest.TestCase):
def setUp(self): def setUp(self):
try: try:
self._cast = self._import_cast() self._cast = self._import_cast()
except Exception, e: except Exception as e:
self._cast = None self._cast = None
self._exc = e self._exc = e

View File

@ -113,9 +113,14 @@ class TypesExtrasTests(ConnectingTestCase):
psycopg2.extensions.adapt, Foo(), ext.ISQLQuote, None) psycopg2.extensions.adapt, Foo(), ext.ISQLQuote, None)
try: try:
psycopg2.extensions.adapt(Foo(), ext.ISQLQuote, None) psycopg2.extensions.adapt(Foo(), ext.ISQLQuote, None)
except psycopg2.ProgrammingError, err: except psycopg2.ProgrammingError as err:
self.failUnless(str(err) == "can't adapt type 'Foo'") self.failUnless(str(err) == "can't adapt type 'Foo'")
def test_point_array(self):
# make sure a point array is never casted to a float array,
# see https://github.com/psycopg/psycopg2/issues/613
s = self.execute("""SELECT '{"(1,2)","(3,4)"}' AS foo""")
self.failUnless(s == """{"(1,2)","(3,4)"}""")
def skip_if_no_hstore(f): def skip_if_no_hstore(f):
@wraps(f) @wraps(f)
@ -173,8 +178,8 @@ class HstoreTestCase(ConnectingTestCase):
m = re.match(br'hstore\(ARRAY\[([^\]]+)\], ARRAY\[([^\]]+)\]\)', q) m = re.match(br'hstore\(ARRAY\[([^\]]+)\], ARRAY\[([^\]]+)\]\)', q)
self.assert_(m, repr(q)) self.assert_(m, repr(q))
kk = m.group(1).split(b", ") kk = m.group(1).split(b",")
vv = m.group(2).split(b", ") vv = m.group(2).split(b",")
ii = zip(kk, vv) ii = zip(kk, vv)
ii.sort() ii.sort()

View File

@ -28,7 +28,7 @@ from __future__ import with_statement
import psycopg2 import psycopg2
import psycopg2.extensions as ext import psycopg2.extensions as ext
from testutils import unittest, ConnectingTestCase from testutils import unittest, ConnectingTestCase, skip_before_postgres
class WithTestCase(ConnectingTestCase): class WithTestCase(ConnectingTestCase):
@ -212,11 +212,16 @@ class WithCursorTestCase(WithTestCase):
with conn.cursor('named') as cur: with conn.cursor('named') as cur:
cur.execute("select 1/0") cur.execute("select 1/0")
cur.fetchone() cur.fetchone()
except psycopg2.DataError, e: except psycopg2.DataError as e:
self.assertEqual(e.pgcode, '22012') self.assertEqual(e.pgcode, '22012')
else: else:
self.fail("where is my exception?") self.fail("where is my exception?")
@skip_before_postgres(8, 2)
def test_named_with_noop(self):
with self.conn.cursor('named') as cur:
pass
def test_suite(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -116,7 +116,7 @@ class ConnectingTestCase(unittest.TestCase):
def connect(self, **kwargs): def connect(self, **kwargs):
try: try:
self._conns self._conns
except AttributeError, e: except AttributeError as e:
raise AttributeError( raise AttributeError(
"%s (did you forget to call ConnectingTestCase.setUp()?)" "%s (did you forget to call ConnectingTestCase.setUp()?)"
% e) % e)
@ -149,7 +149,7 @@ class ConnectingTestCase(unittest.TestCase):
conn = self.connect(**kwargs) conn = self.connect(**kwargs)
if conn.async_ == 1: if conn.async_ == 1:
self.wait(conn) self.wait(conn)
except psycopg2.OperationalError, e: except psycopg2.OperationalError as e:
# If pgcode is not set it is a genuine connection error # If pgcode is not set it is a genuine connection error
# Otherwise we tried to run some bad operation in the connection # Otherwise we tried to run some bad operation in the connection
# (e.g. bug #482) and we'd rather know that. # (e.g. bug #482) and we'd rather know that.
@ -388,7 +388,7 @@ def skip_if_no_superuser(f):
from psycopg2 import ProgrammingError from psycopg2 import ProgrammingError
try: try:
return f(self) return f(self)
except ProgrammingError, e: except ProgrammingError as e:
import psycopg2.errorcodes import psycopg2.errorcodes
if e.pgcode == psycopg2.errorcodes.INSUFFICIENT_PRIVILEGE: if e.pgcode == psycopg2.errorcodes.INSUFFICIENT_PRIVILEGE:
self.skipTest("skipped because not superuser") self.skipTest("skipped because not superuser")