Compare commits

...

82 Commits

Author SHA1 Message Date
Daniele Varrazzo
1690fbace1 Version bumped for released 2.6.2 2016-07-07 02:56:27 +01:00
Daniele Varrazzo
c5411f67c2 Added link from news file 2016-07-07 02:47:34 +01:00
Daniele Varrazzo
a5970a21a8 Bunch of test tweaks to make the test grid green 2016-07-04 23:01:49 +01:00
Daniele Varrazzo
98a9203827 Merge branch 'issue443' into maint_2_6 2016-07-04 22:07:41 +01:00
Daniele Varrazzo
9886a7210e Connection state fixed noted in the news 2016-07-04 22:07:28 +01:00
Daniele Varrazzo
f4b57822b0 Fixed tests on Py 2.5 2016-07-04 22:07:28 +01:00
Daniele Varrazzo
9d4e3a06b7 Skip killing test on brontosaur servers 2016-07-04 21:50:33 +01:00
Daniele Varrazzo
88af6e790c errorcodes map updated to PostgreSQL 9.5. 2016-07-01 20:08:53 +01:00
Daniele Varrazzo
5e7a4983c6 Merge branch 'bug-424' into maint_2_6 2016-07-01 19:30:01 +01:00
Daniele Varrazzo
230d9d6b1a Mention closing bug #424 in the news 2016-07-01 19:29:45 +01:00
Gabriel Kihlman
e3ba21039c Avoid a possible null deref, tz might be NULL.
Found by clang static analyzer.
2016-07-01 19:29:45 +01:00
Daniele Varrazzo
61764ea581 Allow adapting bytes using QuotedString on Python 3 too
Close #365.
2016-07-01 19:11:04 +01:00
Daniele Varrazzo
c585478dd1 Fixed encoding tests on Py3 2016-07-01 18:50:44 +01:00
Daniele Varrazzo
2b52469a0b Docs wrapping 2016-07-01 17:56:29 +01:00
Udi Oron
bbe6414ad3 Suggest installing psycopg2 in windows using pip
pip is becoming the standard method for installing python packages, and now binary wheels are a better and easier option for users:
https://github.com/psycopg/psycopg2/issues/368
2016-07-01 17:55:31 +01:00
Daniele Varrazzo
ece7fb43b5 Merge branch 'qstring-writable-encoding' into maint_2_6 2016-07-01 17:54:28 +01:00
Daniele Varrazzo
f335c46a54 Don't hope to encode stuff in an arbitrary encoding
libpq's PQescapeString will use the same encoding it has seen before in
a connection (static_client_encoding).

So I think I'll leave this feature here for people who know what is
doing, but won't really document it as a feature: it can't really work
in a generic way (unless adding some disgusting hack like creating a
fake connection with the encoding we want to call PQescapeStringConn
instead of PQescapeString).
2016-07-01 17:47:40 +01:00
Daniele Varrazzo
e062404d5c Test moved to the right module, cleanup, but same problem 2016-07-01 16:57:25 +01:00
Daniele Varrazzo
600c744fba Work in progress on writable encoding
Would help using adapt(unicode) to quote strings without a connection,
see ticket #331.

Currently in heisenbug state: if test_connection_wins_anyway and
test_encoding_default run (in this order), the latter fail because the
returned value is "'\xe8 '", with an extra space. Skipping the first
test, the second succeed.

The bad value is returned by the libpq:

    ql = PQescapeString(to+eq+1, from, len);

just returns len = 2 and an extra space in the string... meh.
2016-07-01 11:17:55 +01:00
Daniele Varrazzo
556f25153f Fixed build on win32
Fix #422.
2016-07-01 02:23:59 +01:00
Daniele Varrazzo
8dff6a38c7 Fixed segfault on repr() for uninitialized connections
Close #361.
2016-07-01 02:09:56 +01:00
Daniele Varrazzo
05f23df238 Wordsmithing on COPY commands
Address somehow issue #397.
2016-07-01 01:41:03 +01:00
Daniele Varrazzo
5f3be39e0a Document that the libpq must be available at runtime
Fix issue #408.
2016-07-01 01:21:08 +01:00
Gabriel Linder
4d0b449cbf Typo. 2016-07-01 01:21:08 +01:00
Bernhard M. Wiedemann
89f2ab9f05 dont claim copyright for future years
otherwise, when building from unchanged source in 2018,
it would claim Copyright 2018
which is not true

