diff --git a/INSTALL b/INSTALL index dac661e9..36070022 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ Compiling and installing psycopg ******************************** -** Important note: if you plan to use psyopg2 in a multithreaed application +** Important note: if you plan to use psycopg2 in a multithreaded application, make sure that your libpq has been compiled with the --with-thread-safety option. psycopg2 will work correctly even with a non-thread-safe libpq but libpq will leak memory. @@ -16,7 +16,7 @@ then: to build in the local directory; and: python setup.py install - + to install system-wide. @@ -96,7 +96,7 @@ Dev-C++ (http://www.bloodshed.net/devcpp.html) and Code::Blocks You need a PostgreSQL with include and libary files installed. At least v8.0 is required. -First you need to create a libpython2X.a as described in +First you need to create a libpython2X.a as described in http://starship.python.net/crew/kernr/mingw32/Notes.html. Then run: python setup.py build_ext --compiler=mingw32 install diff --git a/NEWS b/NEWS index ca91abef..71ef7336 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,26 @@ +What's new in psycopg 2.4.5 +--------------------------- + + - The close() methods on connections and cursors don't raise exceptions + if called on already closed objects. + - Fixed fetchmany() with no argument in cursor subclasses + (ticket #84). + - Use lo_creat() instead of lo_create() when possible for better + interaction with pgpool-II (ticket #88). + - Error and its subclasses are picklable, useful for multiprocessing + interaction (ticket #90). + - Better efficiency and formatting of timezone offset objects thanks + to Menno Smits (tickets #94, #95). + - Fixed 'rownumber' during iteration on cursor subclasses. + Regression introduced in 2.4.4 (ticket #100). + - Added support for 'inet' arrays. + - Fixed 'commit()' concurrency problem (ticket #103). + - Codebase cleaned up using the GCC Python plugin's static analysis + tool, which has revealed several unchecked return values, possible + NULL dereferences, reference counting problems. Many thanks to David + Malcolm for the useful tool and the assistance provided using it. + + What's new in psycopg 2.4.4 --------------------------- diff --git a/ZPsycopgDA/DA.py b/ZPsycopgDA/DA.py index 9a8a8c8e..da79b6c0 100644 --- a/ZPsycopgDA/DA.py +++ b/ZPsycopgDA/DA.py @@ -16,7 +16,7 @@ # their work without bothering about the module dependencies. -ALLOWED_PSYCOPG_VERSIONS = ('2.4-beta1', '2.4-beta2', '2.4', '2.4.1', '2.4.2', '2.4.3', '2.4.4') +ALLOWED_PSYCOPG_VERSIONS = ('2.4-beta1', '2.4-beta2', '2.4', '2.4.1', '2.4.2', '2.4.3', '2.4.4', '2.4.5') import sys import time diff --git a/doc/src/advanced.rst b/doc/src/advanced.rst index 87d8449b..a7b406a5 100644 --- a/doc/src/advanced.rst +++ b/doc/src/advanced.rst @@ -143,7 +143,7 @@ geometric type: .. |point| replace:: :sql:`point` -.. _point: http://www.postgresql.org/docs/9.0/static/datatype-geometric.html#DATATYPE-GEOMETRIC +.. _point: http://www.postgresql.org/docs/current/static/datatype-geometric.html#DATATYPE-GEOMETRIC The above function call results in the SQL command:: @@ -246,9 +246,9 @@ documentation), you should keep the connection in `~connection.autocommit` mode if you wish to receive or send notifications in a timely manner. .. |LISTEN| replace:: :sql:`LISTEN` -.. _LISTEN: http://www.postgresql.org/docs/9.0/static/sql-listen.html +.. _LISTEN: http://www.postgresql.org/docs/current/static/sql-listen.html .. |NOTIFY| replace:: :sql:`NOTIFY` -.. _NOTIFY: http://www.postgresql.org/docs/9.0/static/sql-notify.html +.. _NOTIFY: http://www.postgresql.org/docs/current/static/sql-notify.html Notifications are received after every query execution. If the user is interested in receiving notifications but not in performing any query, the @@ -356,7 +356,7 @@ completely non-blocking connection attempt: see the libpq documentation for |PQconnectStart|_. .. |PQconnectStart| replace:: `!PQconnectStart()` -.. _PQconnectStart: http://www.postgresql.org/docs/9.0/static/libpq-connect.html#LIBPQ-PQCONNECTSTART +.. _PQconnectStart: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTSTARTPARAMS The same loop should be also used to perform nonblocking queries: after sending a query via `~cursor.execute()` or `~cursor.callproc()`, call @@ -472,7 +472,7 @@ resources about the topic. .. _gevent: http://www.gevent.org/ .. _SQLAlchemy: http://www.sqlalchemy.org/ .. _psycogreen: http://bitbucket.org/dvarrazzo/psycogreen/ -.. __: http://www.postgresql.org/docs/9.0/static/libpq-async.html +.. __: http://www.postgresql.org/docs/current/static/libpq-async.html .. warning:: diff --git a/doc/src/conf.py b/doc/src/conf.py index dd3e83c7..7105907f 100644 --- a/doc/src/conf.py +++ b/doc/src/conf.py @@ -111,7 +111,7 @@ rst_epilog = """ .. _DBAPI: http://www.python.org/dev/peps/pep-0249/ .. _transaction isolation level: - http://www.postgresql.org/docs/9.1/static/transaction-iso.html + http://www.postgresql.org/docs/current/static/transaction-iso.html .. _mx.DateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/ diff --git a/doc/src/connection.rst b/doc/src/connection.rst index 577517e2..e229f41b 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -185,7 +185,7 @@ The ``connection`` class .. seealso:: the |PREPARE TRANSACTION|_ PostgreSQL command. .. |PREPARE TRANSACTION| replace:: :sql:`PREPARE TRANSACTION` - .. _PREPARE TRANSACTION: http://www.postgresql.org/docs/9.0/static/sql-prepare-transaction.html + .. _PREPARE TRANSACTION: http://www.postgresql.org/docs/current/static/sql-prepare-transaction.html .. index:: @@ -211,7 +211,7 @@ The ``connection`` class .. seealso:: the |COMMIT PREPARED|_ PostgreSQL command. .. |COMMIT PREPARED| replace:: :sql:`COMMIT PREPARED` - .. _COMMIT PREPARED: http://www.postgresql.org/docs/9.0/static/sql-commit-prepared.html + .. _COMMIT PREPARED: http://www.postgresql.org/docs/current/static/sql-commit-prepared.html .. index:: @@ -233,7 +233,7 @@ The ``connection`` class .. seealso:: the |ROLLBACK PREPARED|_ PostgreSQL command. .. |ROLLBACK PREPARED| replace:: :sql:`ROLLBACK PREPARED` - .. _ROLLBACK PREPARED: http://www.postgresql.org/docs/9.0/static/sql-rollback-prepared.html + .. _ROLLBACK PREPARED: http://www.postgresql.org/docs/current/static/sql-rollback-prepared.html .. index:: @@ -264,7 +264,7 @@ The ``connection`` class .. seealso:: the |pg_prepared_xacts|_ system view. .. |pg_prepared_xacts| replace:: `pg_prepared_xacts` - .. _pg_prepared_xacts: http://www.postgresql.org/docs/9.0/static/view-pg-prepared-xacts.html + .. _pg_prepared_xacts: http://www.postgresql.org/docs/current/static/view-pg-prepared-xacts.html @@ -296,7 +296,7 @@ The ``connection`` class |PQcancel|_. .. |PQcancel| replace:: `!PQcancel()` - .. _PQcancel: http://www.postgresql.org/docs/8.4/static/libpq-cancel.html#AEN34765 + .. _PQcancel: http://www.postgresql.org/docs/current/static/libpq-cancel.html#LIBPQ-PQCANCEL .. versionadded:: 2.3 @@ -312,10 +312,10 @@ The ``connection`` class available for recover. .. |RESET| replace:: :sql:`RESET` - .. _RESET: http://www.postgresql.org/docs/9.0/static/sql-reset.html + .. _RESET: http://www.postgresql.org/docs/current/static/sql-reset.html .. |SET SESSION AUTHORIZATION| replace:: :sql:`SET SESSION AUTHORIZATION` - .. __: http://www.postgresql.org/docs/9.0/static/sql-set-session-authorization.html + .. __: http://www.postgresql.org/docs/current/static/sql-set-session-authorization.html .. versionadded:: 2.0.12 @@ -336,7 +336,7 @@ The ``connection`` class the current session. See |SET TRANSACTION|_ for further details. .. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION` - .. _SET TRANSACTION: http://www.postgresql.org/docs/9.1/static/sql-set-transaction.html + .. _SET TRANSACTION: http://www.postgresql.org/docs/current/static/sql-set-transaction.html :param isolation_level: set the `isolation level`_ for the next transactions/statements. The value can be one of the @@ -357,7 +357,7 @@ The ``connection`` class parameter to the server default. .. _isolation level: - http://www.postgresql.org/docs/9.1/static/transaction-iso.html + 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. @@ -367,11 +367,11 @@ The ``connection`` class |default_transaction_read_only|__, |default_transaction_deferrable|__. .. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation` - .. __: http://www.postgresql.org/docs/9.1/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION + .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION .. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only` - .. __: http://www.postgresql.org/docs/9.1/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY + .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY .. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable` - .. __: http://www.postgresql.org/docs/9.1/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE + .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE .. note:: @@ -445,7 +445,7 @@ The ``connection`` class is the encoding defined by the database. It should be one of the `characters set supported by PostgreSQL`__ - .. __: http://www.postgresql.org/docs/9.0/static/multibyte.html + .. __: http://www.postgresql.org/docs/current/static/multibyte.html .. index:: @@ -471,7 +471,7 @@ The ``connection`` class configuration parameters`__ such as ``log_statement``, ``client_min_messages``, ``log_min_duration_statement`` etc. - .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-logging.html + .. __: http://www.postgresql.org/docs/current/static/runtime-config-logging.html .. attribute:: notifies @@ -500,7 +500,7 @@ The ``connection`` class .. seealso:: libpq docs for `PQbackendPID()`__ for details. - .. __: http://www.postgresql.org/docs/9.0/static/libpq-status.html#LIBPQ-PQBACKENDPID + .. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID .. versionadded:: 2.0.8 @@ -521,7 +521,7 @@ The ``connection`` class .. seealso:: libpq docs for `PQparameterStatus()`__ for details. - .. __: http://www.postgresql.org/docs/9.0/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS + .. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS .. versionadded:: 2.0.12 @@ -538,7 +538,7 @@ The ``connection`` class .. seealso:: libpq docs for `PQtransactionStatus()`__ for details. - .. __: http://www.postgresql.org/docs/9.0/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS + .. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS .. index:: @@ -553,7 +553,7 @@ The ``connection`` class .. seealso:: libpq docs for `PQprotocolVersion()`__ for details. - .. __: http://www.postgresql.org/docs/9.0/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION + .. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION .. versionadded:: 2.0.12 @@ -571,7 +571,7 @@ The ``connection`` class .. seealso:: libpq docs for `PQserverVersion()`__ for details. - .. __: http://www.postgresql.org/docs/9.0/static/libpq-status.html#LIBPQ-PQSERVERVERSION + .. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION .. versionadded:: 2.0.12 @@ -606,7 +606,7 @@ The ``connection`` class `~psycopg2.extensions.lobject` to be instantiated. .. |lo_import| replace:: `!lo_import()` - .. _lo_import: http://www.postgresql.org/docs/9.0/static/lo-interfaces.html#LO-IMPORT + .. _lo_import: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT Available values for *mode* are: diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst index 59aa9ac6..4b8495ae 100644 --- a/doc/src/cursor.rst +++ b/doc/src/cursor.rst @@ -67,10 +67,10 @@ The ``cursor`` class |execute*|_ methods yet. .. |pg_type| replace:: :sql:`pg_type` - .. _pg_type: http://www.postgresql.org/docs/9.0/static/catalog-pg-type.html - .. _PQgetlength: http://www.postgresql.org/docs/9.0/static/libpq-exec.html#LIBPQ-PQGETLENGTH - .. _PQfsize: http://www.postgresql.org/docs/9.0/static/libpq-exec.html#LIBPQ-PQFSIZE - .. _NUMERIC: http://www.postgresql.org/docs/9.0/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL + .. _pg_type: http://www.postgresql.org/docs/current/static/catalog-pg-type.html + .. _PQgetlength: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH + .. _PQfsize: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE + .. _NUMERIC: http://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL .. |NUMERIC| replace:: :sql:`NUMERIC` .. versionchanged:: 2.4 @@ -378,10 +378,10 @@ The ``cursor`` class more flexibility. .. |CREATE-TABLE| replace:: :sql:`CREATE TABLE` - .. __: http://www.postgresql.org/docs/9.0/static/sql-createtable.html + .. __: http://www.postgresql.org/docs/current/static/sql-createtable.html .. |INSERT-RETURNING| replace:: :sql:`INSERT ... RETURNING` - .. __: http://www.postgresql.org/docs/9.0/static/sql-insert.html + .. __: http://www.postgresql.org/docs/current/static/sql-insert.html .. attribute:: query @@ -467,7 +467,7 @@ The ``cursor`` class :param table: name of the table to copy data into. :param sep: columns separator expected in the file. Defaults to a tab. :param null: textual representation of :sql:`NULL` in the file. - The default is the two character string ``\N``. + The default is the two characters string ``\N``. :param size: size of the buffer used to read from the file. :param columns: iterable with name of the columns to import. The length and types should match the content of the file to read. @@ -500,7 +500,7 @@ The ``cursor`` class :param table: name of the table to copy data from. :param sep: columns separator expected in the file. Defaults to a tab. :param null: textual representation of :sql:`NULL` in the file. - The default is the two character string ``\N``. + The default is the two characters string ``\N``. :param columns: iterable with name of the columns to export. If not specified, export all the columns. @@ -540,7 +540,7 @@ The ``cursor`` class ... .. |COPY| replace:: :sql:`COPY` - .. __: http://www.postgresql.org/docs/9.0/static/sql-copy.html + .. __: http://www.postgresql.org/docs/current/static/sql-copy.html .. versionadded:: 2.0.6 diff --git a/doc/src/errorcodes.rst b/doc/src/errorcodes.rst index 499881d1..f69dbe1e 100644 --- a/doc/src/errorcodes.rst +++ b/doc/src/errorcodes.rst @@ -39,7 +39,7 @@ From PostgreSQL documentation: .. seealso:: `PostgreSQL Error Codes table`__ - .. __: http://www.postgresql.org/docs/9.0/static/errcodes-appendix.html#ERRCODES-TABLE + .. __: http://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE An example of the available constants defined in the module: diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 1ad88eda..b0a68ce4 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -82,7 +82,7 @@ functionalities defined by the |DBAPI|_. The method uses the efficient |lo_export|_ libpq function. .. |lo_export| replace:: `!lo_export()` - .. _lo_export: http://www.postgresql.org/docs/9.0/static/lo-interfaces.html#LO-EXPORT + .. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT .. method:: seek(offset, whence=0) @@ -103,7 +103,14 @@ functionalities defined by the |DBAPI|_. running these versions. It uses the |lo_truncate|_ libpq function. .. |lo_truncate| replace:: `!lo_truncate()` - .. _lo_truncate: http://www.postgresql.org/docs/9.0/static/lo-interfaces.html#LO-TRUNCATE + .. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE + + .. warning:: + + If Psycopg is built with |lo_truncate| support (i.e. if the + :program:`pg_config` used during setup is version >= 8.3), but at + runtime an older libpq is found, Psycopg will fail to import. See + :ref:`the lo_truncate FAQ ` about the problem. .. method:: close() @@ -325,6 +332,20 @@ details. .. versionadded:: 2.4.3 + .. _cast-array-unknown: + + .. note:: + + The function can be used to create a generic array typecaster, + returning a list of strings: just use the `~psycopg2.STRING` as base + typecaster. For instance, if you want to receive from the database an + array of :sql:`macaddr`, each address represented by string, you can + use:: + + psycopg2.extensions.register_type( + psycopg2.extensions.new_array_type( + (1040,), 'MACADDR[]', psycopg2.STRING)) + .. function:: register_type(obj [, scope]) @@ -349,7 +370,7 @@ details. Used by Psycopg when adapting or casting unicode strings. See :ref:`unicode-handling`. - .. __: http://www.postgresql.org/docs/9.0/static/multibyte.html + .. __: http://www.postgresql.org/docs/current/static/multibyte.html .. __: http://docs.python.org/library/codecs.html#standard-encodings @@ -432,7 +453,7 @@ set to one of the following constants: .. seealso:: `Read Committed Isolation Level`__ in PostgreSQL documentation. - .. __: http://www.postgresql.org/docs/9.1/static/transaction-iso.html#XACT-READ-COMMITTED + .. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED .. data:: ISOLATION_LEVEL_REPEATABLE_READ @@ -456,7 +477,7 @@ set to one of the following constants: .. seealso:: `Repeatable Read Isolation Level`__ in PostgreSQL documentation. - .. __: http://www.postgresql.org/docs/9.1/static/transaction-iso.html#XACT-REPEATABLE-READ + .. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-REPEATABLE-READ .. data:: ISOLATION_LEVEL_SERIALIZABLE @@ -475,7 +496,7 @@ set to one of the following constants: .. seealso:: `Serializable Isolation Level`__ in PostgreSQL documentation. - .. __: http://www.postgresql.org/docs/9.1/static/transaction-iso.html#XACT-SERIALIZABLE + .. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE diff --git a/doc/src/extras.rst b/doc/src/extras.rst index 710e9b5c..f3f10b12 100644 --- a/doc/src/extras.rst +++ b/doc/src/extras.rst @@ -155,7 +155,7 @@ can be enabled using the `register_hstore()` function. .. autofunction:: register_hstore .. |hstore| replace:: :sql:`hstore` -.. _hstore: http://www.postgresql.org/docs/9.0/static/hstore.html +.. _hstore: http://www.postgresql.org/docs/current/static/hstore.html @@ -177,7 +177,7 @@ after a table row type) into a Python named tuple, or into a regular tuple if :py:func:`collections.namedtuple` is not found. .. |CREATE TYPE| replace:: :sql:`CREATE TYPE` -.. _CREATE TYPE: http://www.postgresql.org/docs/9.0/static/sql-createtype.html +.. _CREATE TYPE: http://www.postgresql.org/docs/current/static/sql-createtype.html .. doctest:: @@ -250,6 +250,7 @@ UUID data type ^^^^^^^^^^^^^^^^^^^^^^ .. versionadded:: 2.0.9 +.. versionchanged:: 2.4.5 added inet array support. .. doctest:: @@ -264,7 +265,7 @@ UUID data type '192.168.0.1/24' -.. autofunction:: register_inet() +.. autofunction:: register_inet .. autoclass:: Inet diff --git a/doc/src/faq.rst b/doc/src/faq.rst index 3296cd84..01527ca0 100644 --- a/doc/src/faq.rst +++ b/doc/src/faq.rst @@ -10,6 +10,7 @@ suggest new entries! Problems with transactions handling ----------------------------------- +.. _faq-idle-in-transaction: .. cssclass:: faq Why does `!psycopg2` leave database sessions "idle in transaction"? @@ -25,6 +26,10 @@ Why does `!psycopg2` leave database sessions "idle in transaction"? connection in `~connection.autocommit` mode to avoid a new transaction to be started at the first command. + +.. _faq-transaction-aborted: +.. cssclass:: faq + I receive the error *current transaction is aborted, commands ignored until end of transaction block* and can't do anything else! There was a problem *in the previous* command to the database, which resulted in an error. The database will not recover automatically from @@ -33,7 +38,11 @@ I receive the error *current transaction is aborted, commands ignored until end PostgreSQL supports nested transactions using the |SAVEPOINT|_ command). .. |SAVEPOINT| replace:: :sql:`SAVEPOINT` - .. _SAVEPOINT: http://www.postgresql.org/docs/9.0/static/sql-savepoint.html + .. _SAVEPOINT: http://www.postgresql.org/docs/current/static/sql-savepoint.html + + +.. _faq-transaction-aborted-multiprocess: +.. cssclass:: faq Why do I get the error *current transaction is aborted, commands ignored until end of transaction block* when I use `!multiprocessing` (or any other forking system) and not when use `!threading`? Psycopg's connections can't be shared across processes (but are thread @@ -45,6 +54,7 @@ Why do I get the error *current transaction is aborted, commands ignored until e Problems with type conversions ------------------------------ +.. _faq-cant-adapt: .. cssclass:: faq Why does `!cursor.execute()` raise the exception *can't adapt*? @@ -53,6 +63,10 @@ Why does `!cursor.execute()` raise the exception *can't adapt*? as query parameter an object for which there is no adapter registered for its class. See :ref:`adapting-new-types` for informations. + +.. _faq-number-required: +.. cssclass:: faq + I can't pass an integer or a float parameter to my query: it says *a number is required*, but *it is* a number! In your query string, you always have to use ``%s`` placeholders, event when passing a number. All Python objects are converted by Psycopg @@ -62,6 +76,10 @@ I can't pass an integer or a float parameter to my query: it says *a number is r >>> cur.execute("INSERT INTO numbers VALUES (%d)", (42,)) # WRONG >>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct + +.. _faq-not-all-arguments-converted: +.. cssclass:: faq + I try to execute a query but it fails with the error *not all arguments converted during string formatting* (or *object does not support indexing*). Why? Psycopg always require positional arguments to be passed as a sequence, even when the query takes a single parameter. And remember that to make a @@ -73,6 +91,10 @@ I try to execute a query but it fails with the error *not all arguments converte >>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct >>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct + +.. _faq-unicode: +.. cssclass:: faq + My database is Unicode, but I receive all the strings as UTF-8 `!str`. Can I receive `!unicode` objects instead? The following magic formula will do the trick:: @@ -81,6 +103,10 @@ My database is Unicode, but I receive all the strings as UTF-8 `!str`. Can I rec See :ref:`unicode-handling` for the gory details. + +.. _faq-float: +.. cssclass:: faq + Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!Decimal` objects. Can I have `!float` instead? You can register a customized adapter for PostgreSQL decimal type:: @@ -94,6 +120,10 @@ Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!D documentation. If you find `!psycopg2.extensions.DECIMAL` not avalable, use `!psycopg2._psycopg.DECIMAL` instead. + +.. _faq-bytea-9.0: +.. cssclass:: faq + Transferring binary data from PostgreSQL 9.0 doesn't work. PostgreSQL 9.0 uses by default `the "hex" format`__ to transfer :sql:`bytea` data: the format can't be parsed by the libpq 8.4 and @@ -106,13 +136,25 @@ Transferring binary data from PostgreSQL 9.0 doesn't work. session before reading binary data; - upgrade the libpq library on the client to at least 9.0. - .. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html - .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT + .. __: http://www.postgresql.org/docs/current/static/datatype-binary.html + .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT + + +.. _faq-array: +.. cssclass:: faq + +Arrays of *TYPE* are not casted to list. + Arrays are only casted to list when their oid is known, and an array + typecaster is registered for them. If there is no typecaster, the array is + returned unparsed from PostgreSQL (e.g. ``{a,b,c}``). It is easy to create + a generic arrays typecaster, returning a list of array: an example is + provided in the `~psycopg2.extensions.new_array_type()` documentation. Best practices -------------- +.. _faq-reuse-cursors: .. cssclass:: faq When should I save and re-use a cursor as opposed to creating a new one as needed? @@ -124,6 +166,10 @@ When should I save and re-use a cursor as opposed to creating a new one as neede them.) The only exception are tight loops where one usually use the same cursor for a whole bunch of :sql:`INSERT`\s or :sql:`UPDATE`\s. + +.. _faq-reuse-connections: +.. cssclass:: faq + When should I save and re-use a connection as opposed to creating a new one as needed? Creating a connection can be slow (think of SSL over TCP) so the best practice is to create a single connection and keep it open as long as @@ -132,6 +178,10 @@ When should I save and re-use a connection as opposed to creating a new one as n left "idle in transaction". See also `psycopg2.pool` for lightweight connection pooling. + +.. _faq-named-cursors: +.. cssclass:: faq + What are the advantages or disadvantages of using named cursors? The only disadvantages is that they use up resources on the server and that there is a little overhead because a at least two queries (one to @@ -144,16 +194,44 @@ What are the advantages or disadvantages of using named cursors? Problems compiling and deploying psycopg2 ----------------------------------------- +.. _faq-python-h: .. cssclass:: faq I can't compile `!psycopg2`: the compiler says *error: Python.h: No such file or directory*. What am I missing? You need to install a Python development package: it is usually called ``python-dev``. + +.. _faq-libpq-fe-h: +.. cssclass:: faq + I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file or directory*. What am I missing? You need to install the development version of the libpq: the package is usually called ``libpq-dev``. + +.. _faq-lo_truncate: +.. cssclass:: faq + +`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported. + This means that Psycopg has been compiled with |lo_truncate|_ support, + which means that the libpq used at compile time was version >= 8.3, but at + runtime an older libpq library is found. You can use:: + + $ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq + + to find what is the version used at runtime. + + You can avoid the problem by using the same version of the + :program:`pg_config` at install time and the libpq at runtime. + + .. |lo_truncate| replace:: `!lo_truncate()` + .. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE + + +.. _faq-import-mod_wsgi: +.. cssclass:: faq + Psycopg raises *ImportError: cannot import name tz* on import in mod_wsgi / ASP, but it works fine otherwise. If `!psycopg2` is installed in an egg_ (e.g. because installed by :program:`easy_install`), the user running the program may be unable to diff --git a/doc/src/index.rst b/doc/src/index.rst index 906753e0..e15e7bb7 100644 --- a/doc/src/index.rst +++ b/doc/src/index.rst @@ -32,9 +32,9 @@ Psycopg 2 is both Unicode and Python 3 friendly. .. _PostgreSQL: http://www.postgresql.org/ .. _Python: http://www.python.org/ .. _Zope: http://www.zope.org/ -.. _libpq: http://www.postgresql.org/docs/9.0/static/libpq.html +.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html .. |COPY-TO-FROM| replace:: :sql:`COPY TO/COPY FROM` -.. __: http://www.postgresql.org/docs/9.0/static/sql-copy.html +.. __: http://www.postgresql.org/docs/current/static/sql-copy.html .. rubric:: Contents diff --git a/doc/src/module.rst b/doc/src/module.rst index 29f4b636..35292ba3 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -43,8 +43,8 @@ The module interface respects the standard defined in the |DBAPI|_. Also note that the same parameters can be passed to the client library using `environment variables`__. - .. __: http://www.postgresql.org/docs/9.1/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS - .. __: http://www.postgresql.org/docs/9.1/static/libpq-envars.html + .. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS + .. __: http://www.postgresql.org/docs/current/static/libpq-envars.html Using the *connection_factory* parameter a different class or connections factory can be specified. It should be a callable object @@ -117,31 +117,30 @@ available through the following exceptions: if not available. The `~psycopg2.errorcodes` module contains symbolic constants representing PostgreSQL error codes. + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> try: + ... cur.execute("SELECT * FROM barf") + ... except Exception, e: + ... pass + + >>> e.pgcode + '42P01' + >>> print e.pgerror + ERROR: relation "barf" does not exist + LINE 1: SELECT * FROM barf + ^ + .. attribute:: cursor + + The cursor the exception was raised from; `None` if not applicable. + .. extension:: - The `~Error.pgerror` and `~Error.pgcode` attributes are - Psycopg extensions. - - .. doctest:: - :options: +NORMALIZE_WHITESPACE - - >>> try: - ... cur.execute("SELECT * FROM barf") - ... except Exception, e: - ... pass - - >>> e.pgcode - '42P01' - >>> print e.pgerror - ERROR: relation "barf" does not exist - LINE 1: SELECT * FROM barf - ^ - - .. versionchanged:: 2.0.7 added `Error.pgerror` and - `Error.pgcode` attributes. + The `~Error.pgerror`, `~Error.pgcode`, and `~Error.cursor` attributes + are Psycopg extensions. - .. exception:: InterfaceError Exception raised for errors that are related to the database interface diff --git a/doc/src/usage.rst b/doc/src/usage.rst index d480adef..22e2394f 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -294,9 +294,9 @@ the SQL string that would be sent to the database. `bytea_output`__ configuration parameter to ``escape``, either in the server configuration file or in the client session (using a query such as ``SET bytea_output TO escape;``) before receiving binary data. - - .. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html - .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT + + .. __: http://www.postgresql.org/docs/current/static/datatype-binary.html + .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT .. _adapt-date: @@ -334,6 +334,14 @@ the SQL string that would be sent to the database. >>> cur.mogrify("SELECT %s;", ([10, 20, 30], )) 'SELECT ARRAY[10, 20, 30];' + .. note:: + + Reading back from PostgreSQL, arrays are converted to list of Python + objects as expected, but only if the types are known one. Arrays of + unknown types are returned as represented by the database (e.g. + ``{a,b,c}``). You can easily create a typecaster for :ref:`array of + unknown types `. + .. _adapt-tuple: .. index:: @@ -378,7 +386,7 @@ the SQL string that would be sent to the database. further details. .. |hstore| replace:: :sql:`hstore` - .. _hstore: http://www.postgresql.org/docs/9.0/static/hstore.html + .. _hstore: http://www.postgresql.org/docs/current/static/hstore.html .. versionadded:: 2.3 the :sql:`hstore` adaptation. @@ -403,7 +411,7 @@ defined on the database connection (the `PostgreSQL encoding`__, available in >>> cur.execute("INSERT INTO test (num, data) VALUES (%s,%s);", (74, u)) -.. __: http://www.postgresql.org/docs/9.0/static/multibyte.html +.. __: http://www.postgresql.org/docs/current/static/multibyte.html .. __: http://docs.python.org/library/codecs.html#standard-encodings When reading data from the database, in Python 2 the strings returned are @@ -621,7 +629,7 @@ lifetime extends well after `~connection.commit()`, calling .. |DECLARE| replace:: :sql:`DECLARE` -.. _DECLARE: http://www.postgresql.org/docs/9.0/static/sql-declare.html +.. _DECLARE: http://www.postgresql.org/docs/current/static/sql-declare.html @@ -651,7 +659,7 @@ forked processes`__, so when using a module such as `multiprocessing` or a forking web deploy method such as FastCGI make sure to create the connections *after* the fork. -.. __: http://www.postgresql.org/docs/9.0/static/libpq-connect.html#LIBPQ-CONNECT +.. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT Connections shouldn't be shared either by different green threads: see :ref:`green-support` for further details. @@ -687,7 +695,7 @@ Please refer to the documentation of the single methods for details and examples. .. |COPY| replace:: :sql:`COPY` -.. __: http://www.postgresql.org/docs/9.0/static/sql-copy.html +.. __: http://www.postgresql.org/docs/current/static/sql-copy.html @@ -704,7 +712,7 @@ access to user data that is stored in a special large-object structure. They are useful with data values too large to be manipulated conveniently as a whole. -.. __: http://www.postgresql.org/docs/9.0/static/largeobjects.html +.. __: http://www.postgresql.org/docs/current/static/largeobjects.html Psycopg allows access to the large object using the `~psycopg2.extensions.lobject` class. Objects are generated using the @@ -715,9 +723,9 @@ Psycopg large object support efficient import/export with file system files using the |lo_import|_ and |lo_export|_ libpq functions. .. |lo_import| replace:: `!lo_import()` -.. _lo_import: http://www.postgresql.org/docs/9.0/static/lo-interfaces.html#LO-IMPORT +.. _lo_import: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT .. |lo_export| replace:: `!lo_export()` -.. _lo_export: http://www.postgresql.org/docs/9.0/static/lo-interfaces.html#LO-EXPORT +.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT diff --git a/lib/errorcodes.py b/lib/errorcodes.py index e6c03eb2..4a4ee1ce 100644 --- a/lib/errorcodes.py +++ b/lib/errorcodes.py @@ -26,7 +26,7 @@ This module contains symbolic names for all PostgreSQL error codes. # # Based on: # -# http://www.postgresql.org/docs/8.4/static/errcodes-appendix.html +# http://www.postgresql.org/docs/current/static/errcodes-appendix.html # def lookup(code, _cache={}): diff --git a/lib/extras.py b/lib/extras.py index a6fcbc04..2e3974b6 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -88,25 +88,17 @@ class DictCursorBase(_cursor): def __iter__(self): if self._prefetch: - res = _cursor.fetchmany(self, self.itersize) - if not res: - return + res = _cursor.__iter__(self) + first = res.next() if self._query_executed: self._build_index() if not self._prefetch: - res = _cursor.fetchmany(self, self.itersize) + res = _cursor.__iter__(self) + first = res.next() - for r in res: - yield r - - # the above was the first itersize record. the following are - # in a repeated loop. + yield first while 1: - res = _cursor.fetchmany(self, self.itersize) - if not res: - return - for r in res: - yield r + yield res.next() class DictConnection(_connection): @@ -318,14 +310,17 @@ class NamedTupleCursor(_cursor): return [nt(*t) for t in ts] def __iter__(self): - # Invoking _cursor.__iter__(self) goes to infinite recursion, - # so we do pagination by hand + it = _cursor.__iter__(self) + t = it.next() + + nt = self.Record + if nt is None: + nt = self.Record = self._make_nt() + + yield nt(*t) + while 1: - recs = self.fetchmany(self.itersize) - if not recs: - return - for rec in recs: - yield rec + yield nt(*it.next()) try: from collections import namedtuple @@ -445,7 +440,7 @@ class UUID_adapter(object): """Adapt Python's uuid.UUID__ type to PostgreSQL's uuid__. .. __: http://docs.python.org/library/uuid.html - .. __: http://www.postgresql.org/docs/8.4/static/datatype-uuid.html + .. __: http://www.postgresql.org/docs/current/static/datatype-uuid.html """ def __init__(self, uuid): @@ -460,31 +455,29 @@ class UUID_adapter(object): __str__ = getquoted def register_uuid(oids=None, conn_or_curs=None): - """Create the UUID type and an uuid.UUID adapter.""" + """Create the UUID type and an uuid.UUID adapter. + + :param oids: oid for the PostgreSQL :sql:`uuid` type, or 2-items sequence + with oids of the type and the array. If not specified, use PostgreSQL + standard oids. + :param conn_or_curs: where to register the typecaster. If not specified, + register it globally. + """ import uuid if not oids: oid1 = 2950 oid2 = 2951 - elif type(oids) == list: + elif isinstance(oids, (list, tuple)): oid1, oid2 = oids else: oid1 = oids oid2 = 2951 - def parseUUIDARRAY(data, cursor): - if data is None: - return None - elif data == '{}': - return [] - else: - return [((len(x) > 0 and x != 'NULL') and uuid.UUID(x) or None) - for x in data[1:-1].split(',')] - _ext.UUID = _ext.new_type((oid1, ), "UUID", lambda data, cursor: data and uuid.UUID(data) or None) - _ext.UUIDARRAY = _ext.new_type((oid2,), "UUID[]", parseUUIDARRAY) + _ext.UUIDARRAY = _ext.new_array_type((oid2,), "UUID[]", _ext.UUID) _ext.register_type(_ext.UUID, conn_or_curs) _ext.register_type(_ext.UUIDARRAY, conn_or_curs) @@ -505,13 +498,13 @@ class Inet(object): """ def __init__(self, addr): self.addr = addr - + def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.addr) def prepare(self, conn): self._conn = conn - + def getquoted(self): obj = _A(self.addr) if hasattr(obj, 'prepare'): @@ -524,13 +517,32 @@ class Inet(object): def __str__(self): return str(self.addr) - + def register_inet(oid=None, conn_or_curs=None): - """Create the INET type and an Inet adapter.""" - if not oid: oid = 869 - _ext.INET = _ext.new_type((oid, ), "INET", + """Create the INET type and an Inet adapter. + + :param oid: oid for the PostgreSQL :sql:`inet` type, or 2-items sequence + with oids of the type and the array. If not specified, use PostgreSQL + standard oids. + :param conn_or_curs: where to register the typecaster. If not specified, + register it globally. + """ + if not oid: + oid1 = 869 + oid2 = 1041 + elif isinstance(oid, (list, tuple)): + oid1, oid2 = oid + else: + oid1 = oid + oid2 = 1041 + + _ext.INET = _ext.new_type((oid1, ), "INET", lambda data, cursor: data and Inet(data) or None) + _ext.INETARRAY = _ext.new_array_type((oid2, ), "INETARRAY", _ext.INET) + _ext.register_type(_ext.INET, conn_or_curs) + _ext.register_type(_ext.INETARRAY, conn_or_curs) + return _ext.INET @@ -849,9 +861,9 @@ class CompositeCaster(object): return self._ctor(*attrs) _re_tokenize = regex.compile(r""" - \(? ([,\)]) # an empty token, representing NULL + \(? ([,)]) # an empty token, representing NULL | \(? " ((?: [^"] | "")*) " [,)] # or a quoted string -| \(? ([^",\)]+) [,\)] # or an unquoted string +| \(? ([^",)]+) [,)] # or an unquoted string """, regex.VERBOSE) _re_undouble = regex.compile(r'(["\\])\1') @@ -861,7 +873,7 @@ class CompositeCaster(object): rv = [] for m in self._re_tokenize.finditer(s): if m is None: - raise psycopg2.InterfaceError("can't parse type: %r", s) + raise psycopg2.InterfaceError("can't parse type: %r" % s) if m.group(1): rv.append(None) elif m.group(2): diff --git a/lib/tz.py b/lib/tz.py index f7d9e451..cc99025b 100644 --- a/lib/tz.py +++ b/lib/tz.py @@ -38,20 +38,40 @@ class FixedOffsetTimezone(datetime.tzinfo): with a small change to the `!__init__()` method to allow for pickling and a default name in the form ``sHH:MM`` (``s`` is the sign.). + The implementation also caches instances. During creation, if a + FixedOffsetTimezone instance has previously been created with the same + offset and name that instance will be returned. This saves memory and + improves comparability. + .. __: http://docs.python.org/library/datetime.html#datetime-tzinfo """ _name = None _offset = ZERO - + + _cache = {} + def __init__(self, offset=None, name=None): if offset is not None: self._offset = datetime.timedelta(minutes = offset) if name is not None: self._name = name + def __new__(cls, offset=None, name=None): + """Return a suitable instance created earlier if it exists + """ + key = (offset, name) + try: + return cls._cache[key] + except KeyError: + tz = datetime.tzinfo.__new__(cls, offset, name) + tz.__init__(offset, name) + cls._cache[key] = tz + return tz + def __repr__(self): + offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60 return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \ - % (self._offset.seconds // 60, self._name) + % (offset_mins, self._name) def utcoffset(self, dt): return self._offset diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c index 2574c603..b08c1447 100644 --- a/psycopg/adapter_binary.c +++ b/psycopg/adapter_binary.c @@ -65,6 +65,13 @@ binary_quote(binaryObject *self) int got_view = 0; #endif + /* Allow Binary(None) to work */ + if (self->wrapped == Py_None) { + Py_INCREF(psyco_null); + rv = psyco_null; + goto exit; + } + /* if we got a plain string or a buffer we escape it and save the buffer */ #if HAS_MEMORYVIEW @@ -93,7 +100,7 @@ binary_quote(binaryObject *self) /* escape and build quoted buffer */ - to = (char *)binary_escape((unsigned char*)buffer, (size_t) buffer_len, + to = (char *)binary_escape((unsigned char*)buffer, (size_t)buffer_len, &len, self->conn ? ((connectionObject*)self->conn)->pgconn : NULL); if (to == NULL) { PyErr_NoMemory(); @@ -113,12 +120,6 @@ exit: if (got_view) { PyBuffer_Release(&view); } #endif - /* Allow Binary(None) to work */ - if (self->wrapped == Py_None) { - Py_INCREF(psyco_null); - rv = psyco_null; - } - /* if the wrapped object is not bytes or a buffer, this is an error */ if (!rv && !PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "can't escape %s to binary", @@ -149,16 +150,14 @@ binary_str(binaryObject *self) static PyObject * binary_prepare(binaryObject *self, PyObject *args) { - connectionObject *conn; + PyObject *conn; - if (!PyArg_ParseTuple(args, "O", &conn)) + if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) return NULL; Py_XDECREF(self->conn); - if (conn) { - self->conn = (PyObject*)conn; - Py_INCREF(self->conn); - } + self->conn = conn; + Py_INCREF(self->conn); Py_INCREF(Py_None); return Py_None; diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c index 5850be8f..bf31cfab 100644 --- a/psycopg/adapter_datetime.c +++ b/psycopg/adapter_datetime.c @@ -427,6 +427,10 @@ psyco_DateFromTicks(PyObject *self, PyObject *args) Py_DECREF(args); } } + else { + PyErr_SetString(InterfaceError, "failed localtime call"); + } + return res; } @@ -451,6 +455,10 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args) Py_DECREF(args); } } + else { + PyErr_SetString(InterfaceError, "failed localtime call"); + } + return res; } @@ -473,6 +481,9 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args) tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, pyPsycopgTzLOCAL); } + else { + PyErr_SetString(InterfaceError, "failed localtime call"); + } return res; } diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index cbb75422..d97ecfb1 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -98,9 +98,9 @@ list_getquoted(listObject *self, PyObject *args) static PyObject * list_prepare(listObject *self, PyObject *args) { - connectionObject *conn; + PyObject *conn; - if (!PyArg_ParseTuple(args, "O", &conn)) + if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) return NULL; /* note that we don't copy the encoding from the connection, but take a @@ -109,7 +109,7 @@ list_prepare(listObject *self, PyObject *args) work even without a connection to the backend. */ Py_CLEAR(self->connection); Py_INCREF(conn); - self->connection = (PyObject*)conn; + self->connection = conn; Py_INCREF(Py_None); return Py_None; diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index a9d4ec6e..773cfa0d 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -35,7 +35,7 @@ /* qstring_quote - do the quote process on plain and unicode strings */ -static PyObject * +BORROWED static PyObject * qstring_quote(qstringObject *self) { PyObject *str; @@ -124,24 +124,22 @@ qstring_str(qstringObject *self) static PyObject * qstring_prepare(qstringObject *self, PyObject *args) { - connectionObject *conn; + PyObject *conn; - if (!PyArg_ParseTuple(args, "O", &conn)) + if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) return NULL; /* we bother copying the encoding only if the wrapped string is unicode, we don't need the encoding if that's not the case */ if (PyUnicode_Check(self->wrapped)) { if (self->encoding) free(self->encoding); - self->encoding = strdup(conn->codec); - Dprintf("qstring_prepare: set encoding to %s", conn->codec); + self->encoding = strdup(((connectionObject *)conn)->codec); + Dprintf("qstring_prepare: set encoding to %s", self->encoding); } Py_CLEAR(self->conn); - if (conn) { - Py_INCREF(conn); - self->conn = (PyObject*)conn; - } + Py_INCREF(conn); + self->conn = conn; Py_INCREF(Py_None); return Py_None; diff --git a/psycopg/bytes_format.c b/psycopg/bytes_format.c index 1e729e49..68662075 100644 --- a/psycopg/bytes_format.c +++ b/psycopg/bytes_format.c @@ -86,7 +86,7 @@ /* Helpers for formatstring */ -Py_LOCAL_INLINE(PyObject *) +BORROWED Py_LOCAL_INLINE(PyObject *) getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx) { Py_ssize_t argidx = *p_argidx; diff --git a/psycopg/config.h b/psycopg/config.h index 2112043b..f3094493 100644 --- a/psycopg/config.h +++ b/psycopg/config.h @@ -160,4 +160,33 @@ static double round(double num) #define isinf(x) (!finite((x)) && (x)==(x)) #endif +/* decorators for the gcc cpychecker plugin */ +#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE) +#define BORROWED \ + __attribute__((cpychecker_returns_borrowed_ref)) +#else +#define BORROWED +#endif + +#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE) +#define STEALS(n) \ + __attribute__((cpychecker_steals_reference_to_arg(n))) +#else +#define STEALS(n) +#endif + +#if defined(WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE) +#define RAISES_NEG \ + __attribute__((cpychecker_negative_result_sets_exception)) +#else +#define RAISES_NEG +#endif + +#if defined(WITH_CPYCHECKER_SETS_EXCEPTION_ATTRIBUTE) +#define RAISES \ + __attribute__((cpychecker_sets_exception)) +#else +#define RAISES +#endif + #endif /* !defined(PSYCOPG_CONFIG_H) */ diff --git a/psycopg/connection.h b/psycopg/connection.h index 9a9ea420..9647ffd2 100644 --- a/psycopg/connection.h +++ b/psycopg/connection.h @@ -131,27 +131,27 @@ typedef struct { /* C-callable functions in connection_int.c and connection_ext.c */ HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str); HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn); -HIDDEN int conn_get_isolation_level(connectionObject *self); +RAISES_NEG HIDDEN int conn_get_isolation_level(connectionObject *self); HIDDEN int conn_get_protocol_version(PGconn *pgconn); HIDDEN int conn_get_server_version(PGconn *pgconn); HIDDEN PGcancel *conn_get_cancel(PGconn *pgconn); HIDDEN void conn_notice_process(connectionObject *self); HIDDEN void conn_notice_clean(connectionObject *self); HIDDEN void conn_notifies_process(connectionObject *self); -HIDDEN int conn_setup(connectionObject *self, PGconn *pgconn); +RAISES_NEG HIDDEN int conn_setup(connectionObject *self, PGconn *pgconn); HIDDEN int conn_connect(connectionObject *self, long int async); HIDDEN void conn_close(connectionObject *self); -HIDDEN int conn_commit(connectionObject *self); -HIDDEN int conn_rollback(connectionObject *self); -HIDDEN int conn_set_session(connectionObject *self, const char *isolevel, +RAISES_NEG HIDDEN int conn_commit(connectionObject *self); +RAISES_NEG HIDDEN int conn_rollback(connectionObject *self); +RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, const char *isolevel, const char *readonly, const char *deferrable, int autocommit); HIDDEN int conn_set_autocommit(connectionObject *self, int value); -HIDDEN int conn_switch_isolation_level(connectionObject *self, int level); -HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc); +RAISES_NEG HIDDEN int conn_switch_isolation_level(connectionObject *self, int level); +RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc); HIDDEN int conn_poll(connectionObject *self); -HIDDEN int conn_tpc_begin(connectionObject *self, XidObject *xid); -HIDDEN int conn_tpc_command(connectionObject *self, +RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, XidObject *xid); +RAISES_NEG HIDDEN int conn_tpc_command(connectionObject *self, const char *cmd, XidObject *xid); HIDDEN PyObject *conn_tpc_recover(connectionObject *self); diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c index 70465132..8c8fed47 100644 --- a/psycopg/connection_int.c +++ b/psycopg/connection_int.c @@ -120,8 +120,16 @@ conn_notice_process(connectionObject *self) /* Respect the order in which notices were produced, because in notice_list they are reversed (see ticket #9) */ - PyList_Insert(self->notice_list, nnotices, msg); - Py_DECREF(msg); + if (msg) { + PyList_Insert(self->notice_list, nnotices, msg); + Py_DECREF(msg); + } + else { + /* We don't really have a way to report errors, so gulp it. + * The function should only fail for out of memory, so we are + * likely going to die anyway. */ + PyErr_Clear(); + } notice = notice->next; } @@ -242,19 +250,20 @@ conn_get_standard_conforming_strings(PGconn *pgconn) /* Remove irrelevant chars from encoding name and turn it uppercase. * - * Return a buffer allocated on Python heap, - * NULL and set an exception on error. + * Return a buffer allocated on Python heap into 'clean' and return 0 on + * success, otherwise return -1 and set an exception. */ -static char * -clean_encoding_name(const char *enc) +RAISES_NEG static int +clear_encoding_name(const char *enc, char **clean) { const char *i = enc; - char *rv, *j; + char *j, *buf; + int rv = -1; /* convert to upper case and remove '-' and '_' from string */ - if (!(j = rv = PyMem_Malloc(strlen(enc) + 1))) { + if (!(j = buf = PyMem_Malloc(strlen(enc) + 1))) { PyErr_NoMemory(); - return NULL; + goto exit; } while (*i) { @@ -267,25 +276,28 @@ clean_encoding_name(const char *enc) } *j = '\0'; - Dprintf("clean_encoding_name: %s -> %s", enc, rv); + Dprintf("clear_encoding_name: %s -> %s", enc, buf); + *clean = buf; + rv = 0; +exit: return rv; } /* Convert a PostgreSQL encoding to a Python codec. * - * Return a new copy of the codec name allocated on the Python heap, - * NULL with exception in case of error. + * Set 'codec' to a new copy of the codec name allocated on the Python heap. + * Return 0 in case of success, else -1 and set an exception. * * 'enc' should be already normalized (uppercase, no - or _). */ -static char * -conn_encoding_to_codec(const char *enc) +RAISES_NEG static int +conn_encoding_to_codec(const char *enc, char **codec) { char *tmp; Py_ssize_t size; PyObject *pyenc = NULL; - char *rv = NULL; + int rv = -1; /* Find the Py codec name from the PG encoding */ if (!(pyenc = PyDict_GetItemString(psycoEncodings, enc))) { @@ -305,7 +317,7 @@ conn_encoding_to_codec(const char *enc) } /* have our own copy of the python codec name */ - rv = psycopg_strdup(tmp, size); + rv = psycopg_strdup(codec, tmp, size); exit: Py_XDECREF(pyenc); @@ -320,7 +332,7 @@ exit: * * Return 0 on success, else nonzero. */ -static int +RAISES_NEG static int conn_read_encoding(connectionObject *self, PGconn *pgconn) { char *enc = NULL, *codec = NULL; @@ -335,12 +347,12 @@ conn_read_encoding(connectionObject *self, PGconn *pgconn) goto exit; } - if (!(enc = clean_encoding_name(tmp))) { + if (0 > clear_encoding_name(tmp, &enc)) { goto exit; } /* Look for this encoding in Python codecs. */ - if (!(codec = conn_encoding_to_codec(enc))) { + if (0 > conn_encoding_to_codec(enc, &codec)) { goto exit; } @@ -362,7 +374,7 @@ exit: } -int +RAISES_NEG int conn_get_isolation_level(connectionObject *self) { PGresult *pgres = NULL; @@ -456,7 +468,7 @@ conn_is_datestyle_ok(PGconn *pgconn) /* conn_setup - setup and read basic information about the connection */ -int +RAISES_NEG int conn_setup(connectionObject *self, PGconn *pgconn) { PGresult *pgres = NULL; @@ -470,7 +482,7 @@ conn_setup(connectionObject *self, PGconn *pgconn) return -1; } - if (conn_read_encoding(self, pgconn)) { + if (0 > conn_read_encoding(self, pgconn)) { return -1; } @@ -484,7 +496,7 @@ conn_setup(connectionObject *self, PGconn *pgconn) pthread_mutex_lock(&self->lock); Py_BLOCK_THREADS; - if (psyco_green() && (pq_set_non_blocking(self, 1, 1) != 0)) { + if (psyco_green() && (0 > pq_set_non_blocking(self, 1))) { return -1; } @@ -762,7 +774,7 @@ _conn_poll_setup_async(connectionObject *self) switch (self->status) { case CONN_STATUS_CONNECTING: /* Set the connection to nonblocking now. */ - if (pq_set_non_blocking(self, 1, 1) != 0) { + if (pq_set_non_blocking(self, 1) != 0) { break; } @@ -773,7 +785,7 @@ _conn_poll_setup_async(connectionObject *self) PyErr_SetString(InterfaceError, "only protocol 3 supported"); break; } - if (conn_read_encoding(self, self->pgconn)) { + if (0 > conn_read_encoding(self, self->pgconn)) { break; } self->cancel = conn_get_cancel(self->pgconn); @@ -906,6 +918,10 @@ conn_poll(connectionObject *self) void conn_close(connectionObject *self) { + if (self->closed) { + return; + } + /* sets this connection as closed even for other threads; also note that we need to check the value of pgconn, because we get called even when the connection fails! */ @@ -922,14 +938,13 @@ conn_close(connectionObject *self) * closed only in status CONN_STATUS_READY. */ - if (self->closed == 0) - self->closed = 1; + self->closed = 1; if (self->pgconn) { PQfinish(self->pgconn); - PQfreeCancel(self->cancel); - Dprintf("conn_close: PQfinish called"); self->pgconn = NULL; + Dprintf("conn_close: PQfinish called"); + PQfreeCancel(self->cancel); self->cancel = NULL; } @@ -939,7 +954,7 @@ conn_close(connectionObject *self) /* conn_commit - commit on a connection */ -int +RAISES_NEG int conn_commit(connectionObject *self) { int res; @@ -950,7 +965,7 @@ conn_commit(connectionObject *self) /* conn_rollback - rollback a connection */ -int +RAISES_NEG int conn_rollback(connectionObject *self) { int res; @@ -959,7 +974,7 @@ conn_rollback(connectionObject *self) return res; } -int +RAISES_NEG int conn_set_session(connectionObject *self, const char *isolevel, const char *readonly, const char *deferrable, int autocommit) @@ -1032,7 +1047,7 @@ conn_set_autocommit(connectionObject *self, int value) /* conn_switch_isolation_level - switch isolation level on the connection */ -int +RAISES_NEG int conn_switch_isolation_level(connectionObject *self, int level) { PGresult *pgres = NULL; @@ -1114,12 +1129,12 @@ endlock: /* conn_set_client_encoding - switch client encoding on connection */ -int +RAISES_NEG int conn_set_client_encoding(connectionObject *self, const char *enc) { PGresult *pgres = NULL; char *error = NULL; - int res = 1; + int res = -1; char *codec = NULL; char *clean_enc = NULL; @@ -1128,8 +1143,8 @@ conn_set_client_encoding(connectionObject *self, const char *enc) if (strcmp(self->encoding, enc) == 0) return 0; /* We must know what python codec this encoding is. */ - if (!(clean_enc = clean_encoding_name(enc))) { goto exit; } - if (!(codec = conn_encoding_to_codec(clean_enc))) { goto exit; } + if (0 > clear_encoding_name(enc, &clean_enc)) { goto exit; } + if (0 > conn_encoding_to_codec(clean_enc, &codec)) { goto exit; } Py_BEGIN_ALLOW_THREADS; pthread_mutex_lock(&self->lock); @@ -1187,7 +1202,7 @@ exit: * in regular transactions, as PostgreSQL won't even know we are in a TPC * until PREPARE. */ -int +RAISES_NEG int conn_tpc_begin(connectionObject *self, XidObject *xid) { PGresult *pgres = NULL; @@ -1221,7 +1236,7 @@ conn_tpc_begin(connectionObject *self, XidObject *xid) * The function doesn't change the connection state as it can be used * for many commands and for recovered transactions. */ -int +RAISES_NEG int conn_tpc_command(connectionObject *self, const char *cmd, XidObject *xid) { PGresult *pgres = NULL; diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 693de3ce..4e5b6170 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -122,8 +122,6 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds) static PyObject * psyco_conn_close(connectionObject *self, PyObject *args) { - EXC_IF_CONN_CLOSED(self); - Dprintf("psyco_conn_close: closing connection at %p", self); conn_close(self); Dprintf("psyco_conn_close: connection at %p closed", self); @@ -528,7 +526,7 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs) if (-1 == c_autocommit) { return NULL; } } - if (0 != conn_set_session(self, + if (0 > conn_set_session(self, c_isolevel, c_readonly, c_deferrable, c_autocommit)) { return NULL; } @@ -550,7 +548,7 @@ psyco_conn_autocommit_get(connectionObject *self) return ret; } -static PyObject * +BORROWED static PyObject * _psyco_conn_autocommit_set_checks(connectionObject *self) { /* wrapper to use the EXC_IF macros. @@ -637,7 +635,7 @@ psyco_conn_set_client_encoding(connectionObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "s", &enc)) return NULL; - if (conn_set_client_encoding(self, enc) == 0) { + if (conn_set_client_encoding(self, enc) >= 0) { Py_INCREF(Py_None); rv = Py_None; } @@ -701,8 +699,8 @@ psyco_conn_get_parameter_status(connectionObject *self, PyObject *args) static PyObject * psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds) { - Oid oid=InvalidOid, new_oid=InvalidOid; - char *new_file = NULL; + int oid = (int)InvalidOid, new_oid = (int)InvalidOid; + const char *new_file = NULL; const char *smode = ""; PyObject *factory = (PyObject *)&lobjectType; PyObject *obj; @@ -754,7 +752,7 @@ psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds) "get_backend_pid() -- Get backend process id." static PyObject * -psyco_conn_get_backend_pid(connectionObject *self) +psyco_conn_get_backend_pid(connectionObject *self, PyObject *args) { EXC_IF_CONN_CLOSED(self); @@ -767,7 +765,7 @@ psyco_conn_get_backend_pid(connectionObject *self) "reset() -- Reset current connection to defaults." static PyObject * -psyco_conn_reset(connectionObject *self) +psyco_conn_reset(connectionObject *self, PyObject *args) { int res; @@ -795,7 +793,7 @@ psyco_conn_get_exception(PyObject *self, void *closure) } static PyObject * -psyco_conn_poll(connectionObject *self) +psyco_conn_poll(connectionObject *self, PyObject *args) { int res; @@ -817,7 +815,7 @@ psyco_conn_poll(connectionObject *self) "fileno() -> int -- Return file descriptor associated to database connection." static PyObject * -psyco_conn_fileno(connectionObject *self) +psyco_conn_fileno(connectionObject *self, PyObject *args) { long int socket; @@ -836,7 +834,7 @@ psyco_conn_fileno(connectionObject *self) "executing an asynchronous operation." static PyObject * -psyco_conn_isexecuting(connectionObject *self) +psyco_conn_isexecuting(connectionObject *self, PyObject *args) { /* synchronous connections will always return False */ if (self->async == 0) { @@ -868,7 +866,7 @@ psyco_conn_isexecuting(connectionObject *self) "cancel() -- cancel the current operation" static PyObject * -psyco_conn_cancel(connectionObject *self) +psyco_conn_cancel(connectionObject *self, PyObject *args) { char errbuf[256]; diff --git a/psycopg/cursor.h b/psycopg/cursor.h index eecaa8ff..723bd170 100644 --- a/psycopg/cursor.h +++ b/psycopg/cursor.h @@ -85,7 +85,7 @@ struct cursorObject { /* C-callable functions in cursor_int.c and cursor_ext.c */ -HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid); +BORROWED HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid); HIDDEN void curs_reset(cursorObject *self); /* exception-raising macros */ diff --git a/psycopg/cursor_int.c b/psycopg/cursor_int.c index 40e0bae3..1ac3f550 100644 --- a/psycopg/cursor_int.c +++ b/psycopg/cursor_int.c @@ -38,7 +38,7 @@ * Return a borrowed reference. */ -PyObject * +BORROWED PyObject * curs_get_cast(cursorObject *self, PyObject *oid) { PyObject *cast; diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 86b4ee0a..bcbefc5f 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -52,9 +52,12 @@ extern PyObject *pyPsycopgTzFixedOffsetTimezone; static PyObject * psyco_curs_close(cursorObject *self, PyObject *args) { - EXC_IF_CURS_CLOSED(self); EXC_IF_ASYNC_IN_PROGRESS(self, close); + if (self->closed) { + goto exit; + } + if (self->name != NULL) { char buffer[128]; @@ -66,6 +69,7 @@ psyco_curs_close(cursorObject *self, PyObject *args) self->closed = 1; Dprintf("psyco_curs_close: cursor at %p closed", self); +exit: Py_INCREF(Py_None); return Py_None; } @@ -75,7 +79,7 @@ psyco_curs_close(cursorObject *self, PyObject *args) /* mogrify a query string and build argument array or dict */ -static int +RAISES_NEG static int _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new) { PyObject *key, *value, *n; @@ -217,7 +221,10 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new) } if (n == NULL) { - n = PyTuple_New(PyObject_Length(var)); + if (!(n = PyTuple_New(PyObject_Length(var)))) { + Py_DECREF(value); + return -1; + } } /* let's have d point just after the '%' */ @@ -356,11 +363,12 @@ _psyco_curs_merge_query_args(cursorObject *self, #define psyco_curs_execute_doc \ "execute(query, vars=None) -- Execute query with bound vars." -static int +RAISES_NEG static int _psyco_curs_execute(cursorObject *self, PyObject *operation, PyObject *vars, long int async) { - int res = 0; + int res = -1; + int tmp; PyObject *fquery, *cvt = NULL; operation = _psyco_curs_validate_sql_basic(self, operation); @@ -368,7 +376,7 @@ _psyco_curs_execute(cursorObject *self, /* Any failure from here forward should 'goto fail' rather than 'return 0' directly. */ - if (operation == NULL) { goto fail; } + if (operation == NULL) { goto exit; } IFCLEARPGRES(self->pgres); @@ -385,12 +393,12 @@ _psyco_curs_execute(cursorObject *self, if (vars && vars != Py_None) { - if(_mogrify(vars, operation, self, &cvt) == -1) { goto fail; } + if (0 > _mogrify(vars, operation, self, &cvt)) { goto exit; } } if (vars && cvt) { if (!(fquery = _psyco_curs_merge_query_args(self, operation, cvt))) { - goto fail; + goto exit; } if (self->name != NULL) { @@ -424,25 +432,20 @@ _psyco_curs_execute(cursorObject *self, /* At this point, the SQL statement must be str, not unicode */ - res = pq_execute(self, Bytes_AS_STRING(self->query), async); - Dprintf("psyco_curs_execute: res = %d, pgres = %p", res, self->pgres); - if (res == -1) { goto fail; } + tmp = pq_execute(self, Bytes_AS_STRING(self->query), async); + Dprintf("psyco_curs_execute: res = %d, pgres = %p", tmp, self->pgres); + if (tmp < 0) { goto exit; } - res = 1; /* Success */ - goto cleanup; + res = 0; /* Success */ - fail: - res = 0; - /* Fall through to cleanup */ - cleanup: - /* Py_XDECREF(operation) is safe because the original reference passed - by the caller was overwritten with either NULL or a new - reference */ - Py_XDECREF(operation); +exit: + /* Py_XDECREF(operation) is safe because the original reference passed + by the caller was overwritten with either NULL or a new + reference */ + Py_XDECREF(operation); + Py_XDECREF(cvt); - Py_XDECREF(cvt); - - return res; + return res; } static PyObject * @@ -476,13 +479,13 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) EXC_IF_ASYNC_IN_PROGRESS(self, execute); EXC_IF_TPC_PREPARED(self->conn, execute); - if (_psyco_curs_execute(self, operation, vars, self->conn->async)) { - Py_INCREF(Py_None); - return Py_None; - } - else { + if (0 > _psyco_curs_execute(self, operation, vars, self->conn->async)) { return NULL; } + + /* success */ + Py_INCREF(Py_None); + return Py_None; } #define psyco_curs_executemany_doc \ @@ -521,7 +524,7 @@ psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs) } while ((v = PyIter_Next(vars)) != NULL) { - if (_psyco_curs_execute(self, operation, v, 0) == 0) { + if (0 > _psyco_curs_execute(self, operation, v, 0)) { Py_DECREF(v); Py_XDECREF(iter); return NULL; @@ -568,7 +571,7 @@ _psyco_curs_mogrify(cursorObject *self, if (vars && vars != Py_None) { - if (_mogrify(vars, operation, self, &cvt) == -1) { + if (0 > _mogrify(vars, operation, self, &cvt)) { goto cleanup; } } @@ -644,7 +647,7 @@ psyco_curs_cast(cursorObject *self, PyObject *args) "default) or using the sequence factory previously set in the\n" \ "`row_factory` attribute. Return `!None` when no more data is available.\n" -static int +RAISES_NEG static int _psyco_curs_prefetch(cursorObject *self) { int i = 0; @@ -661,13 +664,14 @@ _psyco_curs_prefetch(cursorObject *self) return i; } -static PyObject * +RAISES_NEG static int _psyco_curs_buildrow_fill(cursorObject *self, PyObject *res, int row, int n, int istuple) { int i, len, err; const char *str; PyObject *val; + int rv = -1; for (i=0; i < n; i++) { if (PQgetisnull(self->pgres, row, i)) { @@ -682,59 +686,59 @@ _psyco_curs_buildrow_fill(cursorObject *self, PyObject *res, Dprintf("_psyco_curs_buildrow: row %ld, element %d, len %d", self->row, i, len); - val = typecast_cast(PyTuple_GET_ITEM(self->casts, i), str, len, - (PyObject*)self); + if (!(val = typecast_cast(PyTuple_GET_ITEM(self->casts, i), str, len, + (PyObject*)self))) { + goto exit; + } - if (val) { - Dprintf("_psyco_curs_buildrow: val->refcnt = " - FORMAT_CODE_PY_SSIZE_T, - Py_REFCNT(val) - ); - if (istuple) { - PyTuple_SET_ITEM(res, i, val); - } - else { - err = PySequence_SetItem(res, i, val); - Py_DECREF(val); - if (err == -1) { - Py_DECREF(res); - res = NULL; - break; - } - } + Dprintf("_psyco_curs_buildrow: val->refcnt = " + FORMAT_CODE_PY_SSIZE_T, + Py_REFCNT(val) + ); + if (istuple) { + PyTuple_SET_ITEM(res, i, val); } else { - /* an error occurred in the type system, we return NULL to raise - an exception. the typecast code should already have set the - exception type and text */ - Py_DECREF(res); - res = NULL; - break; + err = PySequence_SetItem(res, i, val); + Py_DECREF(val); + if (err == -1) { goto exit; } } } - return res; + + rv = 0; + +exit: + return rv; } static PyObject * _psyco_curs_buildrow(cursorObject *self, int row) { int n; + int istuple; + PyObject *t = NULL; + PyObject *rv = NULL; n = PQnfields(self->pgres); - return _psyco_curs_buildrow_fill(self, PyTuple_New(n), row, n, 1); -} + istuple = (self->tuple_factory == Py_None); -static PyObject * -_psyco_curs_buildrow_with_factory(cursorObject *self, int row) -{ - int n; - PyObject *res; + if (istuple) { + t = PyTuple_New(n); + } + else { + t = PyObject_CallFunctionObjArgs(self->tuple_factory, self, NULL); + } + if (!t) { goto exit; } - n = PQnfields(self->pgres); - if (!(res = PyObject_CallFunctionObjArgs(self->tuple_factory, self, NULL))) - return NULL; + if (0 <= _psyco_curs_buildrow_fill(self, t, row, n, istuple)) { + rv = t; + t = NULL; + } + +exit: + Py_XDECREF(t); + return rv; - return _psyco_curs_buildrow_fill(self, res, row, n, 0); } static PyObject * @@ -766,11 +770,7 @@ psyco_curs_fetchone(cursorObject *self, PyObject *args) return Py_None; } - if (self->tuple_factory == Py_None) - res = _psyco_curs_buildrow(self, self->row); - else - res = _psyco_curs_buildrow_with_factory(self, self->row); - + res = _psyco_curs_buildrow(self, self->row); self->row++; /* move the counter to next line */ /* if the query was async aggresively free pgres, to allow @@ -817,11 +817,7 @@ psyco_curs_next_named(cursorObject *self) return NULL; } - if (self->tuple_factory == Py_None) - res = _psyco_curs_buildrow(self, self->row); - else - res = _psyco_curs_buildrow_with_factory(self, self->row); - + res = _psyco_curs_buildrow(self, self->row); self->row++; /* move the counter to next line */ /* if the query was async aggresively free pgres, to allow @@ -841,21 +837,34 @@ psyco_curs_next_named(cursorObject *self) "fetchmany(size=self.arraysize) -> list of tuple\n\n" \ "Return the next `size` rows of a query result set in the form of a list\n" \ "of tuples (by default) or using the sequence factory previously set in\n" \ -"the `row_factory` attribute. Return `!None` when no more data is available.\n" +"the `row_factory` attribute.\n\n" \ +"Return an empty list when no more data is available.\n" static PyObject * psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) { int i; - PyObject *list, *res; + PyObject *list = NULL; + PyObject *row = NULL; + PyObject *rv = NULL; + PyObject *pysize = NULL; long int size = self->arraysize; static char *kwlist[] = {"size", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwords, "|l", kwlist, &size)) { + /* allow passing None instead of omitting the *size* argument, + * or using the method from subclasses would be a problem */ + if (!PyArg_ParseTupleAndKeywords(args, kwords, "|O", kwlist, &pysize)) { return NULL; } + if (pysize && pysize != Py_None) { + size = PyInt_AsLong(pysize); + if (size == -1 && PyErr_Occurred()) { + return NULL; + } + } + EXC_IF_CURS_CLOSED(self); if (_psyco_curs_prefetch(self) < 0) return NULL; EXC_IF_NO_TUPLES(self); @@ -868,8 +877,8 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) EXC_IF_TPC_PREPARED(self->conn, fetchone); PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM \"%s\"", (int)size, self->name); - if (pq_execute(self, buffer, 0) == -1) return NULL; - if (_psyco_curs_prefetch(self) < 0) return NULL; + if (pq_execute(self, buffer, 0) == -1) { goto exit; } + if (_psyco_curs_prefetch(self) < 0) { goto exit; } } /* make sure size is not > than the available number of rows */ @@ -880,26 +889,21 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) Dprintf("psyco_curs_fetchmany: size = %ld", size); if (size <= 0) { - return PyList_New(0); + rv = PyList_New(0); + goto exit; } - list = PyList_New(size); + if (!(list = PyList_New(size))) { goto exit; } for (i = 0; i < size; i++) { - if (self->tuple_factory == Py_None) - res = _psyco_curs_buildrow(self, self->row); - else - res = _psyco_curs_buildrow_with_factory(self, self->row); - + row = _psyco_curs_buildrow(self, self->row); self->row++; - if (res == NULL) { - Py_DECREF(list); - return NULL; - } + if (row == NULL) { goto exit; } - PyList_SET_ITEM(list, i, res); + PyList_SET_ITEM(list, i, row); } + row = NULL; /* if the query was async aggresively free pgres, to allow successive requests to reallocate it */ @@ -908,7 +912,15 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) IFCLEARPGRES(self->pgres); - return list; + /* success */ + rv = list; + list = NULL; + +exit: + Py_XDECREF(list); + Py_XDECREF(row); + + return rv; } @@ -925,7 +937,9 @@ static PyObject * psyco_curs_fetchall(cursorObject *self, PyObject *args) { int i, size; - PyObject *list, *res; + PyObject *list = NULL; + PyObject *row = NULL; + PyObject *rv = NULL; EXC_IF_CURS_CLOSED(self); if (_psyco_curs_prefetch(self) < 0) return NULL; @@ -938,33 +952,27 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args) EXC_IF_ASYNC_IN_PROGRESS(self, fetchall); EXC_IF_TPC_PREPARED(self->conn, fetchall); PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name); - if (pq_execute(self, buffer, 0) == -1) return NULL; - if (_psyco_curs_prefetch(self) < 0) return NULL; + if (pq_execute(self, buffer, 0) == -1) { goto exit; } + if (_psyco_curs_prefetch(self) < 0) { goto exit; } } size = self->rowcount - self->row; if (size <= 0) { - return PyList_New(0); + rv = PyList_New(0); + goto exit; } - list = PyList_New(size); + if (!(list = PyList_New(size))) { goto exit; } for (i = 0; i < size; i++) { - if (self->tuple_factory == Py_None) - res = _psyco_curs_buildrow(self, self->row); - else - res = _psyco_curs_buildrow_with_factory(self, self->row); - + row = _psyco_curs_buildrow(self, self->row); self->row++; + if (row == NULL) { goto exit; } - if (res == NULL) { - Py_DECREF(list); - return NULL; - } - - PyList_SET_ITEM(list, i, res); + PyList_SET_ITEM(list, i, row); } + row = NULL; /* if the query was async aggresively free pgres, to allow successive requests to reallocate it */ @@ -973,7 +981,15 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args) && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) IFCLEARPGRES(self->pgres); - return list; + /* success */ + rv = list; + list = NULL; + +exit: + Py_XDECREF(list); + Py_XDECREF(row); + + return rv; } @@ -983,7 +999,7 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args) "callproc(procname, parameters=None) -- Execute stored procedure." static PyObject * -psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) +psyco_curs_callproc(cursorObject *self, PyObject *args) { const char *procname = NULL; char *sql = NULL; @@ -995,7 +1011,7 @@ psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) if (!PyArg_ParseTuple(args, "s#|O", &procname, &procname_len, ¶meters )) - { return NULL; } + { goto exit; } EXC_IF_CURS_CLOSED(self); EXC_IF_ASYNC_IN_PROGRESS(self, callproc); @@ -1004,10 +1020,10 @@ psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) if (self->name != NULL) { psyco_set_error(ProgrammingError, self, "can't call .callproc() on named cursors", NULL, NULL); - return NULL; + goto exit; } - if(parameters != Py_None) { + if (parameters != Py_None) { nparameters = PyObject_Length(parameters); if (nparameters < 0) nparameters = 0; } @@ -1016,7 +1032,8 @@ psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) sl = procname_len + 17 + nparameters*3 - (nparameters ? 1 : 0); sql = (char*)PyMem_Malloc(sl); if (sql == NULL) { - return PyErr_NoMemory(); + PyErr_NoMemory(); + goto exit; } sprintf(sql, "SELECT * FROM %s(", procname); @@ -1026,15 +1043,16 @@ psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) sql[sl-2] = ')'; sql[sl-1] = '\0'; - operation = Bytes_FromString(sql); - PyMem_Free((void*)sql); + if (!(operation = Bytes_FromString(sql))) { goto exit; } - if (_psyco_curs_execute(self, operation, parameters, self->conn->async)) { + if (0 <= _psyco_curs_execute(self, operation, parameters, self->conn->async)) { Py_INCREF(parameters); res = parameters; } - Py_DECREF(operation); +exit: + Py_XDECREF(operation); + PyMem_Free((void*)sql); return res; } @@ -1191,6 +1209,7 @@ static char *_psyco_curs_copy_columns(PyObject *columns) } if (NULL == (columnlist = PyMem_Malloc(bufsize))) { + Py_DECREF(coliter); PyErr_NoMemory(); goto error; } @@ -1247,8 +1266,8 @@ exit: #define psyco_curs_copy_from_doc \ "copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None) -- Copy table from file." -static int -_psyco_curs_has_read_check(PyObject* o, void* var) +STEALS(1) static int +_psyco_curs_has_read_check(PyObject *o, PyObject **var) { if (PyObject_HasAttrString(o, "readline") && PyObject_HasAttrString(o, "read")) { @@ -1258,7 +1277,7 @@ _psyco_curs_has_read_check(PyObject* o, void* var) * which could invoke the garbage collector. We thus need an * INCREF/DECREF pair if we store this pointer in a GC object, such as * a cursorObject */ - *((PyObject**)var) = o; + *var = o; return 1; } else { @@ -1333,7 +1352,7 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) Py_INCREF(file); self->copyfile = file; - if (pq_execute(self, query, 0) == 1) { + if (pq_execute(self, query, 0) >= 0) { res = Py_None; Py_INCREF(Py_None); } @@ -1354,11 +1373,11 @@ exit: #define psyco_curs_copy_to_doc \ "copy_to(file, table, sep='\\t', null='\\\\N', columns=None) -- Copy table to file." -static int -_psyco_curs_has_write_check(PyObject* o, void* var) +STEALS(1) static int +_psyco_curs_has_write_check(PyObject *o, PyObject **var) { if (PyObject_HasAttrString(o, "write")) { - *((PyObject**)var) = o; + *var = o; return 1; } else { @@ -1429,7 +1448,7 @@ psyco_curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs) Py_INCREF(file); self->copyfile = file; - if (pq_execute(self, query, 0) == 1) { + if (pq_execute(self, query, 0) >= 0) { res = Py_None; Py_INCREF(Py_None); } @@ -1503,7 +1522,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs) self->copyfile = file; /* At this point, the SQL statement must be str, not unicode */ - if (pq_execute(self, Bytes_AS_STRING(sql), 0) == 1) { + if (pq_execute(self, Bytes_AS_STRING(sql), 0) >= 0) { res = Py_None; Py_INCREF(res); } diff --git a/psycopg/lobject.h b/psycopg/lobject.h index f52d85cf..6587198c 100644 --- a/psycopg/lobject.h +++ b/psycopg/lobject.h @@ -51,19 +51,19 @@ typedef struct { /* functions exported from lobject_int.c */ -HIDDEN int lobject_open(lobjectObject *self, connectionObject *conn, - Oid oid, const char *smode, Oid new_oid, - const char *new_file); -HIDDEN int lobject_unlink(lobjectObject *self); -HIDDEN int lobject_export(lobjectObject *self, const char *filename); +RAISES_NEG HIDDEN int lobject_open(lobjectObject *self, connectionObject *conn, + Oid oid, const char *smode, Oid new_oid, + const char *new_file); +RAISES_NEG HIDDEN int lobject_unlink(lobjectObject *self); +RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename); -HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len); -HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf, +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); -HIDDEN int lobject_seek(lobjectObject *self, int pos, int whence); -HIDDEN int lobject_tell(lobjectObject *self); -HIDDEN int lobject_truncate(lobjectObject *self, size_t len); -HIDDEN int lobject_close(lobjectObject *self); +RAISES_NEG HIDDEN int lobject_seek(lobjectObject *self, int pos, int whence); +RAISES_NEG HIDDEN int lobject_tell(lobjectObject *self); +RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len); +RAISES_NEG HIDDEN int lobject_close(lobjectObject *self); #define lobject_is_closed(self) \ ((self)->fd < 0 || !(self)->conn || (self)->conn->closed) diff --git a/psycopg/lobject_int.c b/psycopg/lobject_int.c index e6ad1b6c..2f78e90b 100644 --- a/psycopg/lobject_int.c +++ b/psycopg/lobject_int.c @@ -50,7 +50,7 @@ collect_error(connectionObject *conn, char **error) * * Valid mode are [r|w|rw|n][t|b] */ -static int +RAISES_NEG static int _lobject_parse_mode(const char *mode) { int rv = 0; @@ -147,7 +147,7 @@ _lobject_unparse_mode(int mode) /* lobject_open - create a new/open an existing lo */ -int +RAISES_NEG int lobject_open(lobjectObject *self, connectionObject *conn, Oid oid, const char *smode, Oid new_oid, const char *new_file) { @@ -170,12 +170,18 @@ lobject_open(lobjectObject *self, connectionObject *conn, /* if the oid is InvalidOid we create a new lob before opening it or we import a file from the FS, depending on the value of - new_name */ + new_file */ if (oid == InvalidOid) { if (new_file) self->oid = lo_import(self->conn->pgconn, new_file); - else - self->oid = lo_create(self->conn->pgconn, new_oid); + else { + /* Use lo_creat when possible to be more middleware-friendly. + See ticket #88. */ + if (new_oid != InvalidOid) + self->oid = lo_create(self->conn->pgconn, new_oid); + else + self->oid = lo_creat(self->conn->pgconn, INV_READ | INV_WRITE); + } Dprintf("lobject_open: large object created with oid = %d", self->oid); @@ -232,7 +238,7 @@ lobject_open(lobjectObject *self, connectionObject *conn, /* lobject_close - close an existing lo */ -static int +RAISES_NEG static int lobject_close_locked(lobjectObject *self, char **error) { int retvalue; @@ -265,7 +271,7 @@ lobject_close_locked(lobjectObject *self, char **error) return retvalue; } -int +RAISES_NEG int lobject_close(lobjectObject *self) { PGresult *pgres = NULL; @@ -287,7 +293,7 @@ lobject_close(lobjectObject *self) /* lobject_unlink - remove an lo from database */ -int +RAISES_NEG int lobject_unlink(lobjectObject *self) { PGresult *pgres = NULL; @@ -321,7 +327,7 @@ lobject_unlink(lobjectObject *self) /* lobject_write - write bytes to a lo */ -Py_ssize_t +RAISES_NEG Py_ssize_t lobject_write(lobjectObject *self, const char *buf, size_t len) { Py_ssize_t written; @@ -348,7 +354,7 @@ lobject_write(lobjectObject *self, const char *buf, size_t len) /* lobject_read - read bytes from a lo */ -Py_ssize_t +RAISES_NEG Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len) { Py_ssize_t n_read; @@ -372,7 +378,7 @@ lobject_read(lobjectObject *self, char *buf, size_t len) /* lobject_seek - move the current position in the lo */ -int +RAISES_NEG int lobject_seek(lobjectObject *self, int pos, int whence) { PGresult *pgres = NULL; @@ -400,7 +406,7 @@ lobject_seek(lobjectObject *self, int pos, int whence) /* lobject_tell - tell the current position in the lo */ -int +RAISES_NEG int lobject_tell(lobjectObject *self) { PGresult *pgres = NULL; @@ -427,7 +433,7 @@ lobject_tell(lobjectObject *self) /* lobject_export - export to a local file */ -int +RAISES_NEG int lobject_export(lobjectObject *self, const char *filename) { PGresult *pgres = NULL; @@ -456,7 +462,7 @@ lobject_export(lobjectObject *self, const char *filename) #if PG_VERSION_HEX >= 0x080300 -int +RAISES_NEG int lobject_truncate(lobjectObject *self, size_t len) { int retvalue; diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index a55272ca..625a2939 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -373,7 +373,7 @@ lobject_dealloc(PyObject* obj) static int lobject_init(PyObject *obj, PyObject *args, PyObject *kwds) { - Oid oid=InvalidOid, new_oid=InvalidOid; + int oid = (int)InvalidOid, new_oid = (int)InvalidOid; const char *smode = ""; const char *new_file = NULL; PyObject *conn; @@ -383,7 +383,7 @@ lobject_init(PyObject *obj, PyObject *args, PyObject *kwds) return -1; return lobject_setup((lobjectObject *)obj, - (connectionObject *)conn, oid, smode, new_oid, new_file); + (connectionObject *)conn, (Oid)oid, smode, (Oid)new_oid, new_file); } static PyObject * diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c index f48c0fbf..23f12794 100644 --- a/psycopg/microprotocols.c +++ b/psycopg/microprotocols.c @@ -52,29 +52,35 @@ microprotocols_init(PyObject *dict) } -/* microprotocols_add - add a reverse type-caster to the dictionary */ - +/* microprotocols_add - add a reverse type-caster to the dictionary + * + * Return 0 on success, else -1 and set an exception. + */ int microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast) { - PyObject *key; + PyObject *key = NULL; + int rv = -1; if (proto == NULL) proto = (PyObject*)&isqlquoteType; Dprintf("microprotocols_add: cast %p for (%s, ?)", cast, type->tp_name); - key = PyTuple_Pack(2, (PyObject*)type, proto); - PyDict_SetItem(psyco_adapters, key, cast); - Py_DECREF(key); + if (!(key = PyTuple_Pack(2, (PyObject*)type, proto))) { goto exit; } + if (0 != PyDict_SetItem(psyco_adapters, key, cast)) { goto exit; } - return 0; + rv = 0; + +exit: + Py_XDECREF(key); + return rv; } /* Check if one of `obj` superclasses has an adapter for `proto`. * - * If it does, return a *borrowed reference* to the adapter, else NULL. + * If it does, return a *borrowed reference* to the adapter, else to None. */ -static PyObject * +BORROWED static PyObject * _get_superclass_adapter(PyObject *obj, PyObject *proto) { PyTypeObject *type; @@ -89,14 +95,14 @@ _get_superclass_adapter(PyObject *obj, PyObject *proto) #endif type->tp_mro)) { /* has no mro */ - return NULL; + return Py_None; } /* Walk the mro from the most specific subclass. */ mro = type->tp_mro; for (i = 1, ii = PyTuple_GET_SIZE(mro); i < ii; ++i) { st = PyTuple_GET_ITEM(mro, i); - key = PyTuple_Pack(2, st, proto); + if (!(key = PyTuple_Pack(2, st, proto))) { return NULL; } adapter = PyDict_GetItem(psyco_adapters, key); Py_DECREF(key); @@ -119,7 +125,7 @@ _get_superclass_adapter(PyObject *obj, PyObject *proto) return adapter; } } - return NULL; + return Py_None; } @@ -139,7 +145,7 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) Py_TYPE(obj)->tp_name); /* look for an adapter in the registry */ - key = PyTuple_Pack(2, Py_TYPE(obj), proto); + if (!(key = PyTuple_Pack(2, Py_TYPE(obj), proto))) { return NULL; } adapter = PyDict_GetItem(psyco_adapters, key); Py_DECREF(key); if (adapter) { @@ -148,7 +154,10 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) } /* Check if a superclass can be adapted and use the same adapter. */ - if (NULL != (adapter = _get_superclass_adapter(obj, proto))) { + if (!(adapter = _get_superclass_adapter(obj, proto))) { + return NULL; + } + if (Py_None != adapter) { adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); return adapted; } diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index 1734eceb..6ba4eca6 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -64,7 +64,7 @@ strip_severity(const char *msg) code. A list of error codes can be found at: http://www.postgresql.org/docs/current/static/errcodes-appendix.html */ -static PyObject * +BORROWED static PyObject * exception_from_sqlstate(const char *sqlstate) { switch (sqlstate[0]) { @@ -151,7 +151,7 @@ exception_from_sqlstate(const char *sqlstate) This function should be called while holding the GIL. */ -static void +RAISES static void pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres) { PyObject *exc = NULL; @@ -164,7 +164,7 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres) "psycopg went psycotic and raised a null error"); return; } - + /* if the connection has somehow beed broken, we mark the connection object as closed but requiring cleanup */ if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) @@ -249,7 +249,9 @@ pq_clear_critical(connectionObject *conn) } } -static PyObject * +/* return -1 if the exception is set (i.e. if conn->critical is set), + * else 0 */ +RAISES_NEG static int pq_resolve_critical(connectionObject *conn, int close) { Dprintf("pq_resolve_critical: resolving %s", conn->critical); @@ -264,11 +266,13 @@ pq_resolve_critical(connectionObject *conn, int close) /* we don't want to destroy this connection but just close it */ if (close == 1) conn_close(conn); - + /* remember to clear the critical! */ - pq_clear_critical(conn); + pq_clear_critical(conn); + + return -1; } - return NULL; + return 0; } /* pq_clear_async - clear the effects of a previous async query @@ -300,19 +304,16 @@ pq_clear_async(connectionObject *conn) Accepted arg values are 1 (nonblocking) and 0 (blocking). - Return 0 if everything ok, else nonzero. - - In case of error, if pyerr is nonzero, set a Python exception. + Return 0 if everything ok, else < 0 and set an exception. */ -int -pq_set_non_blocking(connectionObject *conn, int arg, int pyerr) +RAISES_NEG int +pq_set_non_blocking(connectionObject *conn, int arg) { int ret = PQsetnonblocking(conn->pgconn, arg); if (0 != ret) { Dprintf("PQsetnonblocking(%d) FAILED", arg); - if (pyerr) { - PyErr_SetString(OperationalError, "PQsetnonblocking() failed"); - } + PyErr_SetString(OperationalError, "PQsetnonblocking() failed"); + ret = -1; } return ret; } @@ -382,7 +383,7 @@ cleanup: This function should be called while holding the global interpreter lock. */ -void +RAISES void pq_complete_error(connectionObject *conn, PGresult **pgres, char **error) { Dprintf("pq_complete_error: pgconn = %p, pgres = %p, error = %s", @@ -444,38 +445,39 @@ pq_commit(connectionObject *conn) PGresult *pgres = NULL; char *error = NULL; + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&conn->lock); + Dprintf("pq_commit: pgconn = %p, autocommit = %d, status = %d", conn->pgconn, conn->autocommit, conn->status); if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) { Dprintf("pq_commit: no transaction to commit"); - return 0; + retvalue = 0; + } + else { + conn->mark += 1; + retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error, &_save); } - - Py_BEGIN_ALLOW_THREADS; - pthread_mutex_lock(&conn->lock); - conn->mark += 1; - - retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error, &_save); Py_BLOCK_THREADS; conn_notice_process(conn); Py_UNBLOCK_THREADS; + /* Even if an error occurred, the connection will be rolled back, + so we unconditionally set the connection status here. */ + conn->status = CONN_STATUS_READY; + pthread_mutex_unlock(&conn->lock); Py_END_ALLOW_THREADS; if (retvalue < 0) pq_complete_error(conn, &pgres, &error); - /* Even if an error occurred, the connection will be rolled back, - so we unconditionally set the connection status here. */ - conn->status = CONN_STATUS_READY; - return retvalue; } -int +RAISES_NEG int pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error, PyThreadState **tstate) { @@ -502,7 +504,7 @@ pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error, This function should be called while holding the global interpreter lock. */ -int +RAISES_NEG int pq_abort(connectionObject *conn) { int retvalue = -1; @@ -512,11 +514,6 @@ pq_abort(connectionObject *conn) Dprintf("pq_abort: pgconn = %p, autocommit = %d, status = %d", conn->pgconn, conn->autocommit, conn->status); - if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) { - Dprintf("pq_abort: no transaction to abort"); - return 0; - } - Py_BEGIN_ALLOW_THREADS; pthread_mutex_lock(&conn->lock); @@ -544,7 +541,7 @@ pq_abort(connectionObject *conn) connection without holding the global interpreter lock. */ -int +RAISES_NEG int pq_reset_locked(connectionObject *conn, PGresult **pgres, char **error, PyThreadState **tstate) { @@ -836,7 +833,7 @@ pq_flush(connectionObject *conn) this fucntion locks the connection object this function call Py_*_ALLOW_THREADS macros */ -int +RAISES_NEG int pq_execute(cursorObject *curs, const char *query, int async) { PGresult *pgres = NULL; @@ -846,8 +843,7 @@ pq_execute(cursorObject *curs, const char *query, int async) /* if the status of the connection is critical raise an exception and definitely close the connection */ if (curs->conn->critical) { - pq_resolve_critical(curs->conn, 1); - return -1; + return pq_resolve_critical(curs->conn, 1); } /* check status of connection, raise error if not OK */ @@ -942,12 +938,13 @@ pq_execute(cursorObject *curs, const char *query, int async) to respect the old DBAPI-2.0 compatible behaviour */ if (async == 0) { Dprintf("pq_execute: entering syncronous DBAPI compatibility mode"); - if (pq_fetch(curs) == -1) return -1; + if (pq_fetch(curs) < 0) return -1; } else { + PyObject *tmp; curs->conn->async_status = async_status; - curs->conn->async_cursor = PyWeakref_NewRef((PyObject *)curs, NULL); - if (!curs->conn->async_cursor) { + curs->conn->async_cursor = tmp = PyWeakref_NewRef((PyObject *)curs, NULL); + if (!tmp) { /* weakref creation failed */ return -1; } @@ -1016,7 +1013,7 @@ pq_get_last_result(connectionObject *conn) 1 - result from backend (possibly data is ready) */ -static int +RAISES_NEG static int _pq_fetch_tuples(cursorObject *curs) { int i, *dsize = NULL; @@ -1416,8 +1413,7 @@ pq_fetch(cursorObject *curs) Dprintf("pq_fetch: got a NULL pgres, checking for critical"); pq_set_critical(curs->conn); if (curs->conn->critical) { - pq_resolve_critical(curs->conn); - return -1; + return pq_resolve_critical(curs->conn); } else { return 0; @@ -1493,13 +1489,7 @@ pq_fetch(cursorObject *curs) raise the exception but we avoid to close the connection) */ Dprintf("pq_fetch: fetching done; check for critical errors"); if (curs->conn->critical) { - if (ex == -1) { - pq_resolve_critical(curs->conn, 1); - } - else { - pq_resolve_critical(curs->conn, 0); - } - return -1; + return pq_resolve_critical(curs->conn, ex == -1 ? 1 : 0); } return ex; diff --git a/psycopg/pqpath.h b/psycopg/pqpath.h index bf012ade..a8f39c18 100644 --- a/psycopg/pqpath.h +++ b/psycopg/pqpath.h @@ -35,18 +35,18 @@ /* exported functions */ HIDDEN PGresult *pq_get_last_result(connectionObject *conn); -HIDDEN int pq_fetch(cursorObject *curs); -HIDDEN int pq_execute(cursorObject *curs, const char *query, int async); +RAISES_NEG HIDDEN int pq_fetch(cursorObject *curs); +RAISES_NEG HIDDEN int pq_execute(cursorObject *curs, const char *query, int async); HIDDEN int pq_send_query(connectionObject *conn, const char *query); HIDDEN int pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error, PyThreadState **tstate); HIDDEN int pq_commit(connectionObject *conn); -HIDDEN int pq_abort_locked(connectionObject *conn, PGresult **pgres, +RAISES_NEG HIDDEN int pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error, PyThreadState **tstate); -HIDDEN int pq_abort(connectionObject *conn); +RAISES_NEG HIDDEN int pq_abort(connectionObject *conn); HIDDEN int pq_reset_locked(connectionObject *conn, PGresult **pgres, char **error, PyThreadState **tstate); -HIDDEN int pq_reset(connectionObject *conn); +RAISES_NEG HIDDEN int pq_reset(connectionObject *conn); HIDDEN char *pq_get_guc_locked(connectionObject *conn, const char *param, PGresult **pgres, char **error, PyThreadState **tstate); @@ -61,7 +61,7 @@ HIDDEN int pq_is_busy(connectionObject *conn); HIDDEN int pq_is_busy_locked(connectionObject *conn); HIDDEN int pq_flush(connectionObject *conn); HIDDEN void pq_clear_async(connectionObject *conn); -HIDDEN int pq_set_non_blocking(connectionObject *conn, int arg, int pyerr); +RAISES_NEG HIDDEN int pq_set_non_blocking(connectionObject *conn, int arg); HIDDEN void pq_set_critical(connectionObject *conn, const char *msg); @@ -69,7 +69,7 @@ HIDDEN int pq_execute_command_locked(connectionObject *conn, const char *query, PGresult **pgres, char **error, PyThreadState **tstate); -HIDDEN void pq_complete_error(connectionObject *conn, PGresult **pgres, +RAISES HIDDEN void pq_complete_error(connectionObject *conn, PGresult **pgres, char **error); #endif /* !defined(PSYCOPG_PQPATH_H) */ diff --git a/psycopg/psycopg.h b/psycopg/psycopg.h index 2f06c378..2e86dca8 100644 --- a/psycopg/psycopg.h +++ b/psycopg/psycopg.h @@ -120,17 +120,19 @@ HIDDEN PyObject *psyco_GetDecimalType(void); typedef struct cursorObject cursorObject; /* some utility functions */ -HIDDEN void psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, +RAISES HIDDEN void psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, const char *pgerror, const char *pgcode); HIDDEN char *psycopg_escape_string(PyObject *conn, const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen); HIDDEN char *psycopg_escape_identifier_easy(const char *from, Py_ssize_t len); -HIDDEN char *psycopg_strdup(const char *from, Py_ssize_t len); -HIDDEN PyObject * psycopg_ensure_bytes(PyObject *obj); -HIDDEN PyObject * psycopg_ensure_text(PyObject *obj); +HIDDEN int psycopg_strdup(char **to, const char *from, Py_ssize_t len); HIDDEN int psycopg_is_text_file(PyObject *f); +STEALS(1) HIDDEN PyObject * psycopg_ensure_bytes(PyObject *obj); + +STEALS(1) HIDDEN PyObject * psycopg_ensure_text(PyObject *obj); + /* Exceptions docstrings */ #define Error_doc \ "Base class for error exceptions." diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 3b2b0609..3328c609 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -143,14 +143,6 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) " * `name`: Name for the new type\n" \ " * `baseobj`: Adapter to perform type conversion of a single array item." -static void -_psyco_register_type_set(PyObject **dict, PyObject *type) -{ - if (*dict == NULL) - *dict = PyDict_New(); - typecast_add(type, *dict, 0); -} - static PyObject * psyco_register_type(PyObject *self, PyObject *args) { @@ -162,10 +154,16 @@ psyco_register_type(PyObject *self, PyObject *args) if (obj != NULL && obj != Py_None) { if (PyObject_TypeCheck(obj, &cursorType)) { - _psyco_register_type_set(&(((cursorObject*)obj)->string_types), type); + PyObject **dict = &(((cursorObject*)obj)->string_types); + if (*dict == NULL) { + if (!(*dict = PyDict_New())) { return NULL; } + } + if (0 > typecast_add(type, *dict, 0)) { return NULL; } } else if (PyObject_TypeCheck(obj, &connectionType)) { - typecast_add(type, ((connectionObject*)obj)->string_types, 0); + if (0 > typecast_add(type, ((connectionObject*)obj)->string_types, 0)) { + return NULL; + } } else { PyErr_SetString(PyExc_TypeError, @@ -174,7 +172,7 @@ psyco_register_type(PyObject *self, PyObject *args) } } else { - typecast_add(type, NULL, 0); + if (0 > typecast_add(type, NULL, 0)) { return NULL; } } Py_INCREF(Py_None); @@ -182,66 +180,108 @@ psyco_register_type(PyObject *self, PyObject *args) } -/* default adapters */ - -static void +/* Initialize the default adapters map + * + * Return 0 on success, else -1 and set an exception. + */ +static int psyco_adapters_init(PyObject *mod) { - PyObject *call; + PyObject *call = NULL; + int rv = -1; - microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType); + if (0 != microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType)) { + goto exit; + } #if PY_MAJOR_VERSION < 3 - microprotocols_add(&PyInt_Type, NULL, (PyObject*)&pintType); + if (0 != microprotocols_add(&PyInt_Type, NULL, (PyObject*)&pintType)) { + goto exit; + } #endif - microprotocols_add(&PyLong_Type, NULL, (PyObject*)&pintType); - microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType); + if (0 != microprotocols_add(&PyLong_Type, NULL, (PyObject*)&pintType)) { + goto exit; + } + if (0 != microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType)) { + goto exit; + } /* strings */ #if PY_MAJOR_VERSION < 3 - microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType); + if (0 != microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType)) { + goto exit; + } #endif - microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType); + if (0 != microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType)) { + goto exit; + } /* binary */ #if PY_MAJOR_VERSION < 3 - microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType); + if (0 != microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } #else - microprotocols_add(&PyBytes_Type, NULL, (PyObject*)&binaryType); + if (0 != microprotocols_add(&PyBytes_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } #endif #if PY_MAJOR_VERSION >= 3 || PY_MINOR_VERSION >= 6 - microprotocols_add(&PyByteArray_Type, NULL, (PyObject*)&binaryType); + if (0 != microprotocols_add(&PyByteArray_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } #endif #if PY_MAJOR_VERSION >= 3 || PY_MINOR_VERSION >= 7 - microprotocols_add(&PyMemoryView_Type, NULL, (PyObject*)&binaryType); + if (0 != microprotocols_add(&PyMemoryView_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } #endif - microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType); + if (0 != microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType)) { + goto exit; + } /* the module has already been initialized, so we can obtain the callable objects directly from its dictionary :) */ - call = PyMapping_GetItemString(mod, "DateFromPy"); - microprotocols_add(PyDateTimeAPI->DateType, NULL, call); - call = PyMapping_GetItemString(mod, "TimeFromPy"); - microprotocols_add(PyDateTimeAPI->TimeType, NULL, call); - call = PyMapping_GetItemString(mod, "TimestampFromPy"); - microprotocols_add(PyDateTimeAPI->DateTimeType, NULL, call); - call = PyMapping_GetItemString(mod, "IntervalFromPy"); - microprotocols_add(PyDateTimeAPI->DeltaType, NULL, call); + if (!(call = PyMapping_GetItemString(mod, "DateFromPy"))) { goto exit; } + if (0 != microprotocols_add(PyDateTimeAPI->DateType, NULL, call)) { goto exit; } + Py_CLEAR(call); + + if (!(call = PyMapping_GetItemString(mod, "TimeFromPy"))) { goto exit; } + if (0 != microprotocols_add(PyDateTimeAPI->TimeType, NULL, call)) { goto exit; } + Py_CLEAR(call); + + if (!(call = PyMapping_GetItemString(mod, "TimestampFromPy"))) { goto exit; } + if (0 != microprotocols_add(PyDateTimeAPI->DateTimeType, NULL, call)) { goto exit; } + Py_CLEAR(call); + + if (!(call = PyMapping_GetItemString(mod, "IntervalFromPy"))) { goto exit; } + if (0 != microprotocols_add(PyDateTimeAPI->DeltaType, NULL, call)) { goto exit; } + Py_CLEAR(call); #ifdef HAVE_MXDATETIME /* as above, we use the callable objects from the psycopg module */ if (NULL != (call = PyMapping_GetItemString(mod, "TimestampFromMx"))) { - microprotocols_add(mxDateTime.DateTime_Type, NULL, call); + if (0 != microprotocols_add(mxDateTime.DateTime_Type, NULL, call)) { goto exit; } + Py_CLEAR(call); /* if we found the above, we have this too. */ - call = PyMapping_GetItemString(mod, "TimeFromMx"); - microprotocols_add(mxDateTime.DateTimeDelta_Type, NULL, call); + if (!(call = PyMapping_GetItemString(mod, "TimeFromMx"))) { goto exit; } + if (0 != microprotocols_add(mxDateTime.DateTimeDelta_Type, NULL, call)) { goto exit; } + Py_CLEAR(call); } else { PyErr_Clear(); } #endif + + /* Success! */ + rv = 0; + +exit: + Py_XDECREF(call); + + return rv; } /* psyco_encodings_fill @@ -326,15 +366,27 @@ static encodingPair encodings[] = { {NULL, NULL} }; -static void psyco_encodings_fill(PyObject *dict) +/* Initialize the encodings table. + * + * Return 0 on success, else -1 and set an exception. + */ +static int psyco_encodings_fill(PyObject *dict) { + PyObject *value = NULL; encodingPair *enc; + int rv = -1; for (enc = encodings; enc->pgenc != NULL; enc++) { - PyObject *value = Text_FromUTF8(enc->pyenc); - PyDict_SetItemString(dict, enc->pgenc, value); - Py_DECREF(value); + if (!(value = Text_FromUTF8(enc->pyenc))) { goto exit; } + if (0 != PyDict_SetItemString(dict, enc->pgenc, value)) { goto exit; } + Py_CLEAR(value); } + rv = 0; + +exit: + Py_XDECREF(value); + + return rv; } /* psyco_errors_init, psyco_errors_fill (callable from C) @@ -380,7 +432,59 @@ static struct { {NULL} /* Sentinel */ }; -static void + +/* Error.__reduce_ex__ + * + * The method is required to make exceptions picklable: set the cursor + * attribute to None. Only working from Py 2.5: previous versions + * would require implementing __getstate__, and as of 2012 it's a little + * bit too late to care. */ +static PyObject * +psyco_error_reduce_ex(PyObject *self, PyObject *args) +{ + PyObject *proto = NULL; + PyObject *super = NULL; + PyObject *tuple = NULL; + PyObject *dict = NULL; + PyObject *rv = NULL; + + /* tuple = Exception.__reduce_ex__(self, proto) */ + if (!PyArg_ParseTuple(args, "O", &proto)) { + goto error; + } + if (!(super = PyObject_GetAttrString(PyExc_Exception, "__reduce_ex__"))) { + goto error; + } + if (!(tuple = PyObject_CallFunctionObjArgs(super, self, proto, NULL))) { + goto error; + } + + /* tuple[2]['cursor'] = None + * + * If these checks fail, we can still return a valid object. Pickle + * will likely fail downstream, but there's nothing else we can do here */ + if (!PyTuple_Check(tuple)) { goto exit; } + if (3 > PyTuple_GET_SIZE(tuple)) { goto exit; } + dict = PyTuple_GET_ITEM(tuple, 2); /* borrowed */ + if (!PyDict_Check(dict)) { goto exit; } + + /* Modify the tuple inplace and return it */ + if (0 != PyDict_SetItemString(dict, "cursor", Py_None)) { + goto error; + } + +exit: + rv = tuple; + tuple = NULL; + +error: + Py_XDECREF(tuple); + Py_XDECREF(super); + + return rv; +} + +static int psyco_errors_init(void) { /* the names of the exceptions here reflect the oranization of the @@ -388,16 +492,22 @@ psyco_errors_init(void) live in _psycopg */ int i; - PyObject *dict; + PyObject *dict = NULL; PyObject *base; - PyObject *str; + PyObject *str = NULL; + PyObject *descr = NULL; + int rv = -1; + + static PyMethodDef psyco_error_reduce_ex_def = + {"__reduce_ex__", psyco_error_reduce_ex, METH_VARARGS, "pickle helper"}; for (i=0; exctable[i].name; i++) { - dict = PyDict_New(); + if (!(dict = PyDict_New())) { goto exit; } if (exctable[i].docstr) { - str = Text_FromUTF8(exctable[i].docstr); - PyDict_SetItemString(dict, "__doc__", str); + if (!(str = Text_FromUTF8(exctable[i].docstr))) { goto exit; } + if (0 != PyDict_SetItemString(dict, "__doc__", str)) { goto exit; } + Py_CLEAR(str); } if (exctable[i].base == 0) { @@ -411,7 +521,11 @@ psyco_errors_init(void) else base = *exctable[i].base; - *exctable[i].exc = PyErr_NewException(exctable[i].name, base, dict); + if (!(*exctable[i].exc = PyErr_NewException( + exctable[i].name, base, dict))) { + goto exit; + } + Py_CLEAR(dict); } /* Make pgerror, pgcode and cursor default to None on psycopg @@ -420,53 +534,71 @@ psyco_errors_init(void) PyObject_SetAttrString(Error, "pgerror", Py_None); PyObject_SetAttrString(Error, "pgcode", Py_None); PyObject_SetAttrString(Error, "cursor", Py_None); + + /* install __reduce_ex__ on Error to make all the subclasses picklable. + * + * Don't install it on Py 2.4: it is not used by the pickle + * protocol, and if called manually fails in an unsettling way, + * probably because the exceptions were old-style classes. */ +#if PY_VERSION_HEX >= 0x02050000 + if (!(descr = PyDescr_NewMethod((PyTypeObject *)Error, + &psyco_error_reduce_ex_def))) { + goto exit; + } + if (0 != PyObject_SetAttrString(Error, + psyco_error_reduce_ex_def.ml_name, descr)) { + goto exit; + } +#endif + + rv = 0; + +exit: + Py_XDECREF(descr); + Py_XDECREF(str); + Py_XDECREF(dict); + return rv; } void psyco_errors_fill(PyObject *dict) { - PyDict_SetItemString(dict, "Error", Error); - PyDict_SetItemString(dict, "Warning", Warning); - PyDict_SetItemString(dict, "InterfaceError", InterfaceError); - PyDict_SetItemString(dict, "DatabaseError", DatabaseError); - PyDict_SetItemString(dict, "InternalError", InternalError); - PyDict_SetItemString(dict, "OperationalError", OperationalError); - PyDict_SetItemString(dict, "ProgrammingError", ProgrammingError); - PyDict_SetItemString(dict, "IntegrityError", IntegrityError); - PyDict_SetItemString(dict, "DataError", DataError); - PyDict_SetItemString(dict, "NotSupportedError", NotSupportedError); -#ifdef PSYCOPG_EXTENSIONS - PyDict_SetItemString(dict, "QueryCanceledError", QueryCanceledError); - PyDict_SetItemString(dict, "TransactionRollbackError", - TransactionRollbackError); -#endif + int i; + char *name; + + for (i = 0; exctable[i].name; i++) { + if (NULL == exctable[i].exc) { continue; } + + /* the name is the part after the last dot */ + name = strrchr(exctable[i].name, '.'); + name = name ? name + 1 : exctable[i].name; + + PyDict_SetItemString(dict, name, *exctable[i].exc); + } } void psyco_errors_set(PyObject *type) { - PyObject_SetAttrString(type, "Error", Error); - PyObject_SetAttrString(type, "Warning", Warning); - PyObject_SetAttrString(type, "InterfaceError", InterfaceError); - PyObject_SetAttrString(type, "DatabaseError", DatabaseError); - PyObject_SetAttrString(type, "InternalError", InternalError); - PyObject_SetAttrString(type, "OperationalError", OperationalError); - PyObject_SetAttrString(type, "ProgrammingError", ProgrammingError); - PyObject_SetAttrString(type, "IntegrityError", IntegrityError); - PyObject_SetAttrString(type, "DataError", DataError); - PyObject_SetAttrString(type, "NotSupportedError", NotSupportedError); -#ifdef PSYCOPG_EXTENSIONS - PyObject_SetAttrString(type, "QueryCanceledError", QueryCanceledError); - PyObject_SetAttrString(type, "TransactionRollbackError", - TransactionRollbackError); -#endif + int i; + char *name; + + for (i = 0; exctable[i].name; i++) { + if (NULL == exctable[i].exc) { continue; } + + /* the name is the part after the last dot */ + name = strrchr(exctable[i].name, '.'); + name = name ? name + 1 : exctable[i].name; + + PyObject_SetAttrString(type, name, *exctable[i].exc); + } } -/* psyco_error_new +/* psyco_set_error Create a new error of the given type with extra attributes. */ -void +RAISES void psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, const char *pgerror, const char *pgcode) { @@ -495,15 +627,17 @@ psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, } if (pgerror) { - t = conn_text_from_chars(conn, pgerror); - PyObject_SetAttrString(err, "pgerror", t); - Py_DECREF(t); + if ((t = conn_text_from_chars(conn, pgerror))) { + PyObject_SetAttrString(err, "pgerror", t); + Py_DECREF(t); + } } if (pgcode) { - t = conn_text_from_chars(conn, pgcode); - PyObject_SetAttrString(err, "pgcode", t); - Py_DECREF(t); + if ((t = conn_text_from_chars(conn, pgcode))) { + PyObject_SetAttrString(err, "pgcode", t); + Py_DECREF(t); + } } PyErr_SetObject(exc, err); @@ -570,7 +704,7 @@ psyco_GetDecimalType(void) } /* Store the object from future uses. */ - if (can_cache && !cachedType) { + if (can_cache && !cachedType && decimalType) { Py_INCREF(decimalType); cachedType = decimalType; } @@ -594,15 +728,11 @@ psyco_make_description_type(void) /* Try to import collections.namedtuple */ if (!(coll = PyImport_ImportModule("collections"))) { Dprintf("psyco_make_description_type: collections import failed"); - PyErr_Clear(); - rv = Py_None; - goto exit; + goto error; } if (!(nt = PyObject_GetAttrString(coll, "namedtuple"))) { Dprintf("psyco_make_description_type: no collections.namedtuple"); - PyErr_Clear(); - rv = Py_None; - goto exit; + goto error; } /* Build the namedtuple */ @@ -614,6 +744,13 @@ exit: Py_XDECREF(nt); return rv; + +error: + /* controlled error: we will fall back to regular tuples. Return None. */ + PyErr_Clear(); + rv = Py_None; + Py_INCREF(rv); + goto exit; } @@ -826,10 +963,10 @@ INIT_MODULE(_psycopg)(void) #endif /* other mixed initializations of module-level variables */ - psycoEncodings = PyDict_New(); - psyco_encodings_fill(psycoEncodings); + if (!(psycoEncodings = PyDict_New())) { goto exit; } + if (0 != psyco_encodings_fill(psycoEncodings)) { goto exit; } psyco_null = Bytes_FromString("NULL"); - psyco_DescriptionType = psyco_make_description_type(); + if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; } /* set some module's parameters */ PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION); @@ -862,14 +999,14 @@ INIT_MODULE(_psycopg)(void) } #endif /* initialize default set of typecasters */ - typecast_init(dict); + if (0 != typecast_init(dict)) { goto exit; } /* initialize microprotocols layer */ microprotocols_init(dict); - psyco_adapters_init(dict); + if (0 != psyco_adapters_init(dict)) { goto exit; } /* create a standard set of exceptions and add them to the module's dict */ - psyco_errors_init(); + if (0 != psyco_errors_init()) { goto exit; } psyco_errors_fill(dict); /* Solve win32 build issue about non-constant initializer element */ diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 8ede351c..8504631b 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -250,34 +250,28 @@ PyObject *psyco_default_binary_cast; /* typecast_init - initialize the dictionary and create default types */ -int +RAISES_NEG int typecast_init(PyObject *dict) { int i; + int rv = -1; + typecastObject *t = NULL; /* create type dictionary and put it in module namespace */ - psyco_types = PyDict_New(); - psyco_binary_types = PyDict_New(); - - if (!psyco_types || !psyco_binary_types) { - Py_XDECREF(psyco_types); - Py_XDECREF(psyco_binary_types); - return -1; - } - + if (!(psyco_types = PyDict_New())) { goto exit; } PyDict_SetItemString(dict, "string_types", psyco_types); + + if (!(psyco_binary_types = PyDict_New())) { goto exit; } PyDict_SetItemString(dict, "binary_types", psyco_binary_types); /* insert the cast types into the 'types' dictionary and register them in the module dictionary */ for (i = 0; typecast_builtins[i].name != NULL; i++) { - typecastObject *t; - Dprintf("typecast_init: initializing %s", typecast_builtins[i].name); t = (typecastObject *)typecast_from_c(&(typecast_builtins[i]), dict); - if (t == NULL) return -1; - if (typecast_add((PyObject *)t, NULL, 0) != 0) return -1; + if (t == NULL) { goto exit; } + if (typecast_add((PyObject *)t, NULL, 0) < 0) { goto exit; } PyDict_SetItem(dict, t->name, (PyObject *)t); @@ -285,6 +279,8 @@ typecast_init(PyObject *dict) if (typecast_builtins[i].values == typecast_BINARY_types) { psyco_default_binary_cast = (PyObject *)t; } + Py_DECREF((PyObject *)t); + t = NULL; } /* create and save a default cast object (but does not register it) */ @@ -294,29 +290,35 @@ typecast_init(PyObject *dict) #ifdef HAVE_MXDATETIME if (0 == psyco_typecast_mxdatetime_init()) { for (i = 0; typecast_mxdatetime[i].name != NULL; i++) { - typecastObject *t; Dprintf("typecast_init: initializing %s", typecast_mxdatetime[i].name); t = (typecastObject *)typecast_from_c(&(typecast_mxdatetime[i]), dict); - if (t == NULL) return -1; + if (t == NULL) { goto exit; } PyDict_SetItem(dict, t->name, (PyObject *)t); + Py_DECREF((PyObject *)t); + t = NULL; } } #endif - if (psyco_typecast_datetime_init()) { return -1; } + if (0 > psyco_typecast_datetime_init()) { goto exit; } for (i = 0; typecast_pydatetime[i].name != NULL; i++) { - typecastObject *t; Dprintf("typecast_init: initializing %s", typecast_pydatetime[i].name); t = (typecastObject *)typecast_from_c(&(typecast_pydatetime[i]), dict); - if (t == NULL) return -1; + if (t == NULL) { goto exit; } PyDict_SetItem(dict, t->name, (PyObject *)t); + Py_DECREF((PyObject *)t); + t = NULL; } - return 0; + rv = 0; + +exit: + Py_XDECREF((PyObject *)t); + return rv; } /* typecast_add - add a type object to the dictionary */ -int +RAISES_NEG int typecast_add(PyObject *obj, PyObject *dict, int binary) { PyObject *val; @@ -466,7 +468,7 @@ typecast_repr(PyObject *self) static PyObject * typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs) { - char *string; + const char *string; Py_ssize_t length; PyObject *cursor; diff --git a/psycopg/typecast.h b/psycopg/typecast.h index 20def306..04976f31 100644 --- a/psycopg/typecast.h +++ b/psycopg/typecast.h @@ -71,8 +71,8 @@ extern HIDDEN PyObject *psyco_default_binary_cast; /** exported functions **/ /* used by module.c to init the type system and register types */ -HIDDEN int typecast_init(PyObject *dict); -HIDDEN int typecast_add(PyObject *obj, PyObject *dict, int binary); +RAISES_NEG HIDDEN int typecast_init(PyObject *dict); +RAISES_NEG HIDDEN int typecast_add(PyObject *obj, PyObject *dict, int binary); /* the C callable typecastObject creator function */ HIDDEN PyObject *typecast_from_c(typecastObject_initlist *type, PyObject *d); diff --git a/psycopg/typecast_array.c b/psycopg/typecast_array.c index 75b84b50..b5f1062c 100644 --- a/psycopg/typecast_array.c +++ b/psycopg/typecast_array.c @@ -166,7 +166,7 @@ typecast_array_tokenize(const char *str, Py_ssize_t strlength, return res; } -static int +RAISES_NEG static int typecast_array_scan(const char *str, Py_ssize_t strlength, PyObject *curs, PyObject *base, PyObject *array) { @@ -199,7 +199,7 @@ typecast_array_scan(const char *str, Py_ssize_t strlength, /* before anything else we free the memory */ if (state == ASCAN_QUOTED) PyMem_Free(token); - if (obj == NULL) return 0; + if (obj == NULL) return -1; PyList_Append(array, obj); Py_DECREF(obj); @@ -207,25 +207,25 @@ typecast_array_scan(const char *str, Py_ssize_t strlength, else if (state == ASCAN_BEGIN) { PyObject *sub = PyList_New(0); - if (sub == NULL) return 0; + if (sub == NULL) return -1; PyList_Append(array, sub); Py_DECREF(sub); if (stack_index == MAX_DIMENSIONS) - return 0; + return -1; stack[stack_index++] = array; array = sub; } else if (state == ASCAN_ERROR) { - return 0; + return -1; } else if (state == ASCAN_END) { if (--stack_index < 0) - return 0; + return -1; array = stack[stack_index]; } @@ -233,7 +233,7 @@ typecast_array_scan(const char *str, Py_ssize_t strlength, break; } - return 1; + return 0; } @@ -260,12 +260,11 @@ typecast_GENERIC_ARRAY_cast(const char *str, Py_ssize_t len, PyObject *curs) Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s'," " len = " FORMAT_CODE_PY_SSIZE_T, str, len); - obj = PyList_New(0); + if (!(obj = PyList_New(0))) { return NULL; } /* scan the array skipping the first level of {} */ - if (typecast_array_scan(&str[1], len-2, curs, base, obj) == 0) { - Py_DECREF(obj); - obj = NULL; + if (typecast_array_scan(&str[1], len-2, curs, base, obj) < 0) { + Py_CLEAR(obj); } return obj; diff --git a/psycopg/typecast_basic.c b/psycopg/typecast_basic.c index 20c7b81f..5e2a93ea 100644 --- a/psycopg/typecast_basic.c +++ b/psycopg/typecast_basic.c @@ -65,7 +65,7 @@ typecast_FLOAT_cast(const char *s, Py_ssize_t len, PyObject *curs) PyObject *str = NULL, *flo = NULL; if (s == NULL) {Py_INCREF(Py_None); return Py_None;} - str = Text_FromUTF8AndSize(s, len); + if (!(str = Text_FromUTF8AndSize(s, len))) { return NULL; } #if PY_MAJOR_VERSION < 3 flo = PyFloat_FromString(str, NULL); #else diff --git a/psycopg/typecast_binary.c b/psycopg/typecast_binary.c index b145b1b7..49eb547f 100644 --- a/psycopg/typecast_binary.c +++ b/psycopg/typecast_binary.c @@ -208,7 +208,7 @@ static const char hex_lut[128] = { /* Parse a bytea output buffer encoded in 'hex' format. * * the format is described in - * http://www.postgresql.org/docs/9.0/static/datatype-binary.html + * http://www.postgresql.org/docs/current/static/datatype-binary.html * * Parse the buffer in 'bufin', whose length is 'sizein'. * Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size. @@ -258,7 +258,7 @@ exit: /* Parse a bytea output buffer encoded in 'escape' format. * * the format is described in - * http://www.postgresql.org/docs/9.0/static/datatype-binary.html + * http://www.postgresql.org/docs/current/static/datatype-binary.html * * Parse the buffer in 'bufin', whose length is 'sizein'. * Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size. diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c index 29149c05..18e9d0d8 100644 --- a/psycopg/typecast_datetime.c +++ b/psycopg/typecast_datetime.c @@ -26,7 +26,7 @@ #include #include "datetime.h" -static int +RAISES_NEG static int psyco_typecast_datetime_init(void) { Dprintf("psyco_typecast_datetime_init: datetime init"); @@ -239,7 +239,7 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - v = v*10 + (double)*str - (double)'0'; + v = v * 10.0 + (double)(*str - '0'); if (part == 6){ denominator *= 10; } diff --git a/psycopg/typecast_mxdatetime.c b/psycopg/typecast_mxdatetime.c index e61224df..bf69af5a 100644 --- a/psycopg/typecast_mxdatetime.c +++ b/psycopg/typecast_mxdatetime.c @@ -142,7 +142,7 @@ typecast_MXINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - v = v*10 + (double)*str - (double)'0'; + v = v * 10.0 + (double)(*str - '0'); Dprintf("typecast_MXINTERVAL_cast: v = %f", v); if (part == 6){ denominator *= 10; diff --git a/psycopg/utils.c b/psycopg/utils.c index 2e81c113..57586c57 100644 --- a/psycopg/utils.c +++ b/psycopg/utils.c @@ -113,20 +113,19 @@ psycopg_escape_identifier_easy(const char *from, Py_ssize_t len) * Allocate a new buffer on the Python heap containing the new string. * 'len' is optional: if 0 the length is calculated. * - * Return NULL and set an exception in case of error. + * Store the return in 'to' and return 0 in case of success, else return -1 + * and raise an exception. */ -char * -psycopg_strdup(const char *from, Py_ssize_t len) +RAISES_NEG int +psycopg_strdup(char **to, const char *from, Py_ssize_t len) { - char *rv; - if (!len) { len = strlen(from); } - if (!(rv = PyMem_Malloc(len + 1))) { + if (!(*to = PyMem_Malloc(len + 1))) { PyErr_NoMemory(); - return NULL; + return -1; } - strcpy(rv, from); - return rv; + strcpy(*to, from); + return 0; } /* Ensure a Python object is a bytes string. @@ -139,7 +138,7 @@ psycopg_strdup(const char *from, Py_ssize_t len) * * It is safe to call the function on NULL. */ -PyObject * +STEALS(1) PyObject * psycopg_ensure_bytes(PyObject *obj) { PyObject *rv = NULL; @@ -169,7 +168,7 @@ psycopg_ensure_bytes(PyObject *obj) * The function is ref neutral: steals a ref from obj and adds one to the * return value. It is safe to call it on NULL. */ -PyObject * +STEALS(1) PyObject * psycopg_ensure_text(PyObject *obj) { #if PY_MAJOR_VERSION < 3 diff --git a/psycopg/xid_type.c b/psycopg/xid_type.c index 4de46b44..b28543ca 100644 --- a/psycopg/xid_type.c +++ b/psycopg/xid_type.c @@ -78,7 +78,9 @@ static PyMemberDef xid_members[] = { static PyObject * xid_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - XidObject *self = (XidObject *)type->tp_alloc(type, 0); + XidObject *self; + + if (!(self = (XidObject *)type->tp_alloc(type, 0))) { return NULL; } Py_INCREF(Py_None); self->format_id = Py_None; @@ -486,7 +488,7 @@ exit: * * Return a borrowed reference. */ -static PyObject * +BORROWED static PyObject * _xid_get_parse_regex(void) { static PyObject *rv; diff --git a/setup.cfg b/setup.cfg index 4d440a66..db4e771f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,10 +7,10 @@ define=PSYCOPG_EXTENSIONS,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM # PSYCOPG_DEBUG can be added to enable verbose debug information # PSYCOPG_NEW_BOOLEAN to format booleans as true/false vs 't'/'f' -# Set to 1 to use Python datatime objects for default date/time representation. +# Set to 1 to use Python datetime objects for default date/time representation. use_pydatetime=1 -# If the build system does not find the mx.DateTime headers, try +# If the build system does not find the mx.DateTime headers, try # uncommenting the following line and setting its value to the right path. #mx_include_dir= @@ -28,14 +28,14 @@ have_ssl=0 # set it to the pg_config full path. #pg_config= -# If "pg_config" is not available, "include_dirs" can be used to locate +# If "pg_config" is not available, "include_dirs" can be used to locate # postgresql headers and libraries. Some extra checks on sys.platform will # still be done in setup.py. # The next line is the default as used on psycopg author Debian laptop: #include_dirs=/usr/include/postgresql:/usr/include/postgresql/server # Uncomment next line on Mandrake 10.x (and comment previous ones): -#include_dirs=/usr/include/pgsql/8.0:/usr/include/pgsql/8.0/server +#include_dirs=/usr/include/pgsql/8.0:/usr/include/pgsql/8.0/server # Uncomment next line on SUSE 9.3 (and comment previous ones): #include_dirs=/usr/include/pgsql:/usr/include/pgsql/server diff --git a/setup.py b/setup.py index 91920b3e..e670874c 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ except ImportError: # Take a look at http://www.python.org/dev/peps/pep-0386/ # for a consistent versioning pattern. -PSYCOPG_VERSION = '2.4.4' +PSYCOPG_VERSION = '2.4.5' version_flags = ['dt', 'dec'] diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 59232a56..6231ed74 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -380,7 +380,9 @@ class DatabaseAPI20Test(unittest.TestCase): self.assertRaises(self.driver.Error,con.commit) # connection.close should raise an Error if called more than once - self.assertRaises(self.driver.Error,con.close) + # Issue discussed on DB-SIG: consensus seem that close() should not + # raised if called on closed objects. Issue reported back to Stuart. + # self.assertRaises(self.driver.Error,con.close) def test_execute(self): con = self._connect() diff --git a/tests/test_async.py b/tests/test_async.py index 07a3c42d..08113c4f 100755 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -435,9 +435,11 @@ class AsyncTests(unittest.TestCase): self.assert_(self.conn.notices) def test_async_cursor_gone(self): + import gc cur = self.conn.cursor() cur.execute("select 42;"); del cur + gc.collect() self.assertRaises(psycopg2.InterfaceError, self.wait, self.conn) # The connection is still usable diff --git a/tests/test_connection.py b/tests/test_connection.py index a887313a..07400fa4 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -48,6 +48,12 @@ class ConnectionTests(unittest.TestCase): conn.close() self.assertEqual(conn.closed, True) + def test_close_idempotent(self): + conn = self.conn + conn.close() + conn.close() + self.assert_(conn.closed) + def test_cursor_closed_attribute(self): conn = self.conn curs = conn.cursor() @@ -158,12 +164,43 @@ class ConnectionTests(unittest.TestCase): def test_weakref(self): from weakref import ref + import gc conn = psycopg2.connect(dsn) w = ref(conn) conn.close() del conn + gc.collect() self.assert_(w() is None) + def test_commit_concurrency(self): + # The problem is the one reported in ticket #103. Because of bad + # status check, we commit even when a commit is already on its way. + # We can detect this condition by the warnings. + conn = self.conn + notices = [] + stop = [] + + def committer(): + while not stop: + conn.commit() + while conn.notices: + notices.append((2, conn.notices.pop())) + + cur = conn.cursor() + t1 = threading.Thread(target=committer) + t1.start() + i = 1 + for i in range(1000): + cur.execute("select %s;",(i,)) + conn.commit() + while conn.notices: + notices.append((1, conn.notices.pop())) + + # Stop the committer thread + stop.append(True) + + self.assert_(not notices, "%d notices raised" % len(notices)) + class IsolationLevelsTestCase(unittest.TestCase): diff --git a/tests/test_cursor.py b/tests/test_cursor.py index aaeab139..051c9f27 100755 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -37,6 +37,12 @@ class CursorTests(unittest.TestCase): def tearDown(self): self.conn.close() + def test_close_idempotent(self): + cur = self.conn.cursor() + cur.close() + cur.close() + self.assert_(cur.closed) + def test_empty_query(self): cur = self.conn.cursor() self.assertRaises(psycopg2.ProgrammingError, cur.execute, "") @@ -234,6 +240,17 @@ class CursorTests(unittest.TestCase): # everything swallowed in two gulps self.assertEqual(rv, [(i,((i - 1) % 30) + 1) for i in range(1,51)]) + @skip_before_postgres(8, 0) + def test_iter_named_cursor_rownumber(self): + curs = self.conn.cursor('tmp') + # note: this fails if itersize < dataset: internally we check + # rownumber == rowcount to detect when to read anoter page, so we + # would need an extra attribute to have a monotonic rownumber. + curs.itersize = 20 + curs.execute('select generate_series(1,10)') + for i, rec in enumerate(curs): + self.assertEqual(i + 1, curs.rownumber) + @skip_if_no_namedtuple def test_namedtuple_description(self): curs = self.conn.cursor() @@ -284,6 +301,55 @@ class CursorTests(unittest.TestCase): self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3)) self.assertEqual([(5,), (6,), (7,)], cur2.fetchall()) + @skip_before_postgres(8, 0) + def test_scroll(self): + cur = self.conn.cursor() + cur.execute("select generate_series(0,9)") + cur.scroll(2) + self.assertEqual(cur.fetchone(), (2,)) + cur.scroll(2) + self.assertEqual(cur.fetchone(), (5,)) + cur.scroll(2, mode='relative') + self.assertEqual(cur.fetchone(), (8,)) + cur.scroll(-1) + self.assertEqual(cur.fetchone(), (8,)) + cur.scroll(-2) + self.assertEqual(cur.fetchone(), (7,)) + cur.scroll(2, mode='absolute') + self.assertEqual(cur.fetchone(), (2,)) + + # on the boundary + cur.scroll(0, mode='absolute') + self.assertEqual(cur.fetchone(), (0,)) + self.assertRaises((IndexError, psycopg2.ProgrammingError), + cur.scroll, -1, mode='absolute') + cur.scroll(0, mode='absolute') + self.assertRaises((IndexError, psycopg2.ProgrammingError), + cur.scroll, -1) + + cur.scroll(9, mode='absolute') + self.assertEqual(cur.fetchone(), (9,)) + self.assertRaises((IndexError, psycopg2.ProgrammingError), + cur.scroll, 10, mode='absolute') + cur.scroll(9, mode='absolute') + self.assertRaises((IndexError, psycopg2.ProgrammingError), + cur.scroll, 1) + + @skip_before_postgres(8, 0) + def test_scroll_named(self): + cur = self.conn.cursor() + cur.execute("select generate_series(0,9)") + cur.scroll(2) + self.assertEqual(cur.fetchone(), (2,)) + cur.scroll(2) + self.assertEqual(cur.fetchone(), (5,)) + cur.scroll(2, mode='relative') + self.assertEqual(cur.fetchone(), (8,)) + cur.scroll(9, mode='absolute') + self.assertEqual(cur.fetchone(), (9,)) + self.assertRaises((IndexError, psycopg2.ProgrammingError), + cur.scroll, 10, mode='absolute') + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_dates.py b/tests/test_dates.py index 394858ec..9be68a2c 100755 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -25,7 +25,7 @@ import math import unittest import psycopg2 -from psycopg2.tz import FixedOffsetTimezone +from psycopg2.tz import FixedOffsetTimezone, ZERO from testconfig import dsn class CommonDatetimeTestsMixin: @@ -78,7 +78,7 @@ class CommonDatetimeTestsMixin: value = self.DATETIME(None, self.curs) self.assertEqual(value, None) - def test_parse_incomplete_time(self): + def test_parse_incomplete_datetime(self): self.assertRaises(psycopg2.DataError, self.DATETIME, '2007', self.curs) self.assertRaises(psycopg2.DataError, @@ -513,6 +513,32 @@ class FromTicksTestCase(unittest.TestCase): time(0, 11, 59, 999920)) +class FixedOffsetTimezoneTests(unittest.TestCase): + + def test_init_with_no_args(self): + tzinfo = FixedOffsetTimezone() + self.assert_(tzinfo._offset is ZERO) + self.assert_(tzinfo._name is None) + + def test_repr_with_positive_offset(self): + tzinfo = FixedOffsetTimezone(5 * 60) + self.assertEqual(repr(tzinfo), "psycopg2.tz.FixedOffsetTimezone(offset=300, name=None)") + + def test_repr_with_negative_offset(self): + tzinfo = FixedOffsetTimezone(-5 * 60) + self.assertEqual(repr(tzinfo), "psycopg2.tz.FixedOffsetTimezone(offset=-300, name=None)") + + def test_repr_with_name(self): + tzinfo = FixedOffsetTimezone(name="FOO") + self.assertEqual(repr(tzinfo), "psycopg2.tz.FixedOffsetTimezone(offset=0, name='FOO')") + + def test_instance_caching(self): + self.assert_(FixedOffsetTimezone(name="FOO") is FixedOffsetTimezone(name="FOO")) + self.assert_(FixedOffsetTimezone(7 * 60) is FixedOffsetTimezone(7 * 60)) + self.assert_(FixedOffsetTimezone(-9 * 60, 'FOO') is FixedOffsetTimezone(-9 * 60, 'FOO')) + self.assert_(FixedOffsetTimezone(9 * 60) is not FixedOffsetTimezone(9 * 60, 'FOO')) + self.assert_(FixedOffsetTimezone(name='FOO') is not FixedOffsetTimezone(9 * 60, 'FOO')) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_extras_dictcursor.py b/tests/test_extras_dictcursor.py index 3346c114..dd746379 100755 --- a/tests/test_extras_dictcursor.py +++ b/tests/test_extras_dictcursor.py @@ -35,12 +35,16 @@ class ExtrasDictCursorTests(unittest.TestCase): def tearDown(self): self.conn.close() + def testDictCursorWithPlainCursorFetchOne(self): self._testWithPlainCursor(lambda curs: curs.fetchone()) def testDictCursorWithPlainCursorFetchMany(self): self._testWithPlainCursor(lambda curs: curs.fetchmany(100)[0]) + def testDictCursorWithPlainCursorFetchManyNoarg(self): + self._testWithPlainCursor(lambda curs: curs.fetchmany()[0]) + def testDictCursorWithPlainCursorFetchAll(self): self._testWithPlainCursor(lambda curs: curs.fetchall()[0]) @@ -50,12 +54,35 @@ class ExtrasDictCursorTests(unittest.TestCase): return row self._testWithPlainCursor(getter) + def testUpdateRow(self): + row = self._testWithPlainCursor(lambda curs: curs.fetchone()) + row['foo'] = 'qux' + self.failUnless(row['foo'] == 'qux') + self.failUnless(row[0] == 'qux') + + @skip_before_postgres(8, 0) + def testDictCursorWithPlainCursorIterRowNumber(self): + curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) + self._testIterRowNumber(curs) + + def _testWithPlainCursor(self, getter): + curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) + curs.execute("SELECT * FROM ExtrasDictCursorTests") + row = getter(curs) + self.failUnless(row['foo'] == 'bar') + self.failUnless(row[0] == 'bar') + return row + + def testDictCursorWithPlainCursorRealFetchOne(self): self._testWithPlainCursorReal(lambda curs: curs.fetchone()) def testDictCursorWithPlainCursorRealFetchMany(self): self._testWithPlainCursorReal(lambda curs: curs.fetchmany(100)[0]) + def testDictCursorWithPlainCursorRealFetchManyNoarg(self): + self._testWithPlainCursorReal(lambda curs: curs.fetchmany()[0]) + def testDictCursorWithPlainCursorRealFetchAll(self): self._testWithPlainCursorReal(lambda curs: curs.fetchall()[0]) @@ -65,6 +92,17 @@ class ExtrasDictCursorTests(unittest.TestCase): return row self._testWithPlainCursorReal(getter) + @skip_before_postgres(8, 0) + def testDictCursorWithPlainCursorRealIterRowNumber(self): + curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + self._testIterRowNumber(curs) + + def _testWithPlainCursorReal(self, getter): + curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + curs.execute("SELECT * FROM ExtrasDictCursorTests") + row = getter(curs) + self.failUnless(row['foo'] == 'bar') + def testDictCursorWithNamedCursorFetchOne(self): self._testWithNamedCursor(lambda curs: curs.fetchone()) @@ -72,6 +110,9 @@ class ExtrasDictCursorTests(unittest.TestCase): def testDictCursorWithNamedCursorFetchMany(self): self._testWithNamedCursor(lambda curs: curs.fetchmany(100)[0]) + def testDictCursorWithNamedCursorFetchManyNoarg(self): + self._testWithNamedCursor(lambda curs: curs.fetchmany()[0]) + def testDictCursorWithNamedCursorFetchAll(self): self._testWithNamedCursor(lambda curs: curs.fetchall()[0]) @@ -86,6 +127,18 @@ class ExtrasDictCursorTests(unittest.TestCase): curs = self.conn.cursor('tmp', cursor_factory=psycopg2.extras.DictCursor) self._testNamedCursorNotGreedy(curs) + @skip_before_postgres(8, 0) + def testDictCursorWithNamedCursorIterRowNumber(self): + curs = self.conn.cursor('tmp', cursor_factory=psycopg2.extras.DictCursor) + self._testIterRowNumber(curs) + + def _testWithNamedCursor(self, getter): + curs = self.conn.cursor('aname', cursor_factory=psycopg2.extras.DictCursor) + curs.execute("SELECT * FROM ExtrasDictCursorTests") + row = getter(curs) + self.failUnless(row['foo'] == 'bar') + self.failUnless(row[0] == 'bar') + def testDictCursorRealWithNamedCursorFetchOne(self): self._testWithNamedCursorReal(lambda curs: curs.fetchone()) @@ -93,6 +146,9 @@ class ExtrasDictCursorTests(unittest.TestCase): def testDictCursorRealWithNamedCursorFetchMany(self): self._testWithNamedCursorReal(lambda curs: curs.fetchmany(100)[0]) + def testDictCursorRealWithNamedCursorFetchManyNoarg(self): + self._testWithNamedCursorReal(lambda curs: curs.fetchmany()[0]) + def testDictCursorRealWithNamedCursorFetchAll(self): self._testWithNamedCursorReal(lambda curs: curs.fetchall()[0]) @@ -107,27 +163,10 @@ class ExtrasDictCursorTests(unittest.TestCase): curs = self.conn.cursor('tmp', cursor_factory=psycopg2.extras.RealDictCursor) self._testNamedCursorNotGreedy(curs) - - def _testWithPlainCursor(self, getter): - curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) - curs.execute("SELECT * FROM ExtrasDictCursorTests") - row = getter(curs) - self.failUnless(row['foo'] == 'bar') - self.failUnless(row[0] == 'bar') - return row - - def _testWithNamedCursor(self, getter): - curs = self.conn.cursor('aname', cursor_factory=psycopg2.extras.DictCursor) - curs.execute("SELECT * FROM ExtrasDictCursorTests") - row = getter(curs) - self.failUnless(row['foo'] == 'bar') - self.failUnless(row[0] == 'bar') - - def _testWithPlainCursorReal(self, getter): - curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) - curs.execute("SELECT * FROM ExtrasDictCursorTests") - row = getter(curs) - self.failUnless(row['foo'] == 'bar') + @skip_before_postgres(8, 0) + def testDictCursorRealWithNamedCursorIterRowNumber(self): + curs = self.conn.cursor('tmp', cursor_factory=psycopg2.extras.RealDictCursor) + self._testIterRowNumber(curs) def _testWithNamedCursorReal(self, getter): curs = self.conn.cursor('aname', cursor_factory=psycopg2.extras.RealDictCursor) @@ -135,11 +174,6 @@ class ExtrasDictCursorTests(unittest.TestCase): row = getter(curs) self.failUnless(row['foo'] == 'bar') - def testUpdateRow(self): - row = self._testWithPlainCursor(lambda curs: curs.fetchone()) - row['foo'] = 'qux' - self.failUnless(row['foo'] == 'qux') - self.failUnless(row[0] == 'qux') def _testNamedCursorNotGreedy(self, curs): curs.itersize = 2 @@ -153,6 +187,14 @@ class ExtrasDictCursorTests(unittest.TestCase): self.assert_(recs[1]['ts'] - recs[0]['ts'] < timedelta(seconds=0.005)) self.assert_(recs[2]['ts'] - recs[1]['ts'] > timedelta(seconds=0.0099)) + def _testIterRowNumber(self, curs): + # Only checking for dataset < itersize: + # see CursorTests.test_iter_named_cursor_rownumber + curs.itersize = 20 + curs.execute("""select * from generate_series(1,10)""") + for i, r in enumerate(curs): + self.assertEqual(i + 1, curs.rownumber) + class NamedTupleCursorTest(unittest.TestCase): def setUp(self): @@ -180,12 +222,28 @@ class NamedTupleCursorTest(unittest.TestCase): @skip_if_no_namedtuple def test_fetchone(self): curs = self.conn.cursor() - curs.execute("select * from nttest where i = 1") + curs.execute("select * from nttest order by 1") t = curs.fetchone() self.assertEqual(t[0], 1) self.assertEqual(t.i, 1) self.assertEqual(t[1], 'foo') self.assertEqual(t.s, 'foo') + self.assertEqual(curs.rownumber, 1) + self.assertEqual(curs.rowcount, 3) + + @skip_if_no_namedtuple + def test_fetchmany_noarg(self): + curs = self.conn.cursor() + curs.arraysize = 2 + curs.execute("select * from nttest order by 1") + res = curs.fetchmany() + self.assertEqual(2, len(res)) + self.assertEqual(res[0].i, 1) + self.assertEqual(res[0].s, 'foo') + self.assertEqual(res[1].i, 2) + self.assertEqual(res[1].s, 'bar') + self.assertEqual(curs.rownumber, 2) + self.assertEqual(curs.rowcount, 3) @skip_if_no_namedtuple def test_fetchmany(self): @@ -197,6 +255,8 @@ class NamedTupleCursorTest(unittest.TestCase): self.assertEqual(res[0].s, 'foo') self.assertEqual(res[1].i, 2) self.assertEqual(res[1].s, 'bar') + self.assertEqual(curs.rownumber, 2) + self.assertEqual(curs.rowcount, 3) @skip_if_no_namedtuple def test_fetchall(self): @@ -210,6 +270,8 @@ class NamedTupleCursorTest(unittest.TestCase): self.assertEqual(res[1].s, 'bar') self.assertEqual(res[2].i, 3) self.assertEqual(res[2].s, 'baz') + self.assertEqual(curs.rownumber, 3) + self.assertEqual(curs.rowcount, 3) @skip_if_no_namedtuple def test_executemany(self): @@ -227,16 +289,26 @@ class NamedTupleCursorTest(unittest.TestCase): curs = self.conn.cursor() curs.execute("select * from nttest order by 1") i = iter(curs) + self.assertEqual(curs.rownumber, 0) + t = i.next() self.assertEqual(t.i, 1) self.assertEqual(t.s, 'foo') + self.assertEqual(curs.rownumber, 1) + self.assertEqual(curs.rowcount, 3) + t = i.next() self.assertEqual(t.i, 2) self.assertEqual(t.s, 'bar') + self.assertEqual(curs.rownumber, 2) + self.assertEqual(curs.rowcount, 3) + t = i.next() self.assertEqual(t.i, 3) self.assertEqual(t.s, 'baz') self.assertRaises(StopIteration, i.next) + self.assertEqual(curs.rownumber, 3) + self.assertEqual(curs.rowcount, 3) def test_error_message(self): try: @@ -361,6 +433,17 @@ class NamedTupleCursorTest(unittest.TestCase): self.assert_(recs[1].ts - recs[0].ts < timedelta(seconds=0.005)) self.assert_(recs[2].ts - recs[1].ts > timedelta(seconds=0.0099)) + @skip_if_no_namedtuple + @skip_before_postgres(8, 0) + def test_named_rownumber(self): + curs = self.conn.cursor('tmp') + # Only checking for dataset < itersize: + # see CursorTests.test_iter_named_cursor_rownumber + curs.itersize = 4 + curs.execute("""select * from generate_series(1,3)""") + for i, t in enumerate(curs): + self.assertEqual(i + 1, curs.rownumber) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_lobject.py b/tests/test_lobject.py index d327bb96..9c1c44f2 100755 --- a/tests/test_lobject.py +++ b/tests/test_lobject.py @@ -25,13 +25,12 @@ import os import shutil import tempfile -from testutils import unittest, decorate_all_tests, skip_if_tpc_disabled import psycopg2 import psycopg2.extensions from psycopg2.extensions import b from testconfig import dsn, green -from testutils import unittest, decorate_all_tests +from testutils import unittest, decorate_all_tests, skip_if_tpc_disabled def skip_if_no_lo(f): def skip_if_no_lo_(self): diff --git a/tests/test_module.py b/tests/test_module.py index 9c130f3f..870dbbc2 100755 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -22,7 +22,8 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -from testutils import unittest +from testutils import unittest, skip_before_python +from testconfig import dsn import psycopg2 @@ -127,6 +128,40 @@ class ConnectTestCase(unittest.TestCase): self.assertEqual(self.args[0], r"dbname='\\every thing\''") +class ExceptionsTestCase(unittest.TestCase): + def setUp(self): + self.conn = psycopg2.connect(dsn) + + def tearDown(self): + self.conn.close() + + def test_attributes(self): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + e = exc + + self.assertEqual(e.pgcode, '42P01') + self.assert_(e.pgerror) + self.assert_(e.cursor is cur) + + @skip_before_python(2, 5) + def test_pickle(self): + import pickle + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + e = exc + + e1 = pickle.loads(pickle.dumps(e)) + + self.assertEqual(e.pgerror, e1.pgerror) + self.assertEqual(e.pgcode, e1.pgcode) + self.assert_(e1.cursor is None) + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_quote.py b/tests/test_quote.py index 4ee451f3..4b44a86a 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -45,8 +45,8 @@ class QuotingTestCase(unittest.TestCase): The tests also check that no warning is raised ('escape_string_warning' should be on). - http://www.postgresql.org/docs/8.1/static/sql-syntax.html#SQL-SYNTAX-STRINGS - http://www.postgresql.org/docs/8.1/static/runtime-config-compatible.html + http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS + http://www.postgresql.org/docs/current/static/runtime-config-compatible.html """ def setUp(self): self.conn = psycopg2.connect(dsn) diff --git a/tests/test_types_extras.py b/tests/test_types_extras.py index 29a935ee..36dd77ed 100755 --- a/tests/test_types_extras.py +++ b/tests/test_types_extras.py @@ -82,13 +82,22 @@ class TypesExtrasTests(unittest.TestCase): def testINET(self): psycopg2.extras.register_inet() - i = "192.168.1.0/24"; + i = psycopg2.extras.Inet("192.168.1.0/24") s = self.execute("SELECT %s AS foo", (i,)) - self.failUnless(i == s) + self.failUnless(i.addr == s.addr) # must survive NULL cast to inet s = self.execute("SELECT NULL::inet AS foo") self.failUnless(s is None) + def testINETARRAY(self): + psycopg2.extras.register_inet() + i = psycopg2.extras.Inet("192.168.1.0/24") + s = self.execute("SELECT %s AS foo", ([i],)) + self.failUnless(i.addr == s[0].addr) + # must survive NULL cast to inet + s = self.execute("SELECT NULL::inet[] AS foo") + self.failUnless(s is None) + def test_inet_conform(self): from psycopg2.extras import Inet i = Inet("192.168.1.0/24")