Compare commits

...

106 Commits

Author SHA1 Message Date
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 965 additions and 423 deletions

View File

@ -13,52 +13,19 @@ environment:
matrix:
# For Python versions available on Appveyor, see
# 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)
# Py 3.3, 3.4 = VS Ver. 10.0 (VS 2010)
# 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
OPENSSL_VERSION: "1_0_2n"
POSTGRES_VERSION: "10_1"
PSYCOPG2_TESTDB: psycopg2_test
PSYCOPG2_TESTDB_USER: postgres
@ -86,7 +53,22 @@ cache:
init:
# 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'))
#
# 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 PATH=%PYTHON%;%PYTHON%\Scripts;C:\Program Files\Git\mingw64\bin;%PATH%
@ -162,8 +144,8 @@ install:
}
# Download OpenSSL source
- CD C:\Others
- IF NOT EXIST OpenSSL_1_0_2l.zip (
curl -fsSL -o OpenSSL_1_0_2l.zip https://github.com/openssl/openssl/archive/OpenSSL_1_0_2l.zip
- IF NOT EXIST OpenSSL_%OPENSSL_VERSION%.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
@ -175,15 +157,15 @@ install:
# - nmake build_libs install_dev
- IF NOT EXIST %OPENSSLTOP%\lib\ssleay32.lib (
CD %BUILD_DIR% &&
7z x C:\Others\OpenSSL_1_0_2l.zip &&
CD openssl-OpenSSL_1_0_2l &&
7z x C:\Others\OpenSSL_%OPENSSL_VERSION%.zip &&
CD openssl-OpenSSL_%OPENSSL_VERSION% &&
perl Configure %TARGET% no-asm no-shared no-zlib --prefix=%OPENSSLTOP% --openssldir=%OPENSSLTOP% &&
CALL ms\%DO% &&
nmake -f ms\nt.mak init headers lib &&
COPY inc32\openssl\*.h %OPENSSLTOP%\include\openssl &&
COPY out32\*.lib %OPENSSLTOP%\lib &&
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
@ -197,8 +179,8 @@ install:
# Download PostgreSQL source
- CD C:\Others
- IF NOT EXIST postgres-REL_10_0.zip (
curl -fsSL -o postgres-REL_10_0.zip https://github.com/postgres/postgres/archive/REL_10_0.zip
- IF NOT EXIST postgres-REL_%POSTGRES_VERSION%.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)
@ -209,11 +191,11 @@ install:
# Prepare local include directory for building from
# Build pg_config in place
# 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 (
CD %BUILD_DIR% &&
7z x C:\Others\postgres-REL_10_0.zip &&
CD postgres-REL_10_0\src\tools\msvc &&
7z x C:\Others\postgres-REL_%POSTGRES_VERSION%.zip &&
CD postgres-REL_%POSTGRES_VERSION%\src\tools\msvc &&
ECHO $config-^>{ldap} = 0; > config.pl &&
ECHO $config-^>{openssl} = "%OPENSSLTOP:\=\\%"; >> config.pl &&
ECHO.>> config.pl &&

13
LICENSE
View File

@ -1,5 +1,5 @@
psycopg2 and the LGPL
=====================
---------------------
psycopg2 is free software: you can redistribute it and/or modify it
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
====================
--------------------
If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e.,
every file inside the ZPsycopgDA directory) user the ZPL license as
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}:
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,
including commercial applications, and to alter it and redistribute it

65
NEWS
View File