Being able to reproduce identical output from identical input
is important to Linux distributions
2016-07-01 01:21:08 +01:00
Federico Di Gregorio
e79949f76e Merge pull request #447 from gward/maint_2_6
Fix scattered grammar/spelling errors in comments, debug output, etc.
2016-06-29 08:38:48 +02:00
Greg Ward
077e1f0e8d Fix scattered grammar/spelling errors in comments, debug output, etc. 2016-06-28 18:16:16 -04:00
Greg Ward
53c1607644 Always raise OperationalError when connection was closed externally.
From the DB-API (https://www.python.org/dev/peps/pep-0249/):

  OperationalError

  Exception raised for errors that are related to the database's
  operation and not necessarily under the control of the programmer,
  e.g. an unexpected disconnect occurs, [...]

Additionally, psycopg2 was inconsistent, at least in the async case:
depending on how the "connection closed" error was reported from the
kernel to libpq, it would sometimes raise OperationalError and
sometimes DatabaseError. Now it always raises OperationalError.
2016-06-28 18:03:05 -04:00
Greg Ward
ac14957fbb Always detect when a connection is closed behind psycopg2's back.
There's a race condition that only seems to happen over Unix-domain
sockets. Sometimes, the closed socket is reported by the kernel to
libpq like this (captured with strace):

  sendto(3, "Q\0\0\0\34select pg_backend_pid()\0", 29, MSG_NOSIGNAL, NULL, 0) = 29
  recvfrom(3, "E\0\0\0mSFATAL\0C57P01\0Mterminating "..., 16384, 0, NULL, NULL) = 110
  recvfrom(3, 0x12d0330, 16384, 0, 0, 0)  = -1 ECONNRESET (Connection reset by peer)

That is, psycopg2/libpq sees no error when sending the first query
after the connection is closed, but gets an error reading the result.
In that case, everything worked fine.

But sometimes, the error manifests like this:

  sendto(3, "Q\0\0\0\34select pg_backend_pid()\0", 29, MSG_NOSIGNAL, NULL, 0) = -1 EPIPE (Broken pipe)
  recvfrom(3, "E\0\0\0mSFATAL\0C57P01\0Mterminating "..., 16384, 0, NULL, NULL) = 110
  recvfrom(3, "", 16274, 0, NULL, NULL)   = 0
  recvfrom(3, "", 16274, 0, NULL, NULL)   = 0

i.e. libpq received an error when sending the query. This manifests as
a slightly different exception from a slightly different place. More
importantly, in this case connection.closed is left at 0 rather than
being set to 2, and that is the bug I'm fixing here.

Note that we see almost identical behaviour for sync and async
connections, and the fixes are the same. So I added extremely similar
test cases.

Finally, there is still a bug here: for async connections, we
sometimes raise DatabaseError (incorrect) and sometimes raise
OperationalError (correct). Will fix that next.
2016-06-28 17:46:21 -04:00
Karl O. Pinc
2714c9c638 Improve sentence. 2016-03-10 12:07:38 +00:00
Jan Janßen
072b6ef98b Update psycopg1.py 2016-03-10 11:58:37 +00:00
Daniele Varrazzo
5aa7588f56 Merge branch 'py35' into maint_2_6 2016-03-08 05:15:47 +00:00
Daniele Varrazzo
0fa44ea943 Start advertising Py 3.5 support 2016-03-08 05:13:57 +00:00
Daniele Varrazzo
fd58f9105e Fixed read() exception propagation in copy_from
Close issue #412.
2016-03-08 05:12:06 +00:00
Daniele Varrazzo
ae87645c43 Merge branch 'setuptools' into maint_2_6 2016-03-08 04:34:41 +00:00
Daniele Varrazzo
40997d901b Fixed 'make sdist' to work with setuptools 2016-03-08 04:34:12 +00:00
Daniele Varrazzo
1f737c4d65 Merge branch 'msvc-2015-fix' into maint_2_6 2016-03-08 00:27:17 +00:00
Daniele Varrazzo
a355d48aeb Py 3.5 MSVC 2015 build fixed noted in news
Close issue #380.
2016-03-08 00:25:19 +00:00
Christian Ullrich
b6d1fbdfd1 Work around late initialization in distutils._msvccompiler. 2016-03-08 00:23:08 +00:00
Daniele Varrazzo
cbeea0f98f Merge branch 'setuptools' into maint_2_6 2016-03-07 10:39:03 +00:00
Daniele Varrazzo
88593602de setuptools in the news 2016-03-07 10:38:40 +00:00
Jason Erickson
ef09ebe62a Modify setup.py to support setuptools/wheel
To support creation of whl files for PyPI, setuptools need to be imported
instead of distutils.  Created try/except case to fall back to integrated
distutils if setuptools is not installed.
2016-03-05 13:22:39 -07:00
Daniele Varrazzo
2833d4f5ff Merge branch 'bug-382' into maint_2_6 2015-12-16 12:02:31 +00:00
Daniele Varrazzo
c20503544f Fixed race condition on import in errorcodes.lookup
Fixes #382.
2015-12-16 12:00:52 +00:00
Oleksandr Shulgin
ff4c52f435 Fix stale Dprintfs in pqpath.c referring to 'status' 2015-10-27 10:39:39 +00:00
Daniele Varrazzo
db9082b6e4 Decref the ssl module after importing 2015-10-01 17:02:43 +01:00
Daniele Varrazzo
78d6dca65d Bump to next version (uploading the docs) 2015-10-01 15:32:45 +01:00
Daniele Varrazzo
c076fc3a26 The wait_select callback can cancel a query using Ctrl-C
Fixes #333.
2015-10-01 15:27:31 +01:00
Daniele Varrazzo
347a64b979 Fixed PersistentConnectionPool on Python 3
Fixes ticket #348.
2015-10-01 14:44:56 +01:00
ClodoaldoPinto
a09efaf5a1 Typo correction 2015-10-01 14:31:51 +01:00
Daniele Varrazzo
1714bd9273 MSVC 2015 compiler support added to news file 2015-10-01 14:29:56 +01:00
Daniele Varrazzo
ca90fb2791 Merge branch 'msvc-2015-support' into maint_2_6 2015-10-01 13:36:15 +01:00
Photonios
9fd5def484 Fix for MSVC 2015: round has been added to this version 2015-10-01 13:35:58 +01:00
Photonios
4a6b31f56a Fix for MSVC 2015: isnan is supported in this version 2015-10-01 13:35:47 +01:00
Daniele Varrazzo
abf1f28c44 Report NotSupportedError for PGRES_COPY_BOTH and PGRES_SINGLE_TUPLE
Fixes #352.
2015-09-30 12:24:47 +01:00
Daniele Varrazzo
3e31fb359e Report the server response status on errors with no message
Suggested by Craig Ringer in pull request #353, should also give more
information for other cases we were reported on flaky servers (AWS,
digital ocean...), see bug #281.
2015-09-30 12:24:37 +01:00
Daniele Varrazzo
e707c3f657 Fixed build on Python 2.5 2015-06-15 10:31:14 +01:00
Daniele Varrazzo
b3c07fddb8 Fixed manifest trying to include Makefiles from build env 2015-06-15 03:43:11 +01:00
Daniele Varrazzo
c29dd8fffa Version bumped to release 2.6.1 2015-06-14 19:04:06 +01:00
Daniele Varrazzo
1aed1c6ecd Skip null array test on Postgres versions not supporting it 2015-06-14 19:04:04 +01:00
Daniele Varrazzo
6d2bd882ad Merge branch 'patch-328' into maint_2_6 2015-06-14 19:03:56 +01:00
Daniele Varrazzo
9021c90c07 Fixed compiler warnings about Py_ssize_t printf format 2015-06-14 19:03:01 +01:00
Jason Erickson
72c7374d8e Removed added Dprintf statements
Removed extra Dprintf statements added to trouble large objects
2015-06-08 14:05:05 -06:00
Jason Erickson
c4ffc0d940 Fix Windows 64bit lobject support for very (>2GB) large objects
The type 'long' with Windows Visual C is 32bits in size for both 32bit and 64bit platforms.  Changed type of variables that could be > 2GB from long to Py_ssize_t.
2015-06-08 11:37:23 -06:00
btubbs
fb487ff244 Notify example should pop the oldest message in conn.notifies, not the newest. 2015-06-02 17:07:19 +01:00
Pete Hollobon
526c9ea763 Change "non desiderable" to "undesirable" in docs
"desiderable" is considered obsolete
2015-06-02 13:03:11 +01:00
Daniele Varrazzo
57a4342946 Merge branch 'adapt-list-of-none' into maint_2_6 2015-06-02 12:41:10 +01:00
Daniele Varrazzo
6192649d25 Fixed adaptation of lists of None
Note: lists of lists of None are not supported yet.
2015-06-02 12:38:59 +01:00
Daniele Varrazzo
9ee15f9845 OpenSSL deadlock fix noted in NEWSfile 2015-05-03 12:27:27 +01:00
Jan Urbański
60fe90d531 Make sure libcrypto threadsafety callbacks are properly set up
Multithreaded programs using libcrypto (part of OpenSSL) need to set up
callbacks to ensure safe execution. Both Python and libpq set up those
callbacks, which might lead to a conflict.

To avoid leaving dangling function pointers when being unloaded, libpq sets up
and removes the callbacks every time a SSL connection it opened and closed. If
another Python thread is performing unrelated SSL operations (like connecting
to a HTTPS server), this might lead to deadlocks, as described in
http://www.postgresql.org/message-id/871tlzrlkq.fsf@wulczer.org

Even if the problem will be remediated in libpq, it's still useful to have it
fixed in psycopg2. The solution is to use Python's own libcrypto callbacks and
completely disable handling them in libpq.
2015-05-03 12:19:50 +01:00
Daniele Varrazzo
76ac3ec34e Improve docs about connection's with only closing the transaction 2015-05-03 11:59:06 +01:00
Daniele Varrazzo
a70a541e0b Unlock the connection after PQflush error
Apparently this has never happened... anyway the code path was wrong.

Fixes #294.
2015-05-03 11:58:16 +01:00
Daniele Varrazzo
8739484b61 Added makefile target to upload docs 2015-05-03 10:25:51 +01:00
Daniele Varrazzo
b77a8033d3 Added note about table names to be escaped in copy_*() methods. 2015-05-03 10:22:14 +01:00
Hyunjun Kim
00946ae7f4 Fix several typos 2015-05-03 10:00:11 +01:00
Daniele Varrazzo
862f47040c Fix to MinTimeLoggingCursor.callproc() noted in NEWSfile 2015-05-03 09:58:45 +01:00
andrew deryabin
6a62b4fa1d Fixed MinTimeLoggingCursor.callproc() 2015-05-03 09:55:40 +01:00
Daniele Varrazzo
b2734c2cbd Wordsmith on connection.set_session()
Fixes #310.
2015-05-03 09:42:32 +01:00
Daniele Varrazzo
a24597d0e0 Fixed connection.poll() docstring
Fixes #312
2015-05-03 09:30:04 +01:00
Daniele Varrazzo
44ba7df67f Docs build process and docs cleaned up 2015-04-28 09:30:59 +01:00
Daniele Varrazzo
9ddc224c69 Added missing files needed to build the docs
Fixes #291
2015-04-28 08:33:46 +01:00
Daniele Varrazzo
7b78354d58 Bump to next dev version number 2015-04-28 08:33:39 +01:00
42 changed files with 894 additions and 232 deletions

View File

@ -2,9 +2,10 @@ recursive-include psycopg *.c *.h *.manifest
recursive-include lib *.py
recursive-include tests *.py
recursive-include examples *.py somehackers.jpg whereareyou.jpg
recursive-include doc README SUCCESS COPYING.LESSER pep-0249.txt
include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt
include doc/Makefile doc/requirements.txt
recursive-include doc/src *.rst *.py *.css Makefile
recursive-include scripts *.py *.sh
include scripts/maketypes.sh scripts/buildtypes.py
include AUTHORS README.rst INSTALL LICENSE NEWS
include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile
include MANIFEST.in setup.py setup.cfg Makefile

View File

@ -92,24 +92,19 @@ $(PACKAGE)/tests/%.py: tests/%.py
$(PYTHON) setup.py build_py $(BUILD_OPT)
touch $@
$(SDIST): MANIFEST $(SOURCE)
$(SDIST): $(SOURCE)
$(PYTHON) setup.py sdist $(SDIST_OPT)
MANIFEST: MANIFEST.in $(SOURCE)
# Run twice as MANIFEST.in includes MANIFEST
$(PYTHON) setup.py sdist --manifest-only
$(PYTHON) setup.py sdist --manifest-only
# docs depend on the build as it partly use introspection.
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
PYTHONPATH=:$(BUILD_DIR):$$PYTHONPATH $(MAKE) -C doc html
$(MAKE) -C doc html
doc/psycopg2.txt: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
PYTHONPATH=$(BUILD_DIR):$$PYTHONPATH $(MAKE) -C doc text
$(MAKE) -C doc text
doc/docs.zip: doc/html/genindex.html
(cd doc/html && zip -r ../docs.zip *)
clean:
rm -rf build MANIFEST
rm -rf build
$(MAKE) -C doc clean

36
NEWS
View File

@ -1,6 +1,40 @@
Current release
---------------
What's new in psycopg 2.6.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed inconsistent state in externally closed connections
(:tickets:`#263, #311, #443`).
- Report the server response status on errors (such as :ticket:`#281`).
- Raise `!NotSupportedError` on unhandled server response status
(:ticket:`#352`).
- Allow overriding string adapter encoding with no connection (:ticket:`#331`).
- The `~psycopg2.extras.wait_select` callback allows interrupting a
long-running query in an interactive shell using :kbd:`Ctrl-C`
(:ticket:`#333`).
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`).
- Allow adapting bytes using `~psycopg2.extensions.QuotedString` on Python 3
(:ticket:`#365`).
- Added support for setuptools/wheel (:ticket:`#370`).
- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`).
- Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`).
- Fixed `!read()` exception propagation in copy_from (:ticket:`#412`).
- Fixed possible NULL TZ decref (:ticket:`#424`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.5.
What's new in psycopg 2.6.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Lists consisting of only `None` are escaped correctly (:ticket:`#285`).
- Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`).
- Correctly unlock the connection after error in flush (:ticket:`#294`).
- Fixed `!MinTimeLoggingCursor.callproc()` (:ticket:`#309`).
- Added support for MSVC 2015 compiler (:ticket:`#350`).
What's new in psycopg 2.6
-------------------------
@ -13,7 +47,7 @@ New features:
Bug fixes:
- Json apapter's `!str()` returns the adapted content instead of the `!repr()`
- Json adapter's `!str()` returns the adapted content instead of the `!repr()`
(:ticket:`#191`).

View File

@ -7,22 +7,33 @@ check: doctest
# The environment is currently required to build the documentation.
# It is not clean by 'make clean'
help:
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
PYTHON := python$(PYTHON_VERSION)
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
SPHOPTS=PYTHONPATH=$$(pwd)/../build/lib.$(PYTHON_VERSION)/ SPHINXBUILD=$$(pwd)/env/bin/sphinx-build
html:
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
$(MAKE) PYTHON=$(PYTHON) -C .. package
$(MAKE) $(SPHOPTS) -C src $@
cp -r src/_build/html .
text:
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
$(MAKE) PYTHON=$(PYTHON) -C .. package
$(MAKE) $(SPHOPTS) -C src $@
cd src && tools/stitch_text.py index.rst _build/text > ../psycopg2.txt
doctest:
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
$(MAKE) PYTHON=$(PYTHON) -C .. package
$(MAKE) $(SPHOPTS) -C src $@
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) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
$(MAKE) $(SPHOPTS) -C src $@
rm -rf html psycopg2.txt
env: requirements.txt

View File

@ -1,42 +0,0 @@
How to build psycopg documentation
----------------------------------
- Install Sphinx, maybe in a virtualenv. Tested with Sphinx 0.6.4::
~$ virtualenv pd
New python executable in pd/bin/python
Installing setuptools............done.
~$ cd pd
~/pd$ source bin/activate
(pd)~/pd$
- Install Sphinx in the env::
(pd)~/pd$ easy_install sphinx
Searching for sphinx
Reading http://pypi.python.org/simple/sphinx/
Reading http://sphinx.pocoo.org/
Best match: Sphinx 0.6.4
...
Finished processing dependencies for sphinx
- Build psycopg2 and ensure the package can be imported (it will be used for
reading the version number, autodocs etc.)::
(pd)~/pd/psycopg2$ python setup.py build
(pd)~/pd/psycopg2$ python setup.py install
running install
...
creating ~/pd/lib/python2.6/site-packages/psycopg2
...
- Move to the ``doc`` dir and run ``make`` from there::
(pd)~/pd/psycopg2$ cd doc/
(pd)~/pd/psycopg2/doc$ make
Running Sphinx v0.6.4
...
You should have the rendered documentation in ``./html`` and the text file
``psycopg2.txt`` now.

26
doc/README.rst Normal file
View File

@ -0,0 +1,26 @@
How to build psycopg documentation
----------------------------------
Building the documentation usually requires building the library too for
introspection, so you will need the same prerequisites_. The only extra
prerequisite is virtualenv_: the packages needed to build the docs will be
installed when building the env.
.. _prerequisites: http://initd.org/psycopg/docs/install.html#install-from-source
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
Build the env once with::
make env
Then you can build the documentation with::
make
Or the single targets::
make html
make text
You should find the rendered documentation in the ``html`` dir and the text
file ``psycopg2.txt``.

View File

@ -47,7 +47,7 @@ it is the class where query building, execution and result type-casting into
Python variables happens.
The `~psycopg2.extras` module contains several examples of :ref:`connection
and cursor sublcasses <cursor-subclasses>`.
and cursor subclasses <cursor-subclasses>`.
.. note::
@ -270,7 +270,7 @@ wasting resources.
A simple application could poll the connection from time to time to check if
something new has arrived. A better strategy is to use some I/O completion
function such as :py:func:`~select.select` to sleep until awaken from the kernel when there is
function such as :py:func:`~select.select` to sleep until awakened by the kernel when there is
some data to read on the connection, thereby using no CPU unless there is
something to read::
@ -291,7 +291,7 @@ something to read::
else:
conn.poll()
while conn.notifies:
notify = conn.notifies.pop()
notify = conn.notifies.pop(0)
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`

View File

@ -42,9 +42,7 @@ master_doc = 'index'
# General information about the project.
project = u'Psycopg'
from datetime import date
year = date.today().year
copyright = u'2001-%s, Federico Di Gregorio, Daniele Varrazzo' % year
copyright = u'2001-2016, Federico Di Gregorio, Daniele Varrazzo'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View File