@ -1,24 +1,71 @@
Current release
---------------
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()` (:ticket:`#781`).
- Fixed adaptation of arrays of empty arrays (:ticket:`#788`).
- Fixed segfault accessing the `connection.readonly` and
`connection.deferrable` repeatedly (:ticket:`#790`).
- `~psycopg2.extras.execute_values()` accepts `~psycopg2.sql.Composable`
objects (#794).
- `~psycopg2.errorcodes` map updated to PostgreSQL 11.
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 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
@ -113,9 +160,13 @@ New features:
Bug fixes:
- Throw an exception trying to pass ``NULL`` chars as parameters
(:ticket:`#420`).
- Fixed error caused by missing decoding `~psycopg2.extras.LoggingConnection`
(:ticket:`#483`).
- 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:
@ -129,14 +180,6 @@ Other changes:
(: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
^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -25,29 +25,40 @@ Documentation is included in the ``doc`` directory and is `available online`__.
.. __: http://initd.org/psycopg/docs/
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
Installation
------------
If your ``pip`` version supports wheel_ packages it should be possible to
install a binary version of Psycopg including all the dependencies from PyPI_.
Just run::
Building Psycopg requires a few prerequisites (a C compiler, some development
packages): please check the install_ and the faq_ documents in the ``doc`` dir
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
If you want to build Psycopg from source you will need some prerequisites (a C
compiler, development packages): please check the install_ and the faq_
documents in the ``doc`` dir for the details.
or using ``setup.py`` if you have downloaded the source package locally::
.. _wheel: http://pythonwheels.com/
.. _PyPI: https://pypi.python.org/pypi/psycopg2
$ python setup.py build
$ 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
.. _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/

View File

@ -29,8 +29,6 @@ doctest:
upload:
# this command requires ssh configured to the proper target
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:
$(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``.
.. __: https://testpypi.python.org/pypi/psycopg2
.. __: https://test.pypi.org/project/psycopg2/
.. __: https://wiki.python.org/moin/TestPyPI

View File

@ -295,7 +295,9 @@ something to read::
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
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'
Timeout
@ -483,7 +485,7 @@ details. You can check the `psycogreen`_ project for further informations and
resources about the topic.
.. _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
.. _Eventlet: http://eventlet.net/
.. _gevent: http://www.gevent.org/

View File

@ -61,9 +61,8 @@ except ImportError:
release = version
intersphinx_mapping = {
'py': ('http://docs.python.org/2', None),
'py3': ('http://docs.python.org/3', None),
}
'py': ('https://docs.python.org/3', None),
}
# Pattern to generate links to the bug tracker
ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s'
@ -101,6 +100,10 @@ default_role = 'obj'
# output. They are ignored by default.
#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.
pygments_style = 'sphinx'

View File

@ -41,11 +41,6 @@ The ``connection`` class
previously only valid PostgreSQL identifiers were accepted as
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
cursors. The class returned must be a subclass of
`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'
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)

View File

@ -99,20 +99,6 @@ Real dictionary cursor
.. 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:: NamedTupleConnection
@ -403,7 +389,7 @@ The individual messages in the replication stream are represented by
class LogicalStreamConsumer(object):
...
# ...
def __call__(self, msg):
self.process_message(msg.payload)
@ -501,7 +487,7 @@ The individual messages in the replication stream are represented by
from datetime import datetime
def consume(msg):
...
# ...
keepalive_interval = 10.0
while True:
@ -563,7 +549,7 @@ that the last `!simplejson` version supporting Python 2.4 is the 2.0.9.
.. |pgjson| replace:: :sql:`json`
.. |jsonb| replace:: :sql:`jsonb`
.. _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
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
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

View File

@ -48,6 +48,7 @@ Psycopg 2 is both Unicode and Python 3 friendly.
errorcodes
faq
news
license
.. 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
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/
.. _Python: http://www.python.org/
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
@ -32,94 +22,20 @@ The current `!psycopg2` implementation supports:
.. index::
single: Install; from PyPI
single: Install; wheel
single: Wheel
single: Prerequisites
Binary install from PyPI
------------------------
Prerequisites
-------------
`!psycopg2` is `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 including all the dependencies simply
using:
The current `!psycopg2` implementation supports:
.. code-block:: console
..
NOTE: keep consistent with setup.py and the /features/ page.
$ pip install psycopg2
Make sure to use an up-to-date version of :program:`pip` (you can upgrade it
using something like ``pip install -U pip``)
.. __: 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/
- Python 2 versions from 2.6 to 2.7
- Python 3 versions from 3.2 to 3.7
- PostgreSQL server versions from 7.4 to 10
- PostgreSQL client library version from 9.1
@ -128,8 +44,10 @@ page`__ or from PyPI_.
Build prerequisites
^^^^^^^^^^^^^^^^^^^
These notes illustrate how to compile Psycopg on Linux. If you want to compile
Psycopg on other platforms you may have to adjust some details accordingly.
The build prerequisites are to be met in order to install Psycopg from source
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
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:
.. code-block:: console
$ pip install psycopg2
or, from the directory containing the source code:
.. code-block:: console
$ 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::
single: setup.py
single: setup.cfg
Non-standard builds
^^^^^^^^^^^^^^^^^^^
-------------------
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
``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:
@ -300,10 +313,14 @@ Try the following. *In order:*
- Google for `!psycopg2` *your error message*. Especially useful the week
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
ever and about the quality time you have wasted figuring out the correct
: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
=============

View File

@ -24,13 +24,18 @@ directly in the client application.
.. 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)
Put away a connection.
If *close* is `!True`, discard the connection from the pool.
*key* should be used consistently with `getconn()`.
.. 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:
- 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,
- terminate transactions using the methods `~connection.commit()` or
@ -73,70 +73,97 @@ The main entry points of Psycopg are:
Passing parameters to SQL queries
---------------------------------
Psycopg casts Python variables to SQL literals by type. Many standard Python types
are already `adapted to the correct SQL representation`__.
Psycopg converts Python variables to SQL values using their types: the Python
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_
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(
... """INSERT INTO some_table (an_int, a_date, a_string)
... VALUES (%s, %s, %s);""",
>>> cur.execute("""
... INSERT INTO some_table (an_int, a_date, a_string)
... VALUES (%s, %s, %s);
... """,
... (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)
VALUES (10, '2005-11-18', 'O''Reilly');
VALUES (10, '2005-11-18', 'O''Reilly');
Named arguments are supported too using :samp:`%({name})s` placeholders.
Using named arguments the values can be passed to the query in any order and
many placeholders can use the same values::
Named arguments are supported too using :samp:`%({name})s` placeholders in the
query and specifying the values into a mapping. Using named arguments allows
to specify the values in any order and to repeat the same value in several
places in the query::
>>> cur.execute(
... """INSERT INTO some_table (an_int, a_date, another_date, a_string)
... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);""",
>>> cur.execute("""
... INSERT INTO some_table (an_int, a_date, another_date, a_string)
... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);
... """,
... {'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
can use the ``%%`` string. Using characters ``%``, ``(``, ``)`` in the
argument names is not supported.
can use the ``%%`` string::
>>> 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
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.
|sql-warn|__.
|sql-warn|__:
.. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values
into queries
.. __: sql-injection_
- 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)", (42,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct
>>> cur.execute("INSERT INTO numbers VALUES (%s, %s)" % (10, 20)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s, %s)", (10, 20)) # correct
- For positional variables binding, *the second argument must always be a
sequence*, even if it contains a single variable. And remember that Python
requires a comma to create a single element tuple::
sequence*, even if it contains a single variable (remember that Python
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",)) # 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
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.
- The placeholder *must not be quoted*. Psycopg will add quotes where needed::
>>> 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
@ -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
in some other way than using the |DECLARE| executed by
`~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 $$
BEGIN

View File

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

View File

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

View File

@ -30,10 +30,7 @@ import sys as _sys
import time as _time
import re as _re
try:
import logging as _logging
except:
_logging = None
import logging as _logging
import psycopg2
from psycopg2 import extensions as _ext
@ -189,7 +186,7 @@ class DictRow(list):
def get(self, x, default=None):
try:
return self[x]
except:
except Exception:
return default
def iteritems(self):
@ -363,12 +360,26 @@ class NamedTupleCursor(_cursor):
try:
from collections import namedtuple
except ImportError, _exc:
except ImportError as _exc:
def _make_nt(self):
raise self._exc
else:
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):
@ -741,8 +752,8 @@ def wait_select(conn):
The function is an example of a wait callback to be registered with
`~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
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)])
'''
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
# there will be some decoding error because of stupid codec used, and Py3
# doesn't implement % on bytes.

View File

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

View File

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

View File

@ -100,7 +100,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
char buffer[8];
int i;
int a = obj->microseconds;
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
for (i=0; i < 6 ; i++) {
buffer[5-i] = '0' + (a % 10);
@ -109,7 +109,9 @@ _pydatetime_string_delta(pydatetimeObject *self)
buffer[6] = '\0';
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 *

View File

@ -38,13 +38,14 @@ list_quote(listObject *self)
{
/* adapt the list by calling adapt() recursively and then wrapping
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
* so we use the {NULL,...} syntax. Note however that list of lists where
* some element is a list of only null still fails: for that we should use
* the '{...}' syntax uniformly but we cannot do it in the current
* infrastructure. TODO in psycopg3 */
* so we use the {NULL,...} syntax. The same syntax is also necessary
* to convert array of arrays containing only nulls. */
int all_nulls = 1;
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
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++) {
PyObject *quoted;
for (i = 0; i < len; i++) {
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
if (wrapped == Py_None) {
Py_INCREF(psyco_null);
quoted = psyco_null;
qs[i] = psyco_null;
}
else {
quoted = microprotocol_getquoted(wrapped,
(connectionObject*)self->connection);
if (quoted == NULL) goto error;
all_nulls = 0;
if (!(qs[i] = microprotocol_getquoted(
wrapped, (connectionObject*)self->connection))) {
goto exit;
}
/* 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;
}
}
/* 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);
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
}
/* now that we have a tuple of adapted objects we just need to join them
and put "ARRAY[] around the result */
str = Bytes_FromString(", ");
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
if (joined == NULL) goto error;
/* Create an array literal, usually ARRAY[...] but if the contents are
* all NULL or array of NULL we must use the '{...}' syntax
*/
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
PyErr_NoMemory();
goto exit;
}
/* PG doesn't like ARRAY[NULL..] */
if (!all_nulls) {
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
} else {
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined));
strcpy(ptr, "ARRAY[");
ptr += 6;
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:
Py_XDECREF(tmp);
Py_XDECREF(str);
Py_XDECREF(joined);
res = Bytes_FromStringAndSize(buf, ptr - buf);
exit:
if (qs) {
for (i = 0; i < len; i++) {
PyObject *q = qs[i];
Py_XDECREF(q);
}
PyMem_Free(qs);
}
PyMem_Free(buf);
return res;
}

View File

@ -154,8 +154,7 @@ typedef unsigned __int64 uint64_t;
#endif
/* what's this, we have no round function either? */
#if (defined(__FreeBSD__) && __FreeBSD_version < 503000) \
|| (defined(_WIN32) && !defined(__GNUC__)) \
#if (defined(_WIN32) && !defined(__GNUC__)) \
|| (defined(sun) || defined(__sun__)) \
&& (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.
*
* 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;
PGresult *pgres = 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,
"the 'deferrable' setting is only available"
" from PostgreSQL 9.1");
@ -1209,24 +1214,24 @@ conn_set_session(connectionObject *self, int autocommit,
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
if (autocommit) {
/* we are in autocommit state, so no BEGIN will be issued:
if (want_autocommit) {
/* we are or are going in autocommit state, so no BEGIN will be issued:
* configure the session with the characteristics requested */
if (isolevel != self->isolevel) {
if (isolevel != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self,
"default_transaction_isolation", srv_isolevels[isolevel],
&pgres, &error, &_save)) {
goto endlock;
}
}
if (readonly != self->readonly) {
if (readonly != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self,
"default_transaction_read_only", srv_state_guc[readonly],
&pgres, &error, &_save)) {
goto endlock;
}
}
if (deferrable != self->deferrable) {
if (deferrable != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", srv_state_guc[deferrable],
&pgres, &error, &_save)) {
@ -1251,7 +1256,7 @@ conn_set_session(connectionObject *self, int autocommit,
goto endlock;
}
}
if (self->deferrable != STATE_DEFAULT) {
if (self->server_version >= 90100 && self->deferrable != STATE_DEFAULT) {
if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", "default",
&pgres, &error, &_save)) {
@ -1260,10 +1265,18 @@ conn_set_session(connectionObject *self, int autocommit,
}
}
self->autocommit = autocommit;
self->isolevel = isolevel;
self->readonly = readonly;
self->deferrable = deferrable;
if (autocommit != SRV_STATE_UNCHANGED) {
self->autocommit = autocommit;
}
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;
endlock:

View File

@ -39,6 +39,7 @@
extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[];
extern HIDDEN const char *srv_deferrable[];
extern HIDDEN const int SRV_STATE_UNCHANGED;
/** DBAPI methods **/
@ -561,10 +562,10 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
PyObject *deferrable = Py_None;
PyObject *autocommit = Py_None;
int c_isolevel = self->isolevel;
int c_readonly = self->readonly;
int c_deferrable = self->deferrable;
int c_autocommit = self->autocommit;
int c_isolevel = SRV_STATE_UNCHANGED;
int c_readonly = SRV_STATE_UNCHANGED;
int c_deferrable = SRV_STATE_UNCHANGED;
int c_autocommit = SRV_STATE_UNCHANGED;
static char *kwlist[] =
{"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 (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
if (0 > conn_set_session(self, value,
self->isolevel, self->readonly, self->deferrable)) {
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
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 (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit,
value, self->readonly, self->deferrable)) {
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
value, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return -1;
}
@ -715,13 +716,13 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
if (level == 0) {
if (0 > conn_set_session(self, 1,
self->isolevel, self->readonly, self->deferrable)) {
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return NULL;
}
}
else {
if (0 > conn_set_session(self, 0,
level, self->readonly, self->deferrable)) {
level, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return NULL;
}
}
@ -756,6 +757,7 @@ psyco_conn_readonly_get(connectionObject *self)
break;
}
Py_XINCREF(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 (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit,
self->isolevel, value, self->deferrable)) {
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
SRV_STATE_UNCHANGED, value, SRV_STATE_UNCHANGED)) {
return -1;
}
@ -802,6 +804,7 @@ psyco_conn_deferrable_get(connectionObject *self)
break;
}
Py_XINCREF(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 (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit,
self->isolevel, self->readonly, value)) {
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, value)) {
return -1;
}

View File

@ -49,14 +49,19 @@
static PyObject *
psyco_curs_close(cursorObject *self)
{
PyObject *rv = NULL;
char *lname = NULL;
EXC_IF_ASYNC_IN_PROGRESS(self, close);
if (self->closed) {
rv = Py_None;
Py_INCREF(rv);
goto exit;
}
if (self->qname != NULL) {
char buffer[128];
char buffer[256];
PGTransactionStatusType status;
if (self->conn) {
@ -66,22 +71,51 @@ psyco_curs_close(cursorObject *self)
status = PQTRANS_UNKNOWN;
}
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 {
if (status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR) {
Dprintf("skipping named curs close because tx status %d",
(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;
Dprintf("psyco_curs_close: cursor at %p closed", self);
rv = Py_None;
Py_INCREF(rv);
exit:
Py_RETURN_NONE;
PyMem_Free(lname);
return rv;
}
@ -592,8 +626,6 @@ psyco_curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs)
return NULL;
}
EXC_IF_CURS_CLOSED(self);
return _psyco_curs_mogrify(self, operation, vars);
}
@ -738,7 +770,7 @@ psyco_curs_fetchone(cursorObject *self)
EXC_IF_NO_MARK(self);
EXC_IF_ASYNC_IN_PROGRESS(self, 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 (_psyco_curs_prefetch(self) < 0) return NULL;
}
@ -787,7 +819,7 @@ psyco_curs_next_named(cursorObject *self)
if (self->row >= self->rowcount) {
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);
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) 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_ASYNC_IN_PROGRESS(self, fetchmany);
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);
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { 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_ASYNC_IN_PROGRESS(self, 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 (_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);
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);
}
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 (_psyco_curs_prefetch(self) < 0) return NULL;

View File

@ -52,6 +52,10 @@
#include "win32_support.h"
#endif
#if defined(__sun) && defined(__SVR4)
#include "solaris_support.h"
#endif
extern HIDDEN PyObject *psyco_DescriptionType;
extern HIDDEN const char *srv_isolevels[];
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
* 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 *
pq_get_last_result(connectionObject *conn)
{
PGresult *result = NULL, *res;
ExecStatusType status;
/* Read until PQgetResult gives a NULL */
while (NULL != (res = PQgetResult(conn->pgconn))) {
@ -1120,11 +1125,15 @@ pq_get_last_result(connectionObject *conn)
PQclear(result);
}
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
* 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;
}
}
@ -1285,7 +1294,7 @@ _pq_fetch_tuples(cursorObject *curs)
PyTuple_SET_ITEM(dtitem, 4, tmp);
if (!(tmp = PyInt_FromLong(fmod & 0xFFFF))) {
PyTuple_SET_ITEM(dtitem, 5, tmp);
goto err_for;
}
PyTuple_SET_ITEM(dtitem, 5, tmp);
}
@ -1948,8 +1957,9 @@ pq_fetch(cursorObject *curs, int no_result)
}
else {
Dprintf("pq_fetch: got tuples, discarding them");
/* TODO: is there any case in which PQntuples == PQcmdTuples? */
_read_rowcount(curs);
CLEARPGRES(curs->pgres);
curs->rowcount = -1;
ex = 0;
}
break;

View File

@ -72,6 +72,10 @@ HIDDEN PyObject *psyco_null = NULL;
/* The type of the cursor.description items */
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 **/
#define psyco_connect_doc \
"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n"
@ -885,7 +889,7 @@ INIT_MODULE(_psycopg)(void)
psycopg_debug_enabled = 1;
#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 */
Py_TYPE(&connectionType) = &PyType_Type;
@ -1017,7 +1021,7 @@ INIT_MODULE(_psycopg)(void)
if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; }
/* 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_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
PyModule_AddIntMacro(module, REPLICATION_PHYSICAL);

View File

@ -93,6 +93,7 @@ typedef unsigned long Py_uhash_t;
#ifndef PyNumber_Int
#define PyNumber_Int PyNumber_Long
#endif
#endif /* PY_MAJOR_VERSION > 2 */
#if PY_MAJOR_VERSION < 3
@ -128,6 +129,12 @@ typedef unsigned long Py_uhash_t;
#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);
/* 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_LONGINTEGERARRAY_types[] = {1016, 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_UNICODEARRAY_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;
case 'P':
PyErr_SetString(NotSupportedError,
"iso_8601 intervalstyle currently not supported");
return NULL;
default:
break;
}

View File

@ -168,11 +168,11 @@ psycopg_ensure_bytes(PyObject *obj)
PyObject *rv = NULL;
if (!obj) { return NULL; }
if (PyUnicode_CheckExact(obj)) {
if (PyUnicode_Check(obj)) {
rv = PyUnicode_AsUTF8String(obj);
Py_DECREF(obj);
}
else if (Bytes_CheckExact(obj)) {
else if (Bytes_Check(obj)) {
rv = obj;
}
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:
OpenSSL
Version: 1.0.2l
Version: 1.0.2o
PostgreSQL
Version: 10.0
Version: 10.4
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)
classes, errors = fetch_errors(
['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")
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
# 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
errors['55']['55P04'] = 'UNSAFE_NEW_ENUM_VALUE_USAGE'

View File

@ -56,15 +56,15 @@ fi
# Unsupported postgres versions that we still support
# Images built by https://github.com/psycopg/psycopg2-wheels/tree/build-dinosaurs
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.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
# Postgres built from master

View File

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

View File

@ -39,6 +39,7 @@ except ImportError:
from distutils.command.build_ext import build_ext
from distutils.sysconfig import get_python_inc
from distutils.ccompiler import get_default_compiler
from distutils.errors import CompileError
from distutils.util import get_platform
try:
@ -64,7 +65,7 @@ except ImportError:
# Take a look at http://www.python.org/dev/peps/pep-0440/
# for a consistent versioning pattern.
PSYCOPG_VERSION = '2.7.4.dev0'
PSYCOPG_VERSION = '2.7.6'
# 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 :: Zope Public License
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
@ -83,6 +85,8 @@ Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: Implementation :: CPython
Programming Language :: C
Programming Language :: SQL
Topic :: Database
@ -105,15 +109,23 @@ class PostgresConfig:
if not self.pg_config_exe:
self.pg_config_exe = self.autodetect_pg_config_path()
if self.pg_config_exe is None:
sys.stderr.write("""\
sys.stderr.write("""
Error: pg_config executable not found.
Please add the directory containing pg_config to the PATH
or specify the full executable path with the option:
pg_config is required to build psycopg2 from source. Please add the directory
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 ...
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)
@ -195,8 +207,7 @@ or with the pg_config option in 'setup.cfg'.
return None
pg_first_inst_key = winreg.OpenKey(reg,
'SOFTWARE\\PostgreSQL\\Installations\\'
+ first_sub_key_name)
'SOFTWARE\\PostgreSQL\\Installations\\' + first_sub_key_name)
try:
pg_inst_base_dir = winreg.QueryValueEx(
pg_first_inst_key, 'Base Directory')[0]
@ -289,13 +300,43 @@ class psycopg_build_ext(build_ext):
else:
return build_ext.get_export_symbols(self, extension)
built_files = 0
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]
# For Python versions that use MSVC compiler 2008, re-insert the
# 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()
# Default to the 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
# 7.x series supported by psycopg 2)
pgversion = pg_config_helper.query("version").split()[1]
except:
except Exception:
pgversion = "7.4.0"
verre = re.compile(
@ -420,11 +461,14 @@ class psycopg_build_ext(build_ext):
m = verre.match(pgversion)
if m:
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():
pgminor = 0
if pgpatch is None or not pgpatch.isdigit():
pgpatch = 0
pgmajor = int(pgmajor)
pgminor = int(pgminor)
pgpatch = int(pgpatch)
else:
@ -480,7 +524,7 @@ data_files = []
sources = [
'psycopgmodule.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',
'cursor_int.c', 'cursor_type.c',
@ -570,10 +614,7 @@ if version_flags:
else:
PSYCOPG_VERSION_EX = PSYCOPG_VERSION
if not PLATFORM_IS_WINDOWS:
define_macros.append(('PSYCOPG_VERSION', '"' + PSYCOPG_VERSION_EX + '"'))
else:
define_macros.append(('PSYCOPG_VERSION', '\\"' + PSYCOPG_VERSION_EX + '\\"'))
define_macros.append(('PSYCOPG_VERSION', PSYCOPG_VERSION_EX))
if parser.has_option('build_ext', 'have_ssl'):
have_ssl = int(parser.get('build_ext', 'have_ssl'))
@ -617,7 +658,7 @@ try:
f = open("README.rst")
readme = f.read()
f.close()
except:
except Exception:
print("failed to read readme: ignoring...")
readme = __doc__

View File

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

View File

@ -444,12 +444,18 @@ class AsyncTests(ConnectingTestCase):
try:
cnn = psycopg2.connect('dbname=thisdatabasedoesntexist', async_=True)
self.wait(cnn)
except psycopg2.Error, e:
except psycopg2.Error as e:
self.assertNotEqual(str(e), "asynchronous connection failed",
"connection error reason lost")
else:
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():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

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

View File

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

View File

@ -246,6 +246,13 @@ class ConnectionTests(ConnectingTestCase):
else:
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):
from weakref import ref
import gc
@ -358,7 +365,7 @@ class ParseDsnTestCase(ConnectingTestCase):
try:
# unterminated quote after dbname:
ext.parse_dsn("dbname='test 2 user=tester password=secret")
except ProgrammingError, e:
except ProgrammingError as e:
raised = True
self.assertTrue(str(e).find('secret') < 0,
"DSN was not exposed in error message")
@ -376,7 +383,7 @@ class ParseDsnTestCase(ConnectingTestCase):
try:
# extra '=' after port value
ext.parse_dsn(dsn='postgresql://tester:secret@/test?port=1111=x')
except psycopg2.ProgrammingError, e:
except psycopg2.ProgrammingError as e:
raised = True
self.assertTrue(str(e).find('secret') < 0,
"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, 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):
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', '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):
def setUp(self):
@ -1382,6 +1404,16 @@ class TransactionControlTests(ConnectingTestCase):
cur.execute("SHOW default_transaction_read_only;")
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):
def test_closed(self):
@ -1540,9 +1572,13 @@ import os
import sys
import time
import signal
import warnings
import threading
import psycopg2
# ignore wheel deprecation warning
with warnings.catch_warnings():
warnings.simplefilter('ignore')
import psycopg2
def handle_sigabort(sig, frame):
sys.exit(1)

View File

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

View File

@ -121,6 +121,12 @@ class CursorTests(ConnectingTestCase):
nref2 = sys.getrefcount(foo)
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):
cur = self.conn.cursor()
self.assertRaises(psycopg2.ProgrammingError,
@ -437,6 +443,24 @@ class CursorTests(ConnectingTestCase):
self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3))
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)
def test_scroll(self):
cur = self.conn.cursor()
@ -561,7 +585,7 @@ class CursorTests(ConnectingTestCase):
# Issue #443 is in the async code too. Since the fix is duplicated,
# so is the test.
control_conn = self.conn
connect_func = lambda: self.connect(async=True)
connect_func = lambda: self.connect(async_=True)
wait_func = psycopg2.extras.wait_select
self._test_external_close(control_conn, connect_func, wait_func)
@ -592,6 +616,20 @@ class CursorTests(ConnectingTestCase):
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():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -437,6 +437,14 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
r = cur.fetchone()[0]
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.
if not hasattr(psycopg2.extensions, 'PYDATETIME'):
@ -638,7 +646,8 @@ class FromTicksTestCase(unittest.TestCase):
def test_date_value_error_sec_59_99(self):
from datetime import date
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):
from datetime import time

View File

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

View File

@ -19,7 +19,7 @@ from datetime import timedelta
import psycopg2
import psycopg2.extras
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):
@ -382,6 +382,24 @@ class NamedTupleCursorTest(ConnectingTestCase):
curs.execute("update nttest set s = s")
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
def test_minimal_generation(self):
# 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")
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):
cur = self.conn.cursor()
psycopg2.extras.execute_batch(cur,
@ -169,6 +179,16 @@ class TestExecuteValues(FastExecuteTestMixin, testutils.ConnectingTestCase):
cur.execute("select id, val from testfast order by id")
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):
cur = self.conn.cursor()
psycopg2.extras.execute_values(cur,

View File

@ -27,7 +27,7 @@ import psycopg2
import psycopg2.extensions
import psycopg2.extras
from testutils import ConnectingTestCase, slow
from testutils import ConnectingTestCase, skip_before_postgres, slow
class ConnectionStub(object):
@ -111,6 +111,12 @@ class GreenTestCase(ConnectingTestCase):
curs.execute("select 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):
def setUp(self):

View File

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

View File

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

View File

@ -166,6 +166,13 @@ class TypesBasicTests(ConnectingTestCase):
curs.execute("select col from array_test where id = 2")
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):
s = self.execute("SELECT '{}' AS foo")
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)", ([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)", ([[None, None]],))
# curs.execute("insert into na (intaa) values (%s)", ([[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)", ([[42, 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 (intaa) values (%s)", ([[None, 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)", ([[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)
def testTypeRoundtripBuffer(self):
@ -443,7 +465,7 @@ class ByteaParserTest(unittest.TestCase):
def setUp(self):
try:
self._cast = self._import_cast()
except Exception, e:
except Exception as e:
self._cast = None
self._exc = e

View File

@ -113,9 +113,14 @@ class TypesExtrasTests(ConnectingTestCase):
psycopg2.extensions.adapt, Foo(), ext.ISQLQuote, None)
try:
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'")
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):
@wraps(f)
@ -173,8 +178,8 @@ class HstoreTestCase(ConnectingTestCase):
m = re.match(br'hstore\(ARRAY\[([^\]]+)\], ARRAY\[([^\]]+)\]\)', q)
self.assert_(m, repr(q))
kk = m.group(1).split(b", ")
vv = m.group(2).split(b", ")
kk = m.group(1).split(b",")
vv = m.group(2).split(b",")
ii = zip(kk, vv)
ii.sort()

View File

@ -28,7 +28,7 @@ from __future__ import with_statement
import psycopg2
import psycopg2.extensions as ext
from testutils import unittest, ConnectingTestCase
from testutils import unittest, ConnectingTestCase, skip_before_postgres
class WithTestCase(ConnectingTestCase):
@ -212,11 +212,16 @@ class WithCursorTestCase(WithTestCase):
with conn.cursor('named') as cur:
cur.execute("select 1/0")
cur.fetchone()
except psycopg2.DataError, e:
except psycopg2.DataError as e:
self.assertEqual(e.pgcode, '22012')
else:
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():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

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