@ -351,17 +351,14 @@ The ``connection`` class
.. method:: set_session(isolation_level=None, readonly=None, deferrable=None, autocommit=None)
Set one or more parameters for the next transactions or statements in
the current session. See |SET TRANSACTION|_ for further details.
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
.. _SET TRANSACTION: http://www.postgresql.org/docs/current/static/sql-set-transaction.html
the current session.
:param isolation_level: set the `isolation level`_ for the next
transactions/statements. The value can be one of the
:ref:`constants <isolation-level-constants>` defined in the
`~psycopg2.extensions` module or one of the literal values
``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
``SERIALIZABLE``.
transactions/statements. The value can be one of the literal
values ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE
READ``, ``SERIALIZABLE`` or the equivalent :ref:`constant
<isolation-level-constants>` defined in the `~psycopg2.extensions`
module.
:param readonly: if `!True`, set the connection to read only;
read/write if `!False`.
:param deferrable: if `!True`, set the connection to deferrable;
@ -370,19 +367,14 @@ The ``connection`` class
PostgreSQL session setting but an alias for setting the
`autocommit` attribute.
Parameter passed as `!None` (the default for all) will not be changed.
The parameters *isolation_level*, *readonly* and *deferrable* also
accept the string ``DEFAULT`` as a value: the effect is to reset the
parameter to the server default.
.. _isolation level:
http://www.postgresql.org/docs/current/static/transaction-iso.html
The function must be invoked with no transaction in progress. At every
function invocation, only the specified parameters are changed.
The default for the values are defined by the server configuration:
see values for |default_transaction_isolation|__,
Arguments set to `!None` (the default for all) will not be changed.
The parameters *isolation_level*, *readonly* and *deferrable* also
accept the string ``DEFAULT`` as a value: the effect is to reset the
parameter to the server default. Defaults are defined by the server
configuration: see values for |default_transaction_isolation|__,
|default_transaction_read_only|__, |default_transaction_deferrable|__.
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
@ -392,12 +384,20 @@ The ``connection`` class
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE
The function must be invoked with no transaction in progress.
.. note::
There is currently no builtin method to read the current value for
the parameters: use :sql:`SHOW default_transaction_...` to read
the values from the backend.
.. seealso:: |SET TRANSACTION|_ for further details about the behaviour
of the transaction parameters in the server.
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
.. _SET TRANSACTION: http://www.postgresql.org/docs/current/static/sql-set-transaction.html
.. versionadded:: 2.4.2
@ -419,8 +419,8 @@ The ``connection`` class
By default, any query execution, including a simple :sql:`SELECT`
will start a transaction: for long-running programs, if no further
action is taken, the session will remain "idle in transaction", a
condition non desiderable for several reasons (locks are held by
action is taken, the session will remain "idle in transaction", an
undesirable condition for several reasons (locks are held by
the session, tables bloat...). For long lived scripts, either
ensure to terminate a transaction as soon as possible or use an
autocommit connection.

View File

@ -494,6 +494,9 @@ The ``cursor`` class
.. rubric:: COPY-related methods
Efficiently copy data from file-like objects to the database and back. See
:ref:`copy` for an overview.
.. extension::
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
@ -502,7 +505,7 @@ The ``cursor`` class
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
Read data *from* the file-like object *file* appending them to
the table named *table*. See :ref:`copy` for an overview.
the table named *table*.
:param file: file-like object to read data from. It must have both
`!read()` and `!readline()` methods.
@ -524,6 +527,13 @@ The ``cursor`` class
>>> cur.fetchall()
[(6, 42, 'foo'), (7, 74, 'bar')]
.. note:: the name of the table is not quoted: if the table name
contains uppercase letters or special characters it must be quoted
with double quotes::
cur.copy_from(f, '"TABLE"')
.. versionchanged:: 2.0.6
added the *columns* parameter.
@ -553,6 +563,12 @@ The ``cursor`` class
2|\N|dada
...
.. note:: the name of the table is not quoted: if the table name
contains uppercase letters or special characters it must be quoted
with double quotes::
cur.copy_to(f, '"TABLE"')
.. versionchanged:: 2.0.6
added the *columns* parameter.

View File

@ -611,3 +611,6 @@ Coroutine support
.. autofunction:: wait_select(conn)
.. versionchanged:: 2.6.2
allow to cancel a query using :kbd:`Ctrl-C`, see
:ref:`the FAQ <faq-interrupt-query>` for an example.

View File

@ -223,6 +223,37 @@ What are the advantages or disadvantages of using named cursors?
little memory on the client and to skip or discard parts of the result set.
.. _faq-interrupt-query:
.. cssclass:: faq
How do I interrupt a long-running query in an interactive shell?
Normally the interactive shell becomes unresponsive to :kbd:`Ctrl-C` when
running a query. Using a connection in green mode allows Python to
receive and handle the interrupt, although it may leave the connection
broken, if the async callback doesn't handle the `!KeyboardInterrupt`
correctly.
Starting from psycopg 2.6.2, the `~psycopg2.extras.wait_select` callback
can handle a :kbd:`Ctrl-C` correctly. For previous versions, you can use
`this implementation`__.
.. __: http://initd.org/psycopg/articles/2014/07/20/cancelling-postgresql-statements-python/
.. code-block:: pycon
>>> psycopg2.extensions.set_wait_callback(psycopg2.extensions.wait_select)
>>> cnn = psycopg2.connect('')
>>> cur = cnn.cursor()
>>> cur.execute("select pg_sleep(10)")
^C
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
QueryCanceledError: canceling statement due to user request
>>> cnn.rollback()
>>> # You can use the connection and cursor again from here
.. _faq-compile:
Problems compiling and deploying psycopg2

View File

@ -18,7 +18,7 @@ The current `!psycopg2` implementation supports:
NOTE: keep consistent with setup.py and the /features/ page.
- Python 2 versions from 2.5 to 2.7
- Python 3 versions from 3.1 to 3.4
- Python 3 versions from 3.1 to 3.5
- PostgreSQL versions from 7.4 to 9.4
.. _PostgreSQL: http://www.postgresql.org/
@ -51,6 +51,16 @@ extension packages, *above all if you are a Windows or a Mac OS user*, please
use a pre-compiled package and go straight to the :ref:`module usage <usage>`
avoid bothering with the gory details.
.. note::
Regardless of the way `!psycopg2` is installed, at runtime it will need to
use the libpq_ library. `!psycopg2` relies on the host OS to find the
library file (usually ``libpq.so`` or ``libpq.dll``): if the library is
installed in a standard location there is usually no problem; if the
library is in a non-standard location you will have to tell somehow
psycopg how to find it, which is OS-dependent (for instance setting a
suitable :envvar:`LD_LIBRARY_PATH` on Linux).
.. _install-from-package:
@ -95,7 +105,17 @@ Install from a package
pair: Install; Windows
**Microsoft Windows**
Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
There are two options to install a precompiled `psycopg2` package under windows:
**Option 1:** Using `pip`__ (Included in python 2.7.9+ and python 3.4+)
and a binary wheel package. Launch windows' command prompt (`cmd.exe`)
and execute the following command::
pip install psycopg2
.. __: https://pip.pypa.io/en/stable/installing/
**Option 2:** Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
installation executable. Download. Double click. Done.
.. __: http://www.stickpeople.com/projects/python/win-psycopg/

166
doc/src/tools/pypi_docs_upload.py Executable file
View File

@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
"""
Standalone script to upload a project docs on PyPI
Hacked together from the following distutils extension, avaliable from
https://bitbucket.org/jezdez/sphinx-pypi-upload/overview (ver. 0.2.1)
sphinx_pypi_upload
~~~~~~~~~~~~~~~~~~
setuptools command for uploading Sphinx documentation to PyPI
:author: Jannis Leidel
:contact: jannis@leidel.info
:copyright: Copyright 2009, Jannis Leidel.
:license: BSD, see LICENSE for details.
"""
import os
import sys
import socket
import zipfile
import httplib
import base64
import urlparse
import tempfile
import cStringIO as StringIO
from ConfigParser import ConfigParser
from distutils import log
from distutils.command.upload import upload
from distutils.errors import DistutilsOptionError
class UploadDoc(object):
"""Distutils command to upload Sphinx documentation."""
def __init__(self, name, upload_dir, repository=None):
self.name = name
self.upload_dir = upload_dir
p = ConfigParser()
p.read(os.path.expanduser('~/.pypirc'))
self.username = p.get('pypi', 'username')
self.password = p.get('pypi', 'password')
self.show_response = False
self.repository = repository or upload.DEFAULT_REPOSITORY
def create_zipfile(self):
# name = self.distribution.metadata.get_name()
name = self.name
tmp_dir = tempfile.mkdtemp()
tmp_file = os.path.join(tmp_dir, "%s.zip" % name)
zip_file = zipfile.ZipFile(tmp_file, "w")
for root, dirs, files in os.walk(self.upload_dir):
if not files:
raise DistutilsOptionError, \
"no files found in upload directory '%s'" % self.upload_dir
for name in files:
full = os.path.join(root, name)
relative = root[len(self.upload_dir):].lstrip(os.path.sep)
dest = os.path.join(relative, name)
zip_file.write(full, dest)
zip_file.close()
return tmp_file
def upload_file(self, filename):
content = open(filename,'rb').read()
# meta = self.distribution.metadata
data = {
':action': 'doc_upload',
'name': self.name, # meta.get_name(),
'content': (os.path.basename(filename),content),
}
# set up the authentication
auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
# Build up the MIME payload for the POST data
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = '\n--' + boundary
end_boundary = sep_boundary + '--'
body = StringIO.StringIO()
for key, value in data.items():
# handle multiple entries for the same name
if type(value) != type([]):
value = [value]
for value in value:
if type(value) is tuple:
fn = ';filename="%s"' % value[0]
value = value[1]
else:
fn = ""
value = str(value)
body.write(sep_boundary)
body.write('\nContent-Disposition: form-data; name="%s"'%key)
body.write(fn)
body.write("\n\n")
body.write(value)
if value and value[-1] == '\r':
body.write('\n') # write an extra newline (lurve Macs)
body.write(end_boundary)
body.write("\n")
body = body.getvalue()
self.announce("Submitting documentation to %s" % (self.repository), log.INFO)
# build the Request
# We can't use urllib2 since we need to send the Basic
# auth right with the first request
schema, netloc, url, params, query, fragments = \
urlparse.urlparse(self.repository)
assert not params and not query and not fragments
if schema == 'http':
http = httplib.HTTPConnection(netloc)
elif schema == 'https':
http = httplib.HTTPSConnection(netloc)
else:
raise AssertionError, "unsupported schema "+schema
data = ''
loglevel = log.INFO
try:
http.connect()
http.putrequest("POST", url)
http.putheader('Content-type',
'multipart/form-data; boundary=%s'%boundary)
http.putheader('Content-length', str(len(body)))
http.putheader('Authorization', auth)
http.endheaders()
http.send(body)
except socket.error, e:
self.announce(str(e), log.ERROR)
return
response = http.getresponse()
if response.status == 200:
self.announce('Server response (%s): %s' % (response.status, response.reason),
log.INFO)
elif response.status == 301:
location = response.getheader('Location')
if location is None:
location = 'http://packages.python.org/%s/' % self.name # meta.get_name()
self.announce('Upload successful. Visit %s' % location,
log.INFO)
else:
self.announce('Upload failed (%s): %s' % (response.status, response.reason),
log.ERROR)
if self.show_response:
print '-'*75, response.read(), '-'*75
def run(self):
zip_file = self.create_zipfile()
self.upload_file(zip_file)
os.remove(zip_file)
def announce(self, msg, *args, **kwargs):
print msg
if __name__ == '__main__':
if len(sys.argv) != 3:
print >>sys.stderr, "usage: %s PROJECT UPLOAD_DIR" % sys.argv[0]
sys.exit(2)
project, upload_dir = sys.argv[1:]
up = UploadDoc(project, upload_dir=upload_dir)
up.run()

View File

@ -679,7 +679,7 @@ older versions).
By default even a simple :sql:`SELECT` will start a transaction: in
long-running programs, if no further action is taken, the session will
remain "idle in transaction", a condition non desiderable for several
remain "idle in transaction", an undesirable condition for several
reasons (locks are held by the session, tables bloat...). For long lived
scripts, either make sure to terminate a transaction as soon as possible or
use an autocommit connection.
@ -705,13 +705,28 @@ managers* and can be used with the ``with`` statement::
When a connection exits the ``with`` block, if no exception has been raised by
the block, the transaction is committed. In case of exception the transaction
is rolled back. In no case the connection is closed: a connection can be used
in more than a ``with`` statement and each ``with`` block is effectively
wrapped in a transaction.
is rolled back.
When a cursor exits the ``with`` block it is closed, releasing any resource
eventually associated with it. The state of the transaction is not affected.
Note that, unlike file objects or other resources, exiting the connection's
``with`` block *doesn't close the connection* but only the transaction
associated with it: a connection can be used in more than a ``with`` statement
and each ``with`` block is effectively wrapped in a separate transaction::
conn = psycopg2.connect(DSN)
with conn:
with conn.cursor() as curs:
curs.execute(SQL1)
with conn:
with conn.cursor() as curs:
curs.execute(SQL2)
conn.close()
.. index::
@ -849,11 +864,19 @@ Using COPY TO and COPY FROM
Psycopg `cursor` objects provide an interface to the efficient
PostgreSQL |COPY|__ command to move data from files to tables and back.
Currently no adaptation is provided between Python and PostgreSQL types on
|COPY|: the file can be any Python file-like object but its format must be in
the format accepted by `PostgreSQL COPY command`__ (data fromat, escaped
characters, etc).
.. __: COPY_
The methods exposed are:
`~cursor.copy_from()`
Reads data *from* a file-like object appending them to a database table
(:sql:`COPY table FROM file` syntax). The source file must have both
(:sql:`COPY table FROM file` syntax). The source file must provide both
`!read()` and `!readline()` method.
`~cursor.copy_to()`

View File

@ -38,11 +38,17 @@ def lookup(code, _cache={}):
return _cache[code]
# Generate the lookup map at first usage.
tmp = {}
for k, v in globals().iteritems():
if isinstance(v, str) and len(v) in (2, 5):
_cache[v] = k
tmp[v] = k
return lookup(code)
assert tmp
# Atomic update, to avoid race condition on import (bug #382)
_cache.update(tmp)
return _cache[code]
# autogenerated data: do not edit below this point.
@ -193,6 +199,8 @@ INVALID_ESCAPE_SEQUENCE = '22025'
STRING_DATA_LENGTH_MISMATCH = '22026'
TRIM_ERROR = '22027'
ARRAY_SUBSCRIPT_ERROR = '2202E'
INVALID_TABLESAMPLE_REPEAT = '2202G'
INVALID_TABLESAMPLE_ARGUMENT = '2202H'
FLOATING_POINT_EXCEPTION = '22P01'
INVALID_TEXT_REPRESENTATION = '22P02'
INVALID_BINARY_REPRESENTATION = '22P03'
@ -265,6 +273,7 @@ INVALID_SQLSTATE_RETURNED = '39001'
NULL_VALUE_NOT_ALLOWED = '39004'
TRIGGER_PROTOCOL_VIOLATED = '39P01'
SRF_PROTOCOL_VIOLATED = '39P02'
EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03'
# Class 3B - Savepoint Exception
SAVEPOINT_EXCEPTION = '3B000'
@ -402,6 +411,7 @@ PLPGSQL_ERROR = 'P0000'
RAISE_EXCEPTION = 'P0001'
NO_DATA_FOUND = 'P0002'
TOO_MANY_ROWS = 'P0003'
ASSERT_FAILURE = 'P0004'
# Class XX - Internal Error
INTERNAL_ERROR = 'XX000'

View File

@ -434,7 +434,7 @@ class MinTimeLoggingCursor(LoggingCursor):
def callproc(self, procname, vars=None):
self.timestamp = _time.time()
return LoggingCursor.execute(self, procname, vars)
return LoggingCursor.callproc(self, procname, vars)
# a dbtype and adapter for Python UUID type
@ -575,6 +575,7 @@ def wait_select(conn):
from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE
while 1:
try:
state = conn.poll()
if state == POLL_OK:
break
@ -584,6 +585,10 @@ def wait_select(conn):
select.select([], [conn.fileno()], [])
else:
raise conn.OperationalError("bad state from poll: %s" % state)
except KeyboardInterrupt:
conn.cancel()
# the loop will be broken by a server error
continue
def _solve_conn_curs(conn_or_curs):

View File

@ -204,8 +204,8 @@ class PersistentConnectionPool(AbstractConnectionPool):
# we we'll need the thread module, to determine thread ids, so we
# import it here and copy it in an instance variable
import thread
self.__thread = thread
import thread as _thread # work around for 2to3 bug - see ticket #348
self.__thread = _thread
def getconn(self):
"""Generate thread id and return a connection."""

View File

@ -28,7 +28,7 @@ old code while porting to psycopg 2. Import it as follows::
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import _psycopg as _2psycopg
import psycopg2._psycopg as _2psycopg
from psycopg2.extensions import cursor as _2cursor
from psycopg2.extensions import connection as _2connection

View File

@ -451,7 +451,7 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
tz);
exit:
Py_DECREF(tz);
Py_XDECREF(tz);
Py_XDECREF(m);
return res;
}

View File

@ -39,6 +39,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;
/* 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 */
int all_nulls = 1;
Py_ssize_t i, len;
len = PyList_GET_SIZE(self->wrapped);
@ -60,6 +68,7 @@ list_quote(listObject *self)
quoted = microprotocol_getquoted(wrapped,
(connectionObject*)self->connection);
if (quoted == NULL) goto error;
all_nulls = 0;
}
/* here we don't loose a refcnt: SET_ITEM does not change the
@ -74,7 +83,12 @@ list_quote(listObject *self)
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
if (joined == NULL) goto error;
/* 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));
}
error:
Py_XDECREF(tmp);

View File

@ -36,44 +36,56 @@ static const char *default_encoding = "latin1";
/* qstring_quote - do the quote process on plain and unicode strings */
const char *
_qstring_get_encoding(qstringObject *self)
{
/* if the wrapped object is an unicode object we can encode it to match
conn->encoding but if the encoding is not specified we don't know what
to do and we raise an exception */
if (self->conn) {
return self->conn->codec;
}
else {
return self->encoding ? self->encoding : default_encoding;
}
}
static PyObject *
qstring_quote(qstringObject *self)
{
PyObject *str = NULL;
char *s, *buffer = NULL;
Py_ssize_t len, qlen;
const char *encoding = default_encoding;
const char *encoding;
PyObject *rv = NULL;
/* if the wrapped object is an unicode object we can encode it to match
conn->encoding but if the encoding is not specified we don't know what
to do and we raise an exception */
if (self->conn) {
encoding = self->conn->codec;
}
encoding = _qstring_get_encoding(self);
Dprintf("qstring_quote: encoding to %s", encoding);
if (PyUnicode_Check(self->wrapped) && encoding) {
if (PyUnicode_Check(self->wrapped)) {
if (encoding) {
str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL);
Dprintf("qstring_quote: got encoded object at %p", str);
if (str == NULL) goto exit;
}
else {
PyErr_SetString(PyExc_TypeError,
"missing encoding to encode unicode object");
goto exit;
}
}
#if PY_MAJOR_VERSION < 3
/* if the wrapped object is a simple string, we don't know how to
/* if the wrapped object is a binary string, we don't know how to
(re)encode it, so we pass it as-is */
else if (PyString_Check(self->wrapped)) {
else if (Bytes_Check(self->wrapped)) {
str = self->wrapped;
/* INCREF to make it ref-wise identical to unicode one */
Py_INCREF(str);
}
#endif
/* if the wrapped object is not a string, this is an error */
else {
PyErr_SetString(PyExc_TypeError,
"can't quote non-string object (or missing encoding)");
PyErr_SetString(PyExc_TypeError, "can't quote non-string object");
goto exit;
}
@ -150,15 +162,34 @@ qstring_conform(qstringObject *self, PyObject *args)
static PyObject *
qstring_get_encoding(qstringObject *self)
{
const char *encoding = default_encoding;
if (self->conn) {
encoding = self->conn->codec;
}
const char *encoding;
encoding = _qstring_get_encoding(self);
return Text_FromUTF8(encoding);
}
static int
qstring_set_encoding(qstringObject *self, PyObject *pyenc)
{
int rv = -1;
const char *tmp;
char *cenc;
/* get a C copy of the encoding (which may come from unicode) */
Py_INCREF(pyenc);
if (!(pyenc = psycopg_ensure_bytes(pyenc))) { goto exit; }
if (!(tmp = Bytes_AsString(pyenc))) { goto exit; }
if (0 > psycopg_strdup(&cenc, tmp, 0)) { goto exit; }
Dprintf("qstring_set_encoding: encoding set to %s", cenc);
PyMem_Free((void *)self->encoding);
self->encoding = cenc;
rv = 0;
exit:
Py_XDECREF(pyenc);
return rv;
}
/** the QuotedString object **/
/* object member list */
@ -183,7 +214,7 @@ static PyMethodDef qstringObject_methods[] = {
static PyGetSetDef qstringObject_getsets[] = {
{ "encoding",
(getter)qstring_get_encoding,
(setter)NULL,
(setter)qstring_set_encoding,
"current encoding of the adapter" },
{NULL}
};
@ -216,6 +247,7 @@ qstring_dealloc(PyObject* obj)
Py_CLEAR(self->wrapped);
Py_CLEAR(self->buffer);
Py_CLEAR(self->conn);
PyMem_Free((void *)self->encoding);
Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,

View File

@ -39,6 +39,9 @@ typedef struct {
PyObject *buffer;
connectionObject *conn;
const char *encoding;
} qstringObject;
#ifdef __cplusplus

View File

@ -129,28 +129,33 @@ static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake)
/* remove the inline keyword, since it doesn't work unless C++ file */
#define inline
/* Hmmm, MSVC doesn't have a isnan/isinf function, but has _isnan function */
/* Hmmm, MSVC <2015 doesn't have a isnan/isinf function, but has _isnan function */
#if defined (_MSC_VER)
#if !defined(isnan)
#define isnan(x) (_isnan(x))
/* The following line was hacked together from simliar code by Bjorn Reese
* in libxml2 code */
#define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \
: ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0))
#endif
#define strcasecmp(x, y) lstrcmpi(x, y)
#endif
#endif
/* what's this, we have no round function either? */
#if (defined(__FreeBSD__) && __FreeBSD_version < 503000) \
|| (defined(_WIN32) && !defined(__GNUC__)) \
|| (defined(sun) || defined(__sun__)) \
&& (defined(__SunOS_5_8) || defined(__SunOS_5_9))
/* what's this, we have no round function either? */
/* round has been added in the standard library with MSVC 2015 */
#if _MSC_VER < 1900
static double round(double num)
{
return (num >= 0) ? floor(num + 0.5) : ceil(num - 0.5);
}
#endif
#endif
/* resolve missing isinf() function for Solaris */
#if defined (__SVR4) && defined (__sun)

View File

@ -840,6 +840,10 @@ psyco_conn_get_exception(PyObject *self, void *closure)
return exception;
}
#define psyco_conn_poll_doc \
"poll() -> int -- Advance the connection or query process without blocking."
static PyObject *
psyco_conn_poll(connectionObject *self)
{
@ -980,7 +984,7 @@ static struct PyMethodDef connectionObject_methods[] = {
{"reset", (PyCFunction)psyco_conn_reset,
METH_NOARGS, psyco_conn_reset_doc},
{"poll", (PyCFunction)psyco_conn_poll,
METH_NOARGS, psyco_conn_lobject_doc},
METH_NOARGS, psyco_conn_poll_doc},
{"fileno", (PyCFunction)psyco_conn_fileno,
METH_NOARGS, psyco_conn_fileno_doc},
{"isexecuting", (PyCFunction)psyco_conn_isexecuting,
@ -1168,7 +1172,7 @@ connection_repr(connectionObject *self)
{
return PyString_FromFormat(
"<connection object at %p; dsn: '%s', closed: %ld>",
self, self->dsn, self->closed);
self, (self->dsn ? self->dsn : "<unintialized>"), self->closed);
}
static int

View File

@ -335,7 +335,7 @@ _psyco_curs_merge_query_args(cursorObject *self,
PyErr_Fetch(&err, &arg, &trace);
if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) {
Dprintf("psyco_curs_execute: TypeError exception catched");
Dprintf("psyco_curs_execute: TypeError exception caught");
PyErr_NormalizeException(&err, &arg, &trace);
if (PyObject_HasAttrString(arg, "args")) {
@ -1546,7 +1546,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs)
if (sql == NULL) { goto exit; }
/* This validation of file is rather weak, in that it doesn't enforce the
assocation between "COPY FROM" -> "read" and "COPY TO" -> "write".
association between "COPY FROM" -> "read" and "COPY TO" -> "write".
However, the error handling in _pq_copy_[in|out] must be able to handle
the case where the attempt to call file.read|write fails, so no harm
done. */

View File

@ -60,8 +60,8 @@ RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename);
RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len);
RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf,
size_t len);
RAISES_NEG HIDDEN long lobject_seek(lobjectObject *self, long pos, int whence);
RAISES_NEG HIDDEN long lobject_tell(lobjectObject *self);
RAISES_NEG HIDDEN Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence);
RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self);
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);

View File

@ -376,12 +376,12 @@ lobject_read(lobjectObject *self, char *buf, size_t len)
/* lobject_seek - move the current position in the lo */
RAISES_NEG long
lobject_seek(lobjectObject *self, long pos, int whence)
RAISES_NEG Py_ssize_t
lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence)
{
PGresult *pgres = NULL;
char *error = NULL;
long where;
Py_ssize_t where;
Dprintf("lobject_seek: fd = %d, pos = %ld, whence = %d",
self->fd, pos, whence);
@ -391,12 +391,12 @@ lobject_seek(lobjectObject *self, long pos, int whence)
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
} else {
where = lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
}
#else
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
#endif
Dprintf("lobject_seek: where = %ld", where);
if (where < 0)
@ -412,12 +412,12 @@ lobject_seek(lobjectObject *self, long pos, int whence)
/* lobject_tell - tell the current position in the lo */
RAISES_NEG long
RAISES_NEG Py_ssize_t
lobject_tell(lobjectObject *self)
{
PGresult *pgres = NULL;
char *error = NULL;
long where;
Py_ssize_t where;
Dprintf("lobject_tell: fd = %d", self->fd);
@ -426,12 +426,12 @@ lobject_tell(lobjectObject *self)
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
where = (long)lo_tell(self->conn->pgconn, self->fd);
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
} else {
where = lo_tell64(self->conn->pgconn, self->fd);
where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd);
}
#else
where = (long)lo_tell(self->conn->pgconn, self->fd);
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
#endif
Dprintf("lobject_tell: where = %ld", where);
if (where < 0)

View File

@ -105,7 +105,7 @@ psyco_lobj_write(lobjectObject *self, PyObject *args)
goto exit;
}
rv = PyInt_FromLong((long)res);
rv = PyInt_FromSsize_t((Py_ssize_t)res);
exit:
Py_XDECREF(data);
@ -121,7 +121,7 @@ static PyObject *
psyco_lobj_read(lobjectObject *self, PyObject *args)
{
PyObject *res;
long where, end;
Py_ssize_t where, end;
Py_ssize_t size = -1;
char *buffer;
@ -165,10 +165,10 @@ psyco_lobj_read(lobjectObject *self, PyObject *args)
static PyObject *
psyco_lobj_seek(lobjectObject *self, PyObject *args)
{
long offset, pos=0;
Py_ssize_t offset, pos=0;
int whence=0;
if (!PyArg_ParseTuple(args, "l|i", &offset, &whence))
if (!PyArg_ParseTuple(args, "n|i", &offset, &whence))
return NULL;
EXC_IF_LOBJ_CLOSED(self);
@ -187,8 +187,8 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
#else
if (offset < INT_MIN || offset > INT_MAX) {
PyErr_Format(InterfaceError,
"offset out of range (%ld): this psycopg version was not built "
"with lobject 64 API support",
"offset out of range (" FORMAT_CODE_PY_SSIZE_T "): "
"this psycopg version was not built with lobject 64 API support",
offset);
return NULL;
}
@ -197,7 +197,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
if ((pos = lobject_seek(self, offset, whence)) < 0)
return NULL;
return PyLong_FromLong(pos);
return PyInt_FromSsize_t(pos);
}
/* tell method - tell current position in the lobject */
@ -208,7 +208,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
static PyObject *
psyco_lobj_tell(lobjectObject *self, PyObject *args)
{
long pos;
Py_ssize_t pos;
EXC_IF_LOBJ_CLOSED(self);
EXC_IF_LOBJ_LEVEL0(self);
@ -217,7 +217,7 @@ psyco_lobj_tell(lobjectObject *self, PyObject *args)
if ((pos = lobject_tell(self)) < 0)
return NULL;
return PyLong_FromLong(pos);
return PyInt_FromSsize_t(pos);
}
/* unlink method - unlink (destroy) the lobject */
@ -274,9 +274,9 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
static PyObject *
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
{
long len = 0;
Py_ssize_t len = 0;
if (!PyArg_ParseTuple(args, "|l", &len))
if (!PyArg_ParseTuple(args, "|n", &len))
return NULL;
EXC_IF_LOBJ_CLOSED(self);
@ -286,16 +286,16 @@ psyco_lobj_truncate(lobjectObject *self, PyObject *args)
#ifdef HAVE_LO64
if (len > INT_MAX && self->conn->server_version < 90300) {
PyErr_Format(NotSupportedError,
"len out of range (%ld): server version %d "
"does not support the lobject 64 API",
"len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
"server version %d does not support the lobject 64 API",
len, self->conn->server_version);
return NULL;
}
#else
if (len > INT_MAX) {
PyErr_Format(InterfaceError,
"len out of range (%ld): this psycopg version was not built "
"with lobject 64 API support",
"len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
"this psycopg version was not built with lobject 64 API support",
len);
return NULL;
}

View File

@ -161,14 +161,16 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
if (conn == NULL) {
PyErr_SetString(DatabaseError,
"psycopg went psycotic and raised a null error");
"psycopg went psychotic and raised a null error");
return;
}
/* if the connection has somehow beed broken, we mark the connection
/* if the connection has somehow been broken, we mark the connection
object as closed but requiring cleanup */
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD)
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) {
conn->closed = 2;
exc = OperationalError;
}
if (pgres == NULL && curs != NULL)
pgres = &curs->pgres;
@ -190,8 +192,10 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
raise and a meaningful message is better than an empty one.
Note: it can happen without it being our error: see ticket #82 */
if (err == NULL || err[0] == '\0') {
PyErr_SetString(DatabaseError,
"error with no message from the libpq");
PyErr_Format(DatabaseError,
"error with status %s and no message from the libpq",
PQresStatus(pgres == NULL ?
PQstatus(conn->pgconn) : PQresultStatus(*pgres)));
return;
}
@ -200,9 +204,9 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
if (code != NULL) {
exc = exception_from_sqlstate(code);
}
else {
/* Fallback if there is no exception code (reported happening e.g.
* when the connection is closed). */
else if (exc == NULL) {
/* Fallback if there is no exception code (unless we already
determined that the connection was closed). */
exc = DatabaseError;
}
@ -905,7 +909,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn));
return -1;
}
Dprintf("curs_execute: pg connection at %p OK", curs->conn->pgconn);
Dprintf("pq_execute: pg connection at %p OK", curs->conn->pgconn);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(curs->conn->lock));
@ -930,8 +934,11 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
Py_UNBLOCK_THREADS;
}
/* dont let pgres = NULL go to pq_fetch() */
/* don't let pgres = NULL go to pq_fetch() */
if (curs->pgres == NULL) {
if (CONNECTION_BAD == PQstatus(curs->conn->pgconn)) {
curs->conn->closed = 2;
}
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
if (!PyErr_Occurred()) {
@ -959,6 +966,9 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
CLEARPGRES(curs->pgres);
if (PQsendQuery(curs->conn->pgconn, query) == 0) {
if (CONNECTION_BAD == PQstatus(curs->conn->pgconn)) {
curs->conn->closed = 2;
}
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
PyErr_SetString(OperationalError,
@ -980,6 +990,10 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
}
else {
/* there was an error */
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
PyErr_SetString(OperationalError,
PQerrorMessage(curs->conn->pgconn));
return -1;
}
}
@ -1387,7 +1401,11 @@ _pq_copy_in_v3(cursorObject *curs)
Py_DECREF(str);
}
}
PyErr_Restore(t, ex, tb);
/* Clear the Py exception: it will be re-raised from the libpq */
Py_XDECREF(t);
Py_XDECREF(ex);
Py_XDECREF(tb);
PyErr_Clear();
}
res = PQputCopyEnd(curs->conn->pgconn, buf);
}
@ -1591,11 +1609,26 @@ pq_fetch(cursorObject *curs, int no_result)
ex = -1;
break;
default:
Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn);
case PGRES_BAD_RESPONSE:
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR:
Dprintf("pq_fetch: uh-oh, something FAILED: status = %d pgconn = %p",
pgstatus, curs->conn);
pq_raise(curs->conn, curs, NULL);
ex = -1;
break;
default:
/* PGRES_COPY_BOTH, PGRES_SINGLE_TUPLE, future statuses */
Dprintf("pq_fetch: got unsupported result: status = %d pgconn = %p",
pgstatus, curs->conn);
PyErr_Format(NotSupportedError,
"got server response with unsupported status %s",
PQresStatus(curs->pgres == NULL ?
PQstatus(curs->conn->pgconn) : PQresultStatus(curs->pgres)));
CLEARPGRES(curs->pgres);
ex = -1;
break;
}
/* error checking, close the connection if necessary (some critical errors

View File

@ -176,6 +176,29 @@ psyco_register_type(PyObject *self, PyObject *args)
}
/* Make sure libcrypto thread callbacks are set up. */
static void
psyco_libcrypto_threads_init(void)
{
PyObject *m;
/* importing the ssl module sets up Python's libcrypto callbacks */
if ((m = PyImport_ImportModule("ssl"))) {
/* disable libcrypto setup in libpq, so it won't stomp on the callbacks
that have already been set up */
#if PG_VERSION_HEX >= 0x080400
PQinitOpenSSL(1, 0);
#endif
Py_DECREF(m);
}
else {
/* might mean that Python has been compiled without OpenSSL support,
fall back to relying on libpq's libcrypto locking */
PyErr_Clear();
}
}
/* Initialize the default adapters map
*
* Return 0 on success, else -1 and set an exception.
@ -814,6 +837,9 @@ INIT_MODULE(_psycopg)(void)
Py_TYPE(&lobjectType) = &PyType_Type;
if (PyType_Ready(&lobjectType) == -1) goto exit;
/* initialize libcrypto threading callbacks */
psyco_libcrypto_threads_init();
/* import mx.DateTime module, if necessary */
#ifdef HAVE_MXDATETIME
Py_TYPE(&mxdatetimeType) = &PyType_Type;

View File

@ -33,7 +33,7 @@ def main():
file_start = read_base_file(filename)
# If you add a version to the list fix the docs (errorcodes.rst, err.rst)
classes, errors = fetch_errors(
['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4'])
['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5'])
f = open(filename, "w")
for line in file_start:

View File

@ -41,6 +41,7 @@ Programming Language :: Python :: 3.1
Programming Language :: Python :: 3.2
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: C
Programming Language :: SQL
Topic :: Database
@ -57,7 +58,10 @@ import os
import sys
import re
import subprocess
from distutils.core import setup, Extension
try:
from setuptools import setup, Extension
except ImportError:
from distutils.core import setup, Extension
from distutils.command.build_ext import build_ext
from distutils.sysconfig import get_python_inc
from distutils.ccompiler import get_default_compiler
@ -86,7 +90,7 @@ except ImportError:
# Take a look at http://www.python.org/dev/peps/pep-0386/
# for a consistent versioning pattern.
PSYCOPG_VERSION = '2.6'
PSYCOPG_VERSION = '2.6.2'
version_flags = ['dt', 'dec']
@ -301,6 +305,10 @@ class psycopg_build_ext(build_ext):
except AttributeError:
ext_path = os.path.join(self.build_lib,
'psycopg2', '_psycopg.pyd')
# Make sure spawn() will work if compile() was never
# called. https://github.com/psycopg/psycopg2/issues/380
if not self.compiler.initialized:
self.compiler.initialize()
self.compiler.spawn(
['mt.exe', '-nologo', '-manifest',
os.path.join('psycopg', manifest),
@ -343,6 +351,7 @@ class psycopg_build_ext(build_ext):
self.libraries.append("advapi32")
if self.compiler_is_msvc():
# MSVC requires an explicit "libpq"
if "pq" in self.libraries:
self.libraries.remove("pq")
self.libraries.append("secur32")
self.libraries.append("libpq")

View File

@ -34,6 +34,7 @@ import test_connection
import test_copy
import test_cursor
import test_dates
import test_errcodes
import test_extras_dictcursor
import test_green
import test_lobject
@ -71,6 +72,7 @@ def test_suite():
suite.addTest(test_copy.test_suite())
suite.addTest(test_cursor.test_suite())
suite.addTest(test_dates.test_suite())
suite.addTest(test_errcodes.test_suite())
suite.addTest(test_extras_dictcursor.test_suite())
suite.addTest(test_green.test_suite())
suite.addTest(test_lobject.test_suite())

View File

@ -62,7 +62,7 @@ import sys
# - Reversed the polarity of buggy test in test_description
# - Test exception hierarchy correctly
# - self.populate is now self._populate(), so if a driver stub
# overrides self.ddl1 this change propogates
# overrides self.ddl1 this change propagates
# - VARCHAR columns now have a width, which will hopefully make the
# DDL even more portible (this will be reversed if it causes more problems)
# - cursor.rowcount being checked after various execute and fetchXXX methods
@ -804,7 +804,7 @@ class DatabaseAPI20Test(unittest.TestCase):
con.close()
def test_setoutputsize(self):
# Real test for setoutputsize is driver dependant
# Real test for setoutputsize is driver dependent
raise NotImplementedError('Driver needed to override this test')
def test_None(self):

View File

@ -26,6 +26,7 @@ import os
import time
import threading
from operator import attrgetter
from StringIO import StringIO
import psycopg2
import psycopg2.errorcodes
@ -70,16 +71,8 @@ class ConnectionTests(ConnectingTestCase):
# ticket #148
conn = self.conn
cur = conn.cursor()
try:
cur.execute("select pg_terminate_backend(pg_backend_pid())")
except psycopg2.OperationalError, e:
if e.pgcode != psycopg2.errorcodes.ADMIN_SHUTDOWN:
raise
except psycopg2.DatabaseError, e:
# curiously when disconnected in green mode we get a DatabaseError
# without pgcode.
if e.pgcode is not None:
raise
self.assertRaises(psycopg2.OperationalError,
cur.execute, "select pg_terminate_backend(pg_backend_pid())")
self.assertEqual(conn.closed, 2)
conn.close()
@ -1067,6 +1060,17 @@ class AutocommitTests(ConnectingTestCase):
self.assertEqual(cur.fetchone()[0], 'on')
class ReplicationTest(ConnectingTestCase):
@skip_before_postgres(9, 0)
def test_replication_not_supported(self):
conn = self.repl_connect()
if conn is None: return
cur = conn.cursor()
f = StringIO()
self.assertRaises(psycopg2.NotSupportedError,
cur.copy_expert, "START_REPLICATION 0/0", f)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -22,13 +22,18 @@
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
from __future__ import with_statement
import time
import pickle
import psycopg2
import psycopg2.extensions
import psycopg2.extras
from psycopg2.extensions import b
from testutils import unittest, ConnectingTestCase, skip_before_postgres
from testutils import skip_if_no_namedtuple, skip_if_no_getrefcount
from testutils import skip_if_no_superuser, skip_if_windows
class CursorTests(ConnectingTestCase):
@ -48,8 +53,10 @@ class CursorTests(ConnectingTestCase):
conn = self.conn
cur = conn.cursor()
cur.execute("create temp table test_exc (data int);")
def buggygen():
yield 1//0
yield 1 // 0
self.assertRaises(ZeroDivisionError,
cur.executemany, "insert into test_exc values (%s)", buggygen())
cur.close()
@ -194,16 +201,16 @@ class CursorTests(ConnectingTestCase):
self._create_withhold_table()
curs = self.conn.cursor("W")
self.assertEqual(curs.withhold, False);
self.assertEqual(curs.withhold, False)
curs.withhold = True
self.assertEqual(curs.withhold, True);
self.assertEqual(curs.withhold, True)
curs.execute("select data from withhold order by data")
self.conn.commit()
self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)])
curs.close()
curs = self.conn.cursor("W", withhold=True)
self.assertEqual(curs.withhold, True);
self.assertEqual(curs.withhold, True)
curs.execute("select data from withhold order by data")
self.conn.commit()
self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)])
@ -265,18 +272,18 @@ class CursorTests(ConnectingTestCase):
curs = self.conn.cursor()
curs.execute("create table scrollable (data int)")
curs.executemany("insert into scrollable values (%s)",
[ (i,) for i in range(100) ])
[(i,) for i in range(100)])
curs.close()
for t in range(2):
if not t:
curs = self.conn.cursor("S")
self.assertEqual(curs.scrollable, None);
self.assertEqual(curs.scrollable, None)
curs.scrollable = True
else:
curs = self.conn.cursor("S", scrollable=True)
self.assertEqual(curs.scrollable, True);
self.assertEqual(curs.scrollable, True)
curs.itersize = 10
# complex enough to make postgres cursors declare without
@ -304,7 +311,7 @@ class CursorTests(ConnectingTestCase):
curs = self.conn.cursor()
curs.execute("create table scrollable (data int)")
curs.executemany("insert into scrollable values (%s)",
[ (i,) for i in range(100) ])
[(i,) for i in range(100)])
curs.close()
curs = self.conn.cursor("S") # default scrollability
@ -341,7 +348,7 @@ class CursorTests(ConnectingTestCase):
def test_iter_named_cursor_default_itersize(self):
curs = self.conn.cursor('tmp')
curs.execute('select generate_series(1,50)')
rv = [ (r[0], curs.rownumber) for r in curs ]
rv = [(r[0], curs.rownumber) for r in curs]
# everything swallowed in one gulp
self.assertEqual(rv, [(i,i) for i in range(1,51)])
@ -350,7 +357,7 @@ class CursorTests(ConnectingTestCase):
curs = self.conn.cursor('tmp')
curs.itersize = 30
curs.execute('select generate_series(1,50)')
rv = [ (r[0], curs.rownumber) for r in curs ]
rv = [(r[0], curs.rownumber) for r in curs]
# everything swallowed in two gulps
self.assertEqual(rv, [(i,((i - 1) % 30) + 1) for i in range(1,51)])
@ -490,6 +497,56 @@ class CursorTests(ConnectingTestCase):
cur = self.conn.cursor()
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
@skip_if_no_superuser
@skip_if_windows
@skip_before_postgres(8, 4)
def test_external_close_sync(self):
# If a "victim" connection is closed by a "control" connection
# behind psycopg2's back, psycopg2 always handles it correctly:
# raise OperationalError, set conn.closed to 2. This reproduces
# issue #443, a race between control_conn closing victim_conn and
# psycopg2 noticing.
control_conn = self.conn
connect_func = self.connect
wait_func = lambda conn: None
self._test_external_close(control_conn, connect_func, wait_func)
@skip_if_no_superuser
@skip_if_windows
@skip_before_postgres(8, 4)
def test_external_close_async(self):
# 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)
wait_func = psycopg2.extras.wait_select
self._test_external_close(control_conn, connect_func, wait_func)
def _test_external_close(self, control_conn, connect_func, wait_func):
# The short sleep before using victim_conn the second time makes it
# much more likely to lose the race and see the bug. Repeating the
# test several times makes it even more likely.
for i in range(10):
victim_conn = connect_func()
wait_func(victim_conn)
with victim_conn.cursor() as cur:
cur.execute('select pg_backend_pid()')
wait_func(victim_conn)
pid1 = cur.fetchall()[0][0]
with control_conn.cursor() as cur:
cur.execute('select pg_terminate_backend(%s)', (pid1,))
def f():
with victim_conn.cursor() as cur:
cur.execute('select 1')
wait_func(victim_conn)
time.sleep(0.001)
self.assertRaises(psycopg2.OperationalError, f)
self.assertEqual(victim_conn.closed, 2)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

65
tests/test_errcodes.py Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env python
# test_errcodes.py - unit test for psycopg2.errcodes module
#
# Copyright (C) 2015 Daniele Varrazzo <daniele.varrazzo@gmail.com>
#
# 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.
from testutils import unittest, ConnectingTestCase
try:
reload
except NameError:
from imp import reload
from threading import Thread
from psycopg2 import errorcodes
class ErrocodeTests(ConnectingTestCase):
def test_lookup_threadsafe(self):
# Increase if it does not fail with KeyError
MAX_CYCLES = 2000
errs = []
def f(pg_code='40001'):
try:
errorcodes.lookup(pg_code)
except Exception, e:
errs.append(e)
for __ in xrange(MAX_CYCLES):
reload(errorcodes)
(t1, t2) = (Thread(target=f), Thread(target=f))
(t1.start(), t2.start())
(t1.join(), t2.join())
if errs:
self.fail(
"raised %s errors in %s cycles (first is %s %s)" % (
len(errs), MAX_CYCLES,
errs[0].__class__.__name__, errs[0]))
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == "__main__":
unittest.main()

View File

@ -23,12 +23,14 @@
# License for more details.
import sys
import testutils
from testutils import unittest, ConnectingTestCase
import psycopg2
import psycopg2.extensions
from psycopg2.extensions import b
class QuotingTestCase(ConnectingTestCase):
r"""Checks the correct quoting of strings and binary objects.
@ -51,7 +53,7 @@ class QuotingTestCase(ConnectingTestCase):
data = """some data with \t chars
to escape into, 'quotes' and \\ a backslash too.
"""
data += "".join(map(chr, range(1,127)))
data += "".join(map(chr, range(1, 127)))
curs = self.conn.cursor()
curs.execute("SELECT %s;", (data,))
@ -95,8 +97,8 @@ class QuotingTestCase(ConnectingTestCase):
data = u"""some data with \t chars
to escape into, 'quotes', \u20ac euro sign and \\ a backslash too.
"""
data += u"".join(map(unichr, [ u for u in range(1,65536)
if not 0xD800 <= u <= 0xDFFF ])) # surrogate area
data += u"".join(map(unichr, [u for u in range(1, 65536)
if not 0xD800 <= u <= 0xDFFF])) # surrogate area
self.conn.set_client_encoding('UNICODE')
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn)
@ -156,7 +158,7 @@ class QuotingTestCase(ConnectingTestCase):
class TestQuotedString(ConnectingTestCase):
def test_encoding(self):
def test_encoding_from_conn(self):
q = psycopg2.extensions.QuotedString('hi')
self.assertEqual(q.encoding, 'latin1')
@ -164,10 +166,61 @@ class TestQuotedString(ConnectingTestCase):
q.prepare(self.conn)
self.assertEqual(q.encoding, 'utf_8')
def test_encoding_default(self):
from psycopg2.extensions import adapt
a = adapt("hello")
self.assertEqual(a.encoding, 'latin1')
self.assertEqual(a.getquoted(), b("'hello'"))
# NOTE: we can't really test an encoding different from utf8, because
# when encoding without connection the libpq will use parameters from
# a previous one, so what would happens depends jn the tests run order.
# egrave = u'\xe8'
# self.assertEqual(adapt(egrave).getquoted(), "'\xe8'")
def test_encoding_error(self):
from psycopg2.extensions import adapt
snowman = u"\u2603"
a = adapt(snowman)
self.assertRaises(UnicodeEncodeError, a.getquoted)
def test_set_encoding(self):
# Note: this works-ish mostly in case when the standard db connection
# we test with is utf8, otherwise the encoding chosen by PQescapeString
# may give bad results.
from psycopg2.extensions import adapt
snowman = u"\u2603"
a = adapt(snowman)
a.encoding = 'utf8'
self.assertEqual(a.encoding, 'utf8')
q = a.getquoted()
self.assert_(q in (b("'\xe2\x98\x83'"), b("E'\xe2\x98\x83'")), q)
def test_connection_wins_anyway(self):
from psycopg2.extensions import adapt
snowman = u"\u2603"
a = adapt(snowman)
a.encoding = 'latin9'
self.conn.set_client_encoding('utf8')
a.prepare(self.conn)
self.assertEqual(a.encoding, 'utf_8')
q = a.getquoted()
self.assert_(q in (b("'\xe2\x98\x83'"), b("E'\xe2\x98\x83'")), q)
@testutils.skip_before_python(3)
def test_adapt_bytes(self):
snowman = u"\u2603"
self.conn.set_client_encoding('utf8')
a = psycopg2.extensions.QuotedString(snowman.encode('utf8'))
a.prepare(self.conn)
q = a.getquoted()
self.assert_(q in (b("'\xe2\x98\x83'"), b("E'\xe2\x98\x83'")), q)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == "__main__":
unittest.main()

View File

@ -192,6 +192,41 @@ class TypesBasicTests(ConnectingTestCase):
self.assertRaises(psycopg2.DataError,
psycopg2.extensions.STRINGARRAY, b(s), curs)
@testutils.skip_before_postgres(8, 2)
def testArrayOfNulls(self):
curs = self.conn.cursor()
curs.execute("""
create table na (
texta text[],
inta int[],
boola boolean[],
textaa text[][],
intaa int[][],
boolaa boolean[][]
)""")
curs.execute("insert into na (texta) values (%s)", ([None],))
curs.execute("insert into na (texta) values (%s)", (['a', None],))
curs.execute("insert into na (texta) values (%s)", ([None, None],))
curs.execute("insert into na (inta) values (%s)", ([None],))
curs.execute("insert into na (inta) values (%s)", ([42, None],))
curs.execute("insert into na (inta) values (%s)", ([None, None],))
curs.execute("insert into na (boola) values (%s)", ([None],))
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)", ([['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 (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 (boolaa) values (%s)", ([[True, None]],))
# curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
@testutils.skip_from_python(3)
def testTypeRoundtripBuffer(self):
o1 = buffer("".join(map(chr, range(256))))
@ -345,7 +380,6 @@ class AdaptSubclassTest(unittest.TestCase):
finally:
del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
@testutils.skip_before_python(3)
def test_adapt_subtype_3(self):
from psycopg2.extensions import adapt, register_adapter, AsIs
@ -445,6 +479,7 @@ class ByteaParserTest(unittest.TestCase):
self.assertEqual(rv, tgt)
def skip_if_cant_cast(f):
@wraps(f)
def skip_if_cant_cast_(self, *args, **kwargs):
@ -464,4 +499,3 @@ def test_suite():
if __name__ == "__main__":
unittest.main()

View File

@ -7,6 +7,8 @@ dbhost = os.environ.get('PSYCOPG2_TESTDB_HOST', None)
dbport = os.environ.get('PSYCOPG2_TESTDB_PORT', None)
dbuser = os.environ.get('PSYCOPG2_TESTDB_USER', None)
dbpass = os.environ.get('PSYCOPG2_TESTDB_PASSWORD', None)
repl_dsn = os.environ.get('PSYCOPG2_TEST_REPL_DSN',
"dbname=psycopg2_test replication=1")
# Check if we want to test psycopg's green path.
green = os.environ.get('PSYCOPG2_TEST_GREEN', None)
@ -32,5 +34,3 @@ if dbuser is not None:
dsn += ' user=%s' % dbuser
if dbpass is not None:
dsn += ' password=%s' % dbpass

View File

@ -28,7 +28,7 @@ import os
import platform
import sys
from functools import wraps
from testconfig import dsn
from testconfig import dsn, repl_dsn
try:
import unittest2
@ -65,7 +65,8 @@ else:
unittest.TestCase.skipTest = skipTest
# Silence warnings caused by the stubborness of the Python unittest maintainers
# Silence warnings caused by the stubbornness of the Python unittest
# maintainers
# http://bugs.python.org/issue9424
if not hasattr(unittest.TestCase, 'assert_') \
or unittest.TestCase.assert_ is not unittest.TestCase.assertTrue:
@ -99,14 +100,38 @@ class ConnectingTestCase(unittest.TestCase):
self._conns
except AttributeError, e:
raise AttributeError(
"%s (did you remember calling ConnectingTestCase.setUp()?)"
"%s (did you forget to call ConnectingTestCase.setUp()?)"
% e)
if 'dsn' in kwargs:
conninfo = kwargs.pop('dsn')
else:
conninfo = dsn
import psycopg2
conn = psycopg2.connect(dsn, **kwargs)
conn = psycopg2.connect(conninfo, **kwargs)
self._conns.append(conn)
return conn
def repl_connect(self, **kwargs):
"""Return a connection set up for replication
The connection is on "PSYCOPG2_TEST_REPL_DSN" unless overridden by
a *dsn* kwarg.
Should raise a skip test if not available, but guard for None on
old Python versions.
"""
if 'dsn' not in kwargs:
kwargs['dsn'] = repl_dsn
import psycopg2
try:
conn = self.connect(**kwargs)
except psycopg2.OperationalError, e:
return self.skipTest("replication db not configured: %s" % e)
conn.autocommit = True
return conn
def _get_conn(self):
if not hasattr(self, '_the_conn'):
self._the_conn = self.connect()
@ -350,4 +375,3 @@ class py3_raises_typeerror(object):
if sys.version_info[0] >= 3:
assert type is TypeError
return True