diff options
author | Federico Di Gregorio <fog@initd.org> | 2004-10-19 03:17:12 +0000 |
---|---|---|
committer | Federico Di Gregorio <fog@initd.org> | 2004-10-19 03:17:12 +0000 |
commit | c904d97f696a665958c2cc43333d09c0e6357577 (patch) | |
tree | de88cb1cb6a48230f79bc0b532835d26a33660e9 | |
download | psycopg2-c904d97f696a665958c2cc43333d09c0e6357577.tar.gz |
Initial psycopg 2 import after SVN crash.
89 files changed, 12644 insertions, 0 deletions
@@ -0,0 +1,8 @@ +Main authors: + Federico Di Gregorio <fog@debian.org> + +For the win32 port: + Jason Erickson <jerickso@indian.com> (most of his changes are still in 2.0) + +Additional Help: + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..0a56183 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,362 @@ +2004-10-14 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c (_psyco_curs_buildrow_fill): now we use + PySequence_SetItem to avoid problems with containers created from + cursor's .tuple_factory attribute. + + * lib/extras.py (DictCursor.execute): fixed stupid bug with cursor + setting self.tuplefactory instead of self.tuple_factory. + +2004-10-02 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.10. + + * psycopg/cursor_type.c (_psyco_curs_buildrow_*): unified normal + and factory code into the _psyco_curs_buildrow_fill function; no + more memory leaks here. + + * psycopg/config.h (round): added check for __FreeBSD__ (that + should be defined when compiling with gcc, I hope.) + + * setup.py: removed a lot of code now in setup.cfg. + +2004-09-24 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c (cursor_dealloc): fixed small memory leak + due to missing disposal of self->pgres. + +2004-9-14 Federico Di Gregorio <fog@initd.org> + + * examples/dialtone.py: Added adapt() example by Valentino + Volonghi. + +2004-09-14 Federico Di Gregorio <fog@debian.org> + + * psycopg/microprotocols.c (microprotocols_adapt): lots of changes + to the microprotocols layer (it is not micro anymore); + implementing almost all the PEP 246. The adapter registry is now + indexed by (type, protocol) and not by type alone. + +2004-09-13 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c (_mogrify): and qattr is gone. + +2004-09-05 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.9 (or, the "twisting by the pool" release). + + * psycopg/pqpath.c (_pq_fetch_tuples): changed to "static void" + instead of "static int", no ways for this function to fail. + +2004-09-04 Federico Di Gregorio <fog@debian.org> + + * psycopg/pqpath.c (_pq_fetch_tuples): ported rowcount fix from + 1.1.15. + + * ZPsycopgDA/*: ZPsycopgDA back in action, using the new pooling + code. + +2004-08-29 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_basic.c (typecast_DECIMAL_cast): added DECIMAL + typecaster; it even works :). + + * scripts/buildtypes.py (basic_types): added DECIMAL typecaster + for the NUMERIC oid. + + * examples/threads.py: updated threads example to use pooling code. + + * lib/pool.py: added very simple and thread-safe connection + pooling class. + + * psycopg/cursor_type.c (psyco_curs_fetchmany): fixed problem with + .fetchall() and .fetchmany() returning None instead of [] on empty + result sets. + + * Release 1.99.8. + +2004-08-28 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c (psyco_curs_execute): added processing of + unicode queries. + + * examples/encoding.py: much better encoding example, also using + the new UNICODE typecaster. + + * psycopg/typecast_basic.c (typecast_UNICODE_cast): added UNICODE + typecaster. + + * lib/extensions.py: the encodings dictionary is not available by + default but can be accessed from the psycopg.extensions module. + + * psycopg/adapter_qstring.h: remove encoding information from + qstring adapter and moved it into psycopg module. + +2004-08-26 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c (_psyco_curs_prefetch): added check for + asynchronous fetch by wrong cursor. + + * psycopg/pqpath.c (pq_fetch): fixed backend status message (bug + reported by Daniele Varrazzo.) + +2004-07-29 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_basic.c (typecast_BINARY_cast): reverted to + using strings instead of buffers when converting postgresql binary + objects (should *temporarily* fix corruption bug reported on + win32.) + +2004-07-21 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c: removed __iter__ and next methods from + object methods and moved them where they do belong (tp_iter and + tp_iternext.) Bug reported by Daniele Varrazzo (again!) + +2004-07-19 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_datetime.c (typecast_PYINTERVAL_cast): replaced + round() with micro() when rounding seconds (fixes bugs reported by + Daniele Varrazzo.) + +2004-07-16 Federico Di Gregorio <fog@debian.org> + + * psycopg/pqpath.c (pq_set_critical): allow for a custom message + insted of the one from PQerrorMessage. + (pq_resolve_critical): added argument to specify if connection is + to be closed (used to not close it during COPY FROM/TO criticals.) + + * psycopg/cursor_type.c (psyco_curs_fileno, psyco_curs_isready): + added extension methods related to async queries. + +2004-07-15 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.7. + + * examples/tz.py: added example about time zones. + + * psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): create + FixedOffsetTimezone for postgresql "timestamp with time zone" + types. + + * lib/tz.py: added (even more than) needed tzinfo classes. + + * psycopg/typecast.c (typecast_call): changed typecast call code + to take the additional cursor parameter, needed for + cursor-dependent type casting (tzinfo & friends.) + + * psycopg/cursor_type.c (_psyco_curs_buildrow_with_factory): added + use of tuple factories to fetcXXX methods. + + * lib/extras.py: little extra goodies for psycopg. + +2004-07-14 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.6. + + * psycopg/connection_type.c: added .dsn attribute to connection + objects. + + * psycopg/cursor_type.c (psyco_curs_mogrify): added .mogrify() + method. + + * psycopg/adapter_qstring.c: copy the connection encoding only if + wrapped object is unicode and added table of encodings. + +2004-07-13 Federico Di Gregorio <fog@debian.org> + + * psycopg/cursor_type.c (_mogrify): moved Dprintf statement to + avoid dereferencing empty pointer (from 1.1.x) + (psyco_curs_execute): now we save the query in self->query instead + of freeing the memory ASAP. + (cursorObject_members): and we finally export the saved query + through the cursor members interface. that's all folks. + + * lib/extensions.py: added extensions module to clearly separate + psycopg own extensions from DBAPI-2.0 + +2004-07-10 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_datetime.c: ported interval fix from 1.1.x. + +2004-05-16 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_datetime.c (typecast_*_cast): fixed Value error + when seconds > 59 by setting minutes += 1 and seconds -= 60 + (reported by Marcel Gsteiger.) + +2004-04-24 Federico Di Gregorio <fog@debian.org> + + * ported time interval patch by Ross Cohen from 1.1.12. + +2004-04-19 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_datetime.c (typecast_PYDATE_cast): applied + patch from Jason Erickson: min and max taken from datetime.Date + type. + +2004-04-18 Federico Di Gregorio <fog@debian.org> + + * Applied changes from Jason Erickson to build on win32; see his + (slightly edited) entry below. (Still builds on Linux :) + + * psycopg/*.c: removed inclusion of pthread.h from all files + except psycopg/config.h to build on win32 without faking the file. + +2004-04-15 Jason Erickson <jerickso@stickpeople.com> + + * setup.py: Various changes. The critical ones: + - Make an empty pthread.h file so all the code doing an + #include <pthread.h> will find something. + - Appended the winsock2 library and the PostgreSQL library to + the library path. + - Setup the include path. + - Have the PSYCOPG_VERSION macro be included with quotes. + + * config.h: Added/Cleaned up Win32 includes, defines, stub functions. + + * typecast.h: Removed ';' after PyObject_HEAD in the + typecastObject structure since Microsoft Visual Studio does not + like it. + +2004-04-15 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.5 (bug-fixing and reorganization) + + * setup.py et al.: moved psycopg to psycopg._psycopg to make + easier to provide high level python-only utilities (like the + promised pooling code). psycopg/__init__.py imports _psycopg and + make all the default DBAPI-2.0 stuff available. + +2004-04-14 Federico Di Gregorio <fog@debian.org> + + * psycopg/psycopgmodule.c (initpsycopg): wrapped initialization of + date/time adapters in #ifdefs to have psycopg compile without mx + or builtin datetime. + +2004-04-10 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.4. + +2004-04-09 Federico Di Gregorio <fog@debian.org> + + * psycopg/typecast_builtins.c: changed DATE to not include + DATETIME types anymore. + + * psycopg/adapter_datetime.c (pydatetime_str): switched from + strftime to isoformat to preserve fractional seconds. + +2004-04-08 Federico Di Gregorio <fog@debian.org> + + * psycopg/psycopgmodule.c (psyco_connect): ported sslmode + parameter from 1.1 branch. + + * psycopg/adapter_datetime.*: added python built-in datetime + adapters. also added the datetime typecasters (still using mx as + default). + + * psycopg/typecast.h: removed aliases, they now live in the right + typecast_xxx.c file. + +2004-03-08 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.3 (alpha 4). + + * examples/lastrowid.py: and the .lastrowid example is in. + + * psycopg/cursor_type.c (_mogrify): added call to .prepare() + method in both dict and sequence path. + + * psycopg/connection_int.c (conn_set_client_encoding): added + encoding-change code. + + * psycopg/adapter_qstring.c (qstring_quote): added hard-coded + support for utf8 and latin1 encodings. + +2004-03-01 Federico Di Gregorio <fog@debian.org> + + * psycopg/connection_int.c (conn_close): does not use libpq + functions on NULL pgconn (this can happen when conn_close is + called after a failed PQconnect.) + +2004-02-29 Federico Di Gregorio <fog@debian.org> + + * Release 1.99.2 (alpha 3). + + * psycopg/cursor_type.c: added .rownumber and .connection + attributes. Also added .scroll(), .next() and .__iter__() methods + (see DBAPI2-.0 extensions on PEP.) + + * psycopg/connection_type.c (psyco_conn_set_isolation_level): + added connection method .set_isolation_level(). Also added all + error objects to the connection (see DBAPI2-.0 extensions on PEP.) + + * psycopg/connection_int.c (conn_switch_isolation_level): added + isolation level switching code. + + * setup.py: removed all references to PSYCOPG_NEWSTYLE: support + for python < 2.2 has been dropped. + + * typecast_basic.c (typecast_BINARY_cast): now binary objects are + returned as true buffers. + + * adapter_binary.*: added adapter for buffers and binary (bytea) + objects. + + * Release 1.99.1 (alpha 2). + + * adapter_mxdatetime.*: added adapters for all mx.DateTime types. + +2004-02-28 Federico Di Gregorio <fog@debian.org> + + * cursor_type.c (_mogrify): complete rework of the mogrification + code to use the microprotocols_adapt function. + + * typecast_basic.c (typecast_BOOLEAN_cast): we now return real + Py_True and Py_False values. + + * microprotocols.h: added very simple microprotocols + implementation to allow for python->postgresql types registry. + +2004-01-05 Federico Di Gregorio <fog@debian.org> + + * connection_int.c (conn_commit/conn_rollback): added code to + commit/rollback and connection methods. + +2004-01-04 Federico Di Gregorio <fog@debian.org> + + * cursor_type.c (psyco_curs_fetchone): added fetchone method. + +2004-01-03 Federico Di Gregorio <fog@debian.org> + + * added (empty) INSTALL file. + + * cursor_type.c (cursor_dealloc): added qattr for custom object + quoting using a callable attribute. + (_mogrify): ported new, fixed mogrification code from 1.1.12. + +2003-08-01 Federico Di Gregorio <fog@debian.org> + + * cursor_type.c (_mogrify_sequence): added sequence mogrification, + can be done better, on the dict model. + +2003-07-28 Federico Di Gregorio <fog@debian.org> + + * typeobj_qstring.c: added quoted strings (can use both own code, + like psycopg 1.x or PQescapeString from lipq.) + +2003-07-21 Federico Di Gregorio <fog@debian.org> + + * connection_type.c (psyco_conn_close): added .close() + method. wow. + + * cursor_*.c: added basic cursor interface (new-style.) + +2003-07-20 Federico Di Gregorio <fog@debian.org> + + * psycopg/*: beginning of new source layout. if you think this + changelog is somewhat empty, you're right. look at + doc/ChangeLog-1.x for psycopg 1.x changelog just before the + branch. + + @@ -0,0 +1,18 @@ +Compiling and installing psycopg +******************************** + +While psycopg 1.x used autoconf for its build process psycopg 2 switched to +the more pythoning setup.py. Currently both psycopg's author and distutils +have some limitations so the file setup.cfg is almost unused and most build +options are hidden in setup.py. Before building psycopg look at the very +first lines of setup.py and change any settings to follow your system (or +taste); then: + + python setup.py build + +to build in the local directory; and: + + python setup.py install + +to install system-wide. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..62bc8e9 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +recursive-include psycopg *.c *.h +recursive-include lib *.py +recursive-include ZPsycopgDA *.py *.gif *.dtml +recursive-include examples *.py somehackers.jpg whereareyou.jpg +#recursive-include test *.py +recursive-include doc TODO HACKING SUCCESS ChangeLog-1.x +include scripts/maketypes.sh scripts/buildtypes.py +include AUTHORS README INSTALL ChangeLog setup.py setup.cfg @@ -0,0 +1,155 @@ +What's new in psycopg 1.99.10 +----------------------------- + +* The adapt() function now fully supports the adaptation protocol + described in PEP 246. Note that the adapters registry now is indexed + by (type, protocol) and not by type alone. Change your adapters + accordingly. + +* More configuration options moved from setup.py to setup.cfg. + +* Fixed two memory leaks: one in cursor deallocation and one in row + fetching (.fetchXXX() methods.) + +What's new in psycopg 1.99.9 +---------------------------- + +* Added simple pooling code (psycopg.pool module); see the reworked + examples/threads.py for example code. + +* Added DECIMAL typecaster to convert postgresql DECIMAL and NUMERIC + types (i.e, all types with an OID of NUMERICOID.) Note that the + DECIMAL typecaster does not set scale and precision on the created + objects but uses Python defaults. + +* ZPsycopgDA back in and working using the new pooling code. + +* Isn't that enough? :) + +What's new in psycopg 1.99.8 +---------------------------- + +* added support for UNICODE queries. + +* added UNICODE typecaster; to activate it just do: + + psycopg.extensions.register_type(psycopg.extensions.UNICODE) + + Note that the UNICODE typecaster override the STRING one, so it is + not activated by default. + +* cursors now really support the iterator protocol. + +* solved the rounding errors in time conversions. + +* now cursors support .fileno() and .isready() methods, to be used in + select() calls. + +* .copy_from() and .copy_in() methods are back in (still using the old + protocol, will be updated to use new one in next releasae.) + +* fixed memory corruption bug reported on win32 platform. + +What's new in psycopg 1.99.7 +---------------------------- + +* added support for tuple factories in cursor objects (removed factory + argument in favor of a .tuple_factory attribute on the cursor object); + see the new module psycopg.extras for a cursor (DictCursor) that + return rows as objects that support indexing both by position and + column name. + +* added support for tzinfo objects in datetime.timestamp objects: the + PostgreSQL type "timestamp with time zone" is converted to + datetime.timestamp with a FixedOffsetTimezone initialized as necessary. + +What's new in psycopg 1.99.6 +---------------------------- + +* sslmode parameter from 1.1.x + +* various datetime conversion improvements. + +* now psycopg should compile without mx or without native datetime + (not both, obviously.) + +* included various win32/MSVC fixes (pthread.h changes, winsock2 + library, include path in setup.py, etc.) + +* ported interval fixes from 1.1.14/1.1.15. + +* the last query executed by a cursor is now available in the + .query attribute. + +* conversion of unicode strings to backend encoding now uses a table + (that still need to be filled.) + +* cursors now have a .mogrify() method that return the query string + instead of executing it. + +* connection objects now have a .dsn read-only attribute that holds the + connection string. + +* moved psycopg C module to _psycopg and made psycopg a python module: + this allows for a neat separation of DBAPI-2.0 functionality and psycopg + extensions; the psycopg namespace will be also used to provide + python-only extensions (like the pooling code, some ZPsycopgDA support + functions and the like.) + +What's new in psycopg 1.99.3 +---------------------------- + +* added support for python 2.3 datetime types (both ways) and made datetime + the default set of typecasters when available. + +* added example: dt.py. + +What's new in psycopg 1.99.3 +---------------------------- + +* initial working support for unicode bound variables: UTF-8 and latin-1 + backend encodings are natively supported (and the encoding.py example even + works!) + +* added .set_client_encoding() method on the connection object. + +* added examples: encoding.py, binary.py, lastrowid.py. + +What's new in psycopg 1.99.2 +---------------------------- + +* better typecasting: + - DateTimeDelta used for postgresql TIME (merge from 1.1) + - BYTEA now is converted to a real buffer object, not to a string + +* buffer objects are now adapted into Binary objects automatically. + +* ported scroll method from 1.1 (DBAPI-2.0 extension for cursors) + +* initial support for some DBAPI-2.0 extensions: + - .rownumber attribute for cursors + - .connection attribute for cursors + - .next() and .__iter__() methods to have cursors support the iterator + protocol + - all exception objects are exported to the connection object + +What's new in psycopg 1.99.1 +---------------------------- + +* implemented microprotocols to adapt arbitrary types to the interface used by + psycopg to bind variables in execute; + +* moved qstring, pboolean and mxdatetime to the new adapter layout (binary is + still missing; python 2.3 datetime needs to be written). + + +What's new in psycopg 1.99.0 +---------------------------- + +* reorganized the whole source tree; + +* async core is in place; + +* splitted QuotedString objects from mx stuff; + +* dropped autotools and moved to pythonic setup.py (needs work.) @@ -0,0 +1,45 @@ +psycopg - Python-PostgreSQL Database Adapter +******************************************** + +psycopg is a PostgreSQL database adapter for the Python programming +language. This is version 2, a complete rewrite of the original code to +provide new-style classes for connection and cursor objects and other sweet +candies. Like the original, psycopg 2 was written with the aim of being +very small and fast, and stable as a rock. + +psycopg is different from the other database adapter because it was +designed for heavily multi-threaded applications that create and destroy +lots of cursors and make a conspicuous number of concurrent INSERTs or +UPDATEs. psycopg 2 also provide full asycronous operations for the really +brave programmer. + +There are confirmed reports of psycopg 1.x compiling and running on Linux +and FreeBSD on i386, Solaris, MacOS X and win32 architectures. psycopg 2 +does not introduce build-wise incompatible changes so it should be able to +compile on all architectures just as its predecessor did. + +Now go read the INSTALL file. More information about psycopg extensions to +the DBAPI-2.0 is available in the files located in the doc/ direcory. + + +Licence +------- + +psycopg is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. See file COPYING for details. + +As a special exception, specific permission is granted for the GPLed code in +this distribition to be linked to OpenSSL and PostgreSQL libpq without +invoking GPL clause 2(b). + +If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e., every +file inside the ZPsycopgDA directory) under the ZPL license as published on +the Zope web site, http://www.zope.org/Resources/ZPL. The ZPL is perfectly +compatible with the GPL + +psycopg is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. diff --git a/ZPsycopgDA/DA.py b/ZPsycopgDA/DA.py new file mode 100644 index 0000000..b9979b7 --- /dev/null +++ b/ZPsycopgDA/DA.py @@ -0,0 +1,202 @@ +# ZPsycopgDA/DA.py - ZPsycopgDA Zope product: Database Connection +# +# Copyright (C) 2004 Federico Di Gregorio <fog@initd.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# Or, at your option this program (ZPsycopgDA) can be distributed under the +# Zope Public License (ZPL) Version 1.0, as published on the Zope web site, +# http://www.zope.org/Resources/ZPL. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the LICENSE file for details. + + +ALLOWED_PSYCOPG_VERSIONS = ('1.99.9',) + +import sys +import db +import DABase +import Shared.DC.ZRDB.Connection + +from db import DB +from Globals import DTMLFile +from Globals import HTMLFile +from ImageFile import ImageFile +from ExtensionClass import Base +from DateTime import DateTime + +# import psycopg and functions/singletons needed for date/time conversions + +import psycopg +from psycopg import DATETIME +from psycopg.extensions import TIME, DATE, INTERVAL +from psycopg.extensions import new_type, register_type + + + +# add a new connection to a folder + +manage_addZPsycopgConnectionForm = DTMLFile('dtml/add',globals()) + +def manage_addZPsycopgConnection(self, id, title, connection_string, + zdatetime=None, tilevel=2, + check=None, REQUEST=None): + """Add a DB connection to a folder.""" + self._setObject(id, Connection(id, title, connection_string, + zdatetime, check, tilevel)) + if REQUEST is not None: return self.manage_main(self, REQUEST) + + + +# the connection object + +class Connection(DABase.Connection): + """ZPsycopg Connection.""" + id = 'Psycopg_database_connection' + database_type = 'Psycopg' + meta_type = title = 'Z Psycopg Database Connection' + icon = 'misc_/ZPsycopg/conn' + + def __init__(self, id, title, connection_string, + zdatetime, check=None, tilevel=2, encoding=''): + self.zdatetime = zdatetime + self.id = str(id) + self.edit(title, connection_string, zdatetime, + check=check, tilevel=tilevel, encoding=encoding) + + def factory(self): + return DB + + def table_info(self): + return self._v_database_connection.table_info() + + def edit(self, title, connection_string, + zdatetime, check=None, tilevel=2, encoding=''): + self.title = title + self.connection_string = connection_string + self.zdatetime = zdatetime + self.tilevel = tilevel + self.encoding = encoding + + self.set_type_casts() + + if check: self.connect(self.connection_string) + + manage_properties = DTMLFile('dtml/edit', globals()) + + def manage_edit(self, title, connection_string, + zdatetime=None, check=None, tilevel=2, encoding='UTF-8', + REQUEST=None): + """Edit the DB connection.""" + self.edit(title, connection_string, zdatetime, + check=check, tilevel=tilevel, encoding=encoding) + if REQUEST is not None: + msg = "Connection edited." + return self.manage_main(self,REQUEST,manage_tabs_message=msg) + + def connect(self, s): + try: + self._v_database_connection.close() + except: + pass + + # check psycopg version and raise exception if does not match + if psycopg.__version__ not in ALLOWED_PSYCOPG_VERSIONS: + raise ImportError("psycopg version mismatch (imported %s)" + + psycopg.__version__) + + self.set_type_casts() + self._v_connected = '' + dbf = self.factory() + + # TODO: let the psycopg exception propagate, or not? + self._v_database_connection = dbf( + self.connection_string, self.tilevel, self.encoding) + self._v_database_connection.open() + self._v_connected = DateTime() + + return self + + def set_type_casts(self): + # note that in both cases order *is* important + if self.zdatetime: + # use zope internal datetime routines + register_type(ZDATETIME) + register_type(ZDATE) + register_type(ZTIME) + register_type(ZINTERVAL) + else: + # use the standard + register_type(DATETIME) + register_type(DATE) + register_type(TIME) + register_type(INTERVAL) + +# database connection registration data + +classes = (Connection,) + +meta_types = ({'name':'Z Psycopg Database Connection', + 'action':'manage_addZPsycopgConnectionForm'},) + +folder_methods = { + 'manage_addZPsycopgConnection': manage_addZPsycopgConnection, + 'manage_addZPsycopgConnectionForm': manage_addZPsycopgConnectionForm} + +__ac_permissions__ = ( + ('Add Z Psycopg Database Connections', + ('manage_addZPsycopgConnectionForm', 'manage_addZPsycopgConnection')),) + +# add icons + +misc_={'conn': ImageFile('Shared/DC/ZRDB/www/DBAdapterFolder_icon.gif')} + +for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin', + 'int', 'float', 'date', 'time', 'datetime'): + misc_[icon] = ImageFile('icons/%s.gif' % icon, globals()) + +# zope-specific psycopg typecasters + +# convert an ISO timestamp string from postgres to a Zope DateTime object +def _cast_DateTime(str): + if str: + # this will split us into [date, time, GMT/AM/PM(if there)] + dt = split(str, ' ') + if len(dt) > 1: + # we now should split out any timezone info + dt[1] = split(dt[1], '-')[0] + dt[1] = split(dt[1], '+')[0] + return DateTime(join(dt[:2], ' ')) + else: + return DateTime(dt[0]) + +# convert an ISO date string from postgres to a Zope DateTime object +def _cast_Date(str): + if str: + return DateTime(str) + +# Convert a time string from postgres to a Zope DateTime object. +# NOTE: we set the day as today before feeding to DateTime so +# that it has the same DST settings. +def _cast_Time(str): + if str: + return DateTime(time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(time.time())[:3]+ + time.strptime(str[:8], "%H:%M:%S")[3:])) + +# TODO: DateTime does not support intervals: what's the best we can do? +def _cast_Interval(str): + return str + +ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime) +ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval) +ZDATE = new_type((1082,), "ZDATE", _cast_Date) +ZTIME = new_type((1083,), "ZTIME", _cast_Time) + diff --git a/ZPsycopgDA/DABase.py b/ZPsycopgDA/DABase.py new file mode 100644 index 0000000..03102c3 --- /dev/null +++ b/ZPsycopgDA/DABase.py @@ -0,0 +1,67 @@ +# ZPsycopgDA/DABase.py - ZPsycopgDA Zope product: Database inspection +# +# Copyright (C) 2004 Federico Di Gregorio <fog@initd.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# Or, at your option this program (ZPsycopgDA) can be distributed under the +# Zope Public License (ZPL) Version 1.0, as published on the Zope web site, +# http://www.zope.org/Resources/ZPL. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the LICENSE file for details. + +import sys +import Shared.DC.ZRDB.Connection + +from db import DB +from Globals import HTMLFile +from ImageFile import ImageFile +from ExtensionClass import Base +from DateTime import DateTime + +# import psycopg and functions/singletons needed for date/time conversions + +import psycopg +from psycopg.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN +from psycopg import NUMBER, STRING, ROWID, DATETIME + + + +class Connection(Shared.DC.ZRDB.Connection.Connection): + _isAnSQLConnection = 1 + + info = None + + #manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options + ( + # {'label': 'Browse', 'action':'manage_browse'},) + + #manage_tables = HTMLFile('tables', globals()) + #manage_browse = HTMLFile('browse',globals()) + + def __getitem__(self, name): + if name == 'tableNamed': + if not hasattr(self, '_v_tables'): self.tpValues() + return self._v_tables.__of__(self) + raise KeyError, name + + + ## old stuff from ZPsycopgDA 1.1 (never implemented) ## + + def manage_wizard(self, tables): + "Wizard of what? Oozing?" + + def manage_join(self, tables, select_cols, join_cols, REQUEST=None): + """Create an SQL join""" + + def manage_insert(self, table, cols, REQUEST=None): + """Create an SQL insert""" + + def manage_update(self, table, keys, cols, REQUEST=None): + """Create an SQL update""" diff --git a/ZPsycopgDA/__init__.py b/ZPsycopgDA/__init__.py new file mode 100644 index 0000000..b0e2a45 --- /dev/null +++ b/ZPsycopgDA/__init__.py @@ -0,0 +1,32 @@ +# ZPsycopgDA/__init__.py - ZPsycopgDA Zope product +# +# Copyright (C) 2004 Federico Di Gregorio <fog@initd.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# Or, at your option this program (ZPsycopgDA) can be distributed under the +# Zope Public License (ZPL) Version 1.0, as published on the Zope web site, +# http://www.zope.org/Resources/ZPL. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the LICENSE file for details. + +__doc__ = "ZPsycopg Database Adalper Registration." +__version__ = '2.0' + +import sys +import string +import DA + +methods = DA.folder_methods +classes = DA.classes +meta_types = DA.meta_types +misc_ = DA.misc_ + +__ac_permissions__=DA.__ac_permissions__ diff --git a/ZPsycopgDA/db.py b/ZPsycopgDA/db.py new file mode 100644 index 0000000..c859535 --- /dev/null +++ b/ZPsycopgDA/db.py @@ -0,0 +1,209 @@ +# ZPsycopgDA/db.py - query execution +# +# Copyright (C) 2004 Federico Di Gregorio <fog@initd.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# Or, at your option this program (ZPsycopgDA) can be distributed under the +# Zope Public License (ZPL) Version 1.0, as published on the Zope web site, +# http://www.zope.org/Resources/ZPL. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the LICENSE file for details. + +from Shared.DC.ZRDB.TM import TM +from Shared.DC.ZRDB import dbi_db + +from ZODB.POSException import ConflictError + +import time +import site +import pool + +import psycopg +from psycopg.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN +from psycopg import NUMBER, STRING, ROWID, DATETIME + + + +# the DB object, managing all the real query work + +class DB(TM, dbi_db.DB): + + _p_oid = _p_changed = _registered = None + + def __init__(self, dsn, tilevel, enc='utf-8'): + self.dsn = dsn + self.tilevel = tilevel + self.encoding = enc + self.failures = 0 + self.calls = 0 + + def getconn(self, create=True): + conn = pool.getconn(self.dsn) + conn.set_isolation_level(int(self.tilevel)) + return conn + + def putconn(self, close=False): + try: + conn = pool.getconn(self.dsn, False) + except AttributeError: + pass + pool.putconn(self.dsn, conn, close) + + def getcursor(self): + conn = self.getconn() + return conn.cursor() + + def _finish(self, *ignored): + try: + conn = self.getconn(False) + conn.commit() + self.putconn() + except AttributeError: + pass + + def _abort(self, *ignored): + try: + conn = self.getconn(False) + conn.rollback() + self.putconn() + except AttributeError: + pass + + def open(self): + # this will create a new pool for our DSN if not already existing, + # then get and immediately release a connection + self.getconn() + self.putconn() + + def close(self): + # FIXME: if this connection is closed we flush all the pool associated + # with the current DSN; does this makes sense? + pool.flushpool(self.dsn) + + def sortKey(self): + return 1 + + ## tables and rows ## + + def tables(self, rdb=0, _care=('TABLE', 'VIEW')): + self._register() + c = self.getcursor() + c.execute( + "SELECT t.tablename AS NAME, 'TABLE' AS TYPE " + " FROM pg_tables t WHERE tableowner <> 'postgres' " + "UNION SELECT v.viewname AS NAME, 'VIEW' AS TYPE " + " FROM pg_views v WHERE viewowner <> 'postgres' " + "UNION SELECT t.tablename AS NAME, 'SYSTEM_TABLE\' AS TYPE " + " FROM pg_tables t WHERE tableowner = 'postgres' " + "UNION SELECT v.viewname AS NAME, 'SYSTEM_TABLE' AS TYPE " + "FROM pg_views v WHERE viewowner = 'postgres'") + res = [] + for name, typ in c.fetchall(): + if typ in _care: + res.append({'TABLE_NAME': name, 'TABLE_TYPE': typ}) + self.putconn() + return res + + def columns(self, table_name): + self._register() + c = self.getcursor() + try: + r = c.execute('SELECT * FROM "%s" WHERE 1=0' % table_name) + except: + return () + res = [] + for name, type, width, ds, p, scale, null_ok in c.description: + if type == NUMBER: + if type == INTEGER: + type = INTEGER + elif type == FLOAT: + type = FLOAT + else: type = NUMBER + elif type == BOOLEAN: + type = BOOLEAN + elif type == ROWID: + type = ROWID + elif type == DATETIME: + type = DATETIME + else: + type = STRING + + res.append({'Name': name, + 'Type': type.name, + 'Precision': 0, + 'Scale': 0, + 'Nullable': 0}) + self.putconn() + return res + + ## query execution ## + + def query(self, query_string, max_rows=None, query_data=None): + self._register() + self.calls = self.calls+1 + + desc = () + res = [] + nselects = 0 + + c = self.getcursor() + + try: + for qs in [x for x in query_string.split('\0') if x]: + if type(qs) == unicode: + if self.encoding: + qs = qs.encode(self.encoding) + try: + if (query_data): + c.execute(qs, query_data) + else: + c.execute(qs) + except (psycopg.ProgrammingError, psycopg.IntegrityError), e: + if e.args[0].find("concurrent update") > -1: + raise ConflictError + raise e + if c.description is not None: + nselects += 1 + if c.description != desc and nselects > 1: + raise psycopg.ProgrammingError( + 'multiple selects in single query not allowed') + if max_rows: + res = c.fetchmany(max_rows) + else: + res = c.fetchall() + desc = c.description + self.failures = 0 + + except StandardError, err: + self._abort() + raise err + + items = [] + for name, typ, width, ds, p, scale, null_ok in desc: + if typ == NUMBER: + if typ == INTEGER or typ == LONGINTEGER: typs = 'i' + else: typs = 'n' + elif typ == BOOLEAN: + typs = 'n' + elif typ == ROWID: + typs = 'i' + elif typ == DATETIME: + typs = 'd' + else: + typs = 's' + items.append({ + 'name': name, + 'type': typs, + 'width': width, + 'null': null_ok, + }) + + return items, res diff --git a/ZPsycopgDA/dtml/add.dtml b/ZPsycopgDA/dtml/add.dtml new file mode 100644 index 0000000..d138779 --- /dev/null +++ b/ZPsycopgDA/dtml/add.dtml @@ -0,0 +1,96 @@ +<dtml-var manage_page_header> + +<dtml-var "manage_form_title(this(), _, + form_title='Add Z Psycopg Database Connection', + help_product='ZPsycopgDA', + help_topic='ZPsycopgDA-Method-Add.stx' + )"> + +<p class="form-help"> +A Zope Psycopg Database Connection is used to connect and execute +queries on a PostgreSQL database. +</p> + +<p class="form-help"> +In the form below <em>Connection String</em> (also called the Data Source Name +or DSN for short) is a string... (TODO: finish docs) +</p> + +<form action="manage_addZPsycopgConnection" method="POST"> +<table cellspacing="0" cellpadding="2" border="0"> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Id + </div> + </td> + <td align="left" valign="top"> + <input type="text" name="id" size="40" + value="Psycopg_database_connection" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-optional"> + Title + </div> + </td> + <td align="left" valign="top"> + <input type="text" name="title" size="40" + value="Z Psycopg Database Connection"/> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Connection string + </div> + </td> + <td align="left" valign="top"> + <input type="text" name="connection_string" size="40" value="" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Connect immediately + </div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="check" value="YES" checked="YES" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Use Zope's internal DateTime + </div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="zdatetime" value="YES" checked="YES" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Transaction isolation level + </div> + </td> + <td align="left" valign="top"> + <select name="tilevel:int"> + <option value="1">Read committed</option> + <option value="2" selected="YES">Serializable</option> + </select> + </td> + </tr> + <tr> + <td align="left" valign="top" colspan="2"> + <div class="form-element"> + <input class="form-element" type="submit" name="submit" value=" Add " /> + </div> + </td> + </tr> +</table> +</form> + +<dtml-var manage_page_footer> diff --git a/ZPsycopgDA/dtml/edit.dtml b/ZPsycopgDA/dtml/edit.dtml new file mode 100644 index 0000000..45275ed --- /dev/null +++ b/ZPsycopgDA/dtml/edit.dtml @@ -0,0 +1,67 @@ +<dtml-var manage_page_header> +<dtml-var manage_tabs> + +<form action="manage_edit" method="POST"> +<table cellspacing="0" cellpadding="2" border="0"> + <tr> + <td align="left" valign="top"> + <div class="form-optional"> + Title + </div> + </td> + <td align="left" valign="top"> + <input type="text" name="title" size="40" + value="&dtml-title;"/> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Connection string + </div> + </td> + <td align="left" valign="top"> + <input type="text" name="connection_string" size="40" + value="&dtml-connection_string;" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Use Zope's internal DateTime + </div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="zdatetime" value="YES" + <dtml-if expr="zdatetime">checked="YES"</dtml-if> /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Transaction isolation level + </div> + </td> + <td align="left" valign="top"> + <select name="tilevel:int"> + <option value="1" + <dtml-if expr="tilevel==1">selected="YES"</dtml-if">> + Read committed</option> + <option value="2" + <dtml-if expr="tilevel==2">selected="YES"</dtml-if">> + Serializable</option> + </select> + </td> + </tr> + <tr> + <td align="left" valign="top" colspan="2"> + <div class="form-element"> + <input class="form-element" type="submit" name="submit" + value=" Save Changes " /> + </div> + </td> + </tr> +</table> +</form> + +<dtml-var manage_page_footer> diff --git a/ZPsycopgDA/icons/bin.gif b/ZPsycopgDA/icons/bin.gif Binary files differnew file mode 100644 index 0000000..fa4fdd0 --- /dev/null +++ b/ZPsycopgDA/icons/bin.gif diff --git a/ZPsycopgDA/icons/date.gif b/ZPsycopgDA/icons/date.gif Binary files differnew file mode 100644 index 0000000..0d88a57 --- /dev/null +++ b/ZPsycopgDA/icons/date.gif diff --git a/ZPsycopgDA/icons/datetime.gif b/ZPsycopgDA/icons/datetime.gif Binary files differnew file mode 100644 index 0000000..faa540b --- /dev/null +++ b/ZPsycopgDA/icons/datetime.gif diff --git a/ZPsycopgDA/icons/field.gif b/ZPsycopgDA/icons/field.gif Binary files differnew file mode 100644 index 0000000..9bf8692 --- /dev/null +++ b/ZPsycopgDA/icons/field.gif diff --git a/ZPsycopgDA/icons/float.gif b/ZPsycopgDA/icons/float.gif Binary files differnew file mode 100644 index 0000000..efc5c78 --- /dev/null +++ b/ZPsycopgDA/icons/float.gif diff --git a/ZPsycopgDA/icons/int.gif b/ZPsycopgDA/icons/int.gif Binary files differnew file mode 100644 index 0000000..5ee3ced --- /dev/null +++ b/ZPsycopgDA/icons/int.gif diff --git a/ZPsycopgDA/icons/stable.gif b/ZPsycopgDA/icons/stable.gif Binary files differnew file mode 100644 index 0000000..acdd37d --- /dev/null +++ b/ZPsycopgDA/icons/stable.gif diff --git a/ZPsycopgDA/icons/table.gif b/ZPsycopgDA/icons/table.gif Binary files differnew file mode 100644 index 0000000..4fb32d9 --- /dev/null +++ b/ZPsycopgDA/icons/table.gif diff --git a/ZPsycopgDA/icons/text.gif b/ZPsycopgDA/icons/text.gif Binary files differnew file mode 100644 index 0000000..c9d5365 --- /dev/null +++ b/ZPsycopgDA/icons/text.gif diff --git a/ZPsycopgDA/icons/time.gif b/ZPsycopgDA/icons/time.gif Binary files differnew file mode 100644 index 0000000..6d08915 --- /dev/null +++ b/ZPsycopgDA/icons/time.gif diff --git a/ZPsycopgDA/icons/view.gif b/ZPsycopgDA/icons/view.gif Binary files differnew file mode 100644 index 0000000..71b30de --- /dev/null +++ b/ZPsycopgDA/icons/view.gif diff --git a/ZPsycopgDA/icons/what.gif b/ZPsycopgDA/icons/what.gif Binary files differnew file mode 100644 index 0000000..0214a4d --- /dev/null +++ b/ZPsycopgDA/icons/what.gif diff --git a/ZPsycopgDA/pool.py b/ZPsycopgDA/pool.py new file mode 100644 index 0000000..8cc7fa7 --- /dev/null +++ b/ZPsycopgDA/pool.py @@ -0,0 +1,51 @@ +# ZPsycopgDA/pool.py - ZPsycopgDA Zope product: connection pooling +# +# Copyright (C) 2004 Federico Di Gregorio <fog@initd.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# Or, at your option this program (ZPsycopgDA) can be distributed under the +# Zope Public License (ZPL) Version 1.0, as published on the Zope web site, +# http://www.zope.org/Resources/ZPL. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the LICENSE file for details. + +# all the connections are held in a pool of pools, directly accessible by the +# ZPsycopgDA code in db.py + +import threading +import psycopg.pool + +_connections_pool = {} +_connections_lock = threading.Lock() + +def getpool(dsn, create=True): + _connections_lock.acquire() + try: + if not _connections_pool.has_key(dsn) and create: + _connections_pool[dsn] = \ + psycopg.pool.ThreadedConnectionPool(4, 200, dsn) + finally: + _connections_lock.release() + return _connections_pool[dsn] + +def flushpool(dsn): + _connections_lock.acquire() + try: + _connections_pool[dsn].closeall() + del _connections_pool[dsn] + finally: + _connections_lock.release() + +def getconn(dsn, create=True): + return getpool(dsn, create=create).getconn() + +def putconn(dsn, conn, close=False): + getpool(dsn).putconn(conn, close=close) diff --git a/doc/ChangeLog-1.x b/doc/ChangeLog-1.x new file mode 100644 index 0000000..dadfc1b --- /dev/null +++ b/doc/ChangeLog-1.x @@ -0,0 +1,1744 @@ +2003-07-26 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.7. + + * ZPsycopgDA/db.py: added _cursor method that checks for self.db + before returning a new cursor. Should fix problem reported with + Zope 2.7. + +2003-07-23 Federico Di Gregorio <fog@debian.org> + + * cursor.c: applied notify and fileno patch from Vsevolod Lobko. + +2003-07-20 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_mogrify_dict): applied (slightly modofied) patch from + Tobias Sargeant: now .execute() accept not only dictionaries but + every type that has a __getitem__ method. + +2003-07-13 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.6. + + * cursor.c (psyco_curs_scroll): added scroll method, patch from + Jason D.Hildebrand. + + * typemod.c (new_psyco_quotedstringobject): discard NUL characters + (\0) in quoted strings (fix problem reported by Richard Taylor.) + +2003-07-10 Federico Di Gregorio <fog@debian.org> + + * Added python-taylor.txt in doc directory: very nice introduction + to DBAPI programming by Richard Taylor. + +2003-07-09 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_psyco_curs_execute): another MT problem exposed and + fixed by Sebastien Bigaret (self->keeper->status still LOCKED + after a fatal error during PQexec call.) + +2003-06-23 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.5.1. + + * ZPsycopgDA/db.py (DB.query): stupid error making ZPsycopgDA + unusable fixed (else->except). + +2003-06-22 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.5 candidate. + + * cursor.c (psyco_curs_copy_to): now any object with the write + method can be used as a copy_to target. + +2003-06-20 Federico Di Gregorio <fog@debian.org> + + * cursor.c (psyco_curs_copy_from): applied patch to allow copy_to + from any object having a "readline" attribute (patch from Lex + Berezhny.) (psyco_curs_copy_from): another patch from Lex to make + psycopg raise an error on COPY FROM errors. + + * ZPsycopgDA/db.py (DB.query): if a query raise an exception, + first self._abort() is called to rollback current + "sub-transaction". this is a backward-compatible change for + people that think continuing to work in the same zope transaction + after an exception is a Good Thing (TM). + + * finally updated check_types.expected. checked by hand the + conversions work the right way. + + * doc/examples/work.py: fixed example. note that it is a long time + (at least two releases) that psycopg does not END a transaction + initiated explicitly by the user while in autocommit mode. + +2003-06-19 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_mogrify_dict): fixed dictionary mogrification (patch + by Vsevolod Lobko.) (_psyco_curs_execute): fixed keeper status + trashing problem by letting only one thread at time play with + keeper->status (as suggested by Sebastien Bigaret.) + +2003-05-07 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.4. + + * cursor.c: Added "statusmessage" attribute that holds the backend + message (modified lots of functions, look for self->status). + +2003-05-06 Federico Di Gregorio <fog@debian.org> + + * typemod.c (new_psyco_datetimeobject): moved Py_INCREF into + XXX_FromMx functions, to fix memory leak reported by Jim Crumpler. + +2003-04-11 Federico Di Gregorio <fog@debian.org> + + * module.h (PyObject_TypeCheck): fixed leak in python 2.1 + (Guido van Rossum). + +2003-04-08 Federico Di Gregorio <fog@debian.org> + + * buildtypes.py (basic_types): removed LXTEXT (never user, does + not exists anymore.) + +2003-04-07 Federico Di Gregorio <fog@debian.org> + + * setup.py: added very lame setup.py script. + +2003-04-02 Federico Di Gregorio <fog@debian.org> + + * Release 1.3. + + * psycopg.spec: Added (but modified) spec file by William + K. Volkman (again, this change was lost somewhere in time...) + +2003-04-01 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_psyco_curs_execute): psycopg was reporting everything + as IntegrityError; reported and fix suggested by Amin Abdulghani. + +2003-03-21 Federico Di Gregorio <fog@debian.org> + + * cursor.c (psyco_curs_fetchone): debug statements sometimes made + psycopg segfault: fixed by a patch by Ken Simpson. + +2003-03-18 Federico Di Gregorio <fog@debian.org> + + * cursor.c (alloc_keeper): patch from Dieter Maurer to unlock GIL + whaile calling PQconnectdb(). + +2003-03-05 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.2. + + * Applied cygwin patch from Hajime Nakagami. + +2003-02-25 Federico Di Gregorio <fog@debian.org> + + * Release 1.1.2pre1. + + * cursor.c: added .lastrowid attribute to cursors (lastoid is + deprecated and will be removed sometime in the future.) + + * cursor.c (begin_pgconn): implemented various isolation levels + (also, in abort_pgconn, commit_pgconn.) + + * Added keyword parameters to psycopg.connect(): all take strings + (even port): database, host, port, user, password. + + * configure.in: fixed test for postgres version > 7.2. + + * cursor.c (_psyco_curs_execute): removed if on pgerr in default + case (if we enter default pgerr can't be one of the cased ones.) + Also applied slightly modified patch from William K. Volkman. + +2003-02-24 Federico Di Gregorio <fog@debian.org> + + * Merged in changes from 1.0.15.1 (see below for merged + ChangeLog.) + +2003-02-14 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.15.1. + + * cursor.c (_mogrify_fmt): in some cases we where removing one + character too much from the format string, resulting in BIG BAD + BUG. <g> Fixed. + +2003-02-13 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.15. <g> + + * connection.c (_psyco_conn_close): now call dispose_pgconn on all + cursors, to make sure all phisical connections to the db are + closed (problem first reported by Amin Abdulghani.) + + * DBAPI-2.0 fixed mainly due to Stuart Bishop: + - cursor.c (psyco_curs_setinputsizes): removed PARSEARGS, as + this method does nothing. + - cursor.c (psyco_curs_setoutputsize): .setoutputsize was + spelled .setoutputsizes! fixed. Also removed PARSEARGS, as this + method does nothing. + +2003-02-12 Federico Di Gregorio <fog@debian.org> + + * module.h (Dprintf): check on __APPLE__ to avoid variadic macros + on macos x (as reported by Stuart Bishop, btw, why gcc seems to + not support them on macos?) + + * cursor.c (_mogrify_fmt): non-alphabetic characters are dropped + after the closing ")" until a real alphabetic, formatting one is + found. (Fix bug reported by Randall Randall.) + +2003-02-05 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): patched again to take into + account leading zeroes. + +2003-02-02 Federico Di Gregorio <fog@debian.org> + + * Makefile.pre.in: applied patch from Albert Chin-A-Young to + define BLDSHARED. + + * README: added explicit permission to link with OpenSSL. + +2003-01-30 Federico Di Gregorio <fog@debian.org> + + * config.h.in: applied patch from Albert Chin-A-Young to fix + asprintf prototype. + +2003-01-29 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_mogrify_seq): fixed little refcount leak, as + suggested by Yves Bastide. + +2003-01-24 Federico Di Gregorio <fog@debian.org> + + * Merged-in changes from 1.0.14.2 (emacs diff mode is great..) + + * Release 1.0.14.2. + + * ZPsycopgDA/db.py (DB.query): back to allowing up to 1000 db + errors before trying to reopen the connection by ourselves. + + * ZPsycopgDA/db.py: a false (None preferred, 0 allowed) max_rows + value now means "fetch all results". + +2003-01-22 Federico Di Gregorio <fog@debian.org> + + * cursor.c (psyco_curs_fetchone): fixed little memory leak + reported by Dieter Maurer. + +2003-01-20 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (DB.tables/columns): added registration with + Zope's transaction machinery. + + * Release 1.0.14.1. + + * ZPsycopgDA/db.py: applied some fixes and cleanups by Dieter + Maurer (serialization problem were no more correctly detected!) + + * Release 1.0.14. + + * Merged in 1.0.14. + + * Import of 1.1.1 done. + + * Moved everything to cvs HEAD. + +2003-01-20 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/connectionAdd.dtml: fixed typo (thanks to Andrew + Veitch.) + + * typeobj.c (psyco_INTERVAL_cast): applied patch from Karl Putland + to fix problems with fractional seconds. + +2002-12-03 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.14-pre2. + + * module.h: added macro for PyObject_TypeCheck if python version <2.2. + + * typeobj.c (psyco_DBAPITypeObject_coerce): added error message to + coercion errors. + +2002-12-02 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.14-pre1. + + * ZPsycopgDA/db.py (DB.sortKey): added sortKey(). + + * ZPsycopgDA/DA.py: applied a patch that was lost on hard disk + (sic), if you sent me a patch names psycopg-1.0.13.diff modifying + DA.py imports and want your name here, send me an email. :) + [btw, the patch fix the ImageFile import, getting it from Globals + as it is right.] + + * typeobj.c (psyco_DBAPITypeObject_coerce): Fixed coerce segfault + by checking explicitly for all the allowed types. + +2002-11-25 Federico Di Gregorio <fog@debian.org> + + * doc/examples/*.py: added .rollback() to all exceptions before + deleteing the old table. + + * cursor.c: Apllied patch from John Goerzen (fix memory leak in + executemany). + +2002-10-25 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.13. + + * connection.c (_psyco_conn_close): remove cursors from the list + starting from last and moving backward (as suggested by Jeremy + Hylton; this is not such a big gain because python lists are + *linked* lists, but not removing the element 0 makes the code a + little bit clear.) + + * cursor.c (_psyco_curs_execute): now IntegrityError is raised + instead of ProgrammingError when adding NULL values to a non-NULL + column (suggested by Edmund Lian) and on referential integrity + violation (as per debian bug #165791.) + + * typeobj.c (psyco_DATE_cast): now we use 999999 instead of + 5867440 for very large (both signs) dates. This allow to re-insert + the DateTime object into postgresql (suggested by Zahid Malik.) + +2002-09-13 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.12. + + * Removed code to support COPY FROM/TO, will be added to new 1.1 + branch to be released next week. + + * cursor.c (_mogrify_seq): Fixed memory leak reported by Menno + Smits (values obtained by calling PySequence_GetItem are *new* + references!) + +2002-09-07 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_psyco_curs_execute): Added skeleton to support COPY + FROM/TO. + +2002-09-06 Federico Di Gregorio <fog@debian.org> + + * configure.in: if libcrypt can't be found we probably are on + MacOS X: check for libcrypto, as suggested by Aparajita Fishman. + +2002-09-03 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (DB.columns): Applied patch from Dieter Maurer + to allow the DA-browser to work with mixed case table names. + +2002-08-30 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (cast_DateTime): Applied patch from Yury to fix + timestamps (before they were returned with time always set to 0.) + +2002-08-26 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.11.1 (to fix a %&£$"! bug in ZPsycopgDA not + accepting psycopg 1.0.11 as a valid version. + + * Release 1.0.11. + +2002-08-22 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.11pre2. + + * cursor.c (_psyco_curs_execute): fixed IntegrityError as reported + by Andy Dustman. (psyco_curs_execute): converting TypeError to + ProgrammingError on wrong number of % and/or aeguments. + + * doc/examples/integrity.py: added example and check for + IntegrityError. + +2002-08-08 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.11pre1. + +2002-08-06 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (cast_DateTime): patched as suggested by Tom + Jenkins; now it shouldwork with time zones too. + +2002-08-01 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (cast_DateTime): fixed problem with missing + AM/PM, as reported by Tom Jenkins. + +2002-07-23 Federico Di Gregorio <fog@debian.org> + + * Fixed buglets reported by Mike Coleman. + +2002-07-22 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.10. + +2002-07-14 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.10pre2. + + * typeobj.c (psyco_LONGINTEGER_cast): fixed bad segfault by + INCREFfing Py_None when it is the result of a NULL conversion. + +2002-07-04 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.10pre1. + + * buildtypes.py (basic_types): added TIMESTAMPTZ to the types + converted by the DATE builtin. + + * ZPsycopgDA/DA.py (Connection.connect): Added version check. + +2002-07-03 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_XXX_cast): fixed bug reported by multiple users + by appliying Matt patch. + +2002-06-30 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (Connection.set_type_casts): applied patch from + Tom Jenkins to parse dates with TZ. + +2002-06-20 Federico Di Gregorio <fog@debian.org> + + * Preparing for release 1.0.9. + + * Makefile.pre.in (dist): now we really include psycopg.spec. + +2002-06-17 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (_finish, _abort): fixed problem with + connection left in invalid state by applying Tom Jenkins patch. + +2002-06-06 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (DB._abort): fixed exception raising after an + error in execute triggerer deletion of self.db. + +2002-05-16 Federico Di Gregorio <fog@debian.org> + + * cursor.c (psyco_curs_fetchone): None values passed to the + internal typecasters. + + * typeobj.c: added management of None to all the builtin + typecasters. + +2002-04-29 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (cast_Time): applied 'seconds as a float' patch + from Jelle. + +2002-04-23 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.8. + + * Makefile.pre.in: we now include win32 related files in the + distribution. + + * connection.c (psyco_conn_destroy): fixed segfault reported by + Scott Leerssen (we were double calling _psyco_conn_close().) + + * typemod.c (new_psyco_quotedstringobject): fixed memory stomping + catched by assert(); thanks to Matt Hoskins for reporting this + one. + +2002-04-22 Federico Di Gregorio <fog@debian.org> + + * configure.in: grmpf. we need a VERSION file for windows, we'll + use it for configue and debian/rules too. + + * Integrated win32 changes from Jason Erickson. Moved his + Readme.txt to README.win32, removed VERSION and DATE, patched + source where required. Renamed HISTORY to ChangeLog.win32, hoping + Jason will start adding changes to the real ChangeLog file. + +2002-04-07 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.7.1. + + * configure.in: fixed little bug as reported by ron. + +2002-04-05 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.7? + + * typemod.c (new_psyco_bufferobject): fixed encoding problem (0xff + now encoded as \377 and not \777.) Also encoding *all* chars as + quoted octal values to prevent "Invalid UNICODE character sequence + found" errors. + + * Release 1.0.7. (Real this time.) (Ok, it was a joke....) + +2002-04-03 Federico Di Gregorio <fog@debian.org> + + * configure.in: fixed problem with postgres versions in the format + 7.2.x (sic.) + + * connection.c (psyco_conn_destroy): moved most of the destroy + stuff into its own function (_psyco_conn_close) and added a call + to it from psyco_conn_close. This should fix the "psycopg does not + release postgres connections on .close()" problem. + +2002-03-29 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.7. Delayed. + + * buildtypes.py (basic_types): added TIMESTAMPTZ postgres type to + the list of valid DATETIME types (incredible luck, no changes to + the parse are needed!) + + * typeobj.c (psyco_DATE_cast): fixed wrong managment of sign in + infinity. + +2002-03-27 Federico Di Gregorio <fog@debian.org> + + * configure.in (INSTALLOPTS): added AC_PROG_CPP test, now uses + AC_TRY_CPP to test for _all_ required mx includes. + +2002-03-19 Federico Di Gregorio <fog@debian.org> + + * configure.in: added check for both pg_config.h and config.h to + detect postgres version. + + * cursor.c: now None values are correctly handled when the format + string is not %s but %d, etc. + +2002-03-08 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py: added MessageDialog import suggested by + Guido. + +2002-03-07 Federico Di Gregorio <fog@debian.org> + + * psycopg.spec: added RPM specs by William K. Volkman. + + * Release 1.0.6. + + * configure.in: imported changes to allow postgres 7.2 builds from + unstable branch. + +2002-03-04 Federico Di Gregorio <fog@debian.org> + + * Release 1.0.5. + + * applied table browser patch from Andy Dustman. + +2002-02-26 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_DATE_cast): added management of infinity + values, this can be done in a better way, by accessing the + MaxDateTime and MinDateTime constants of the mx.DateTime module. + +2002-02-20 Federico Di Gregorio <fog@debian.org> + + * configure.in: Release 1.0.4. + +2002-02-12 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (DB.columns): fixed select to reenable column + expansion in table browsing. + + * ZPsycopgDA/__init__.py: removed code that made psycopg think + double. + +2002-02-11 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_mogrify_dict): removed Py_DECREF of Py_None, + references returned by PyDict_Next() are borrowed (thanks to + Michael Lausch for this one.) + +2002-02-08 Federico Di Gregorio <fog@debian.org> + + * A little bug slipped in ZPsycopgDA, releasing 1.0.3 immediately. + + * Release 1.0.2. + + * tests/check_types.py (TYPES): added check for hundredths of a + second. + +2002-02-07 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): patched to correct wrong + interpretation of hundredths of a second (patch from + A. R. Beresford, kudos!) + +2002-01-31 Federico Di Gregorio <fog@debian.org> + + * FAQ: added. + +2002-01-16 Federico Di Gregorio <fog@debian.org> + + * Preparing for release 1.0.1. + + * cursor.c (alloc_keeper): removed ALLOW_THREADS wrapper around + PQconnectdb: libpq calls crypt() that is *not* reentrant. + +2001-12-19 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_DBAPITypeObject_cmp): added check to simply + return false when two type objects are compared (type objects are + meaned to be compared to integers!) + + * typeobj.c: fixed the memory leak reported by the guys at + racemi, for real this time. (added about 5 DECREFS and 2 INCREFS, + ouch!) + +2001-12-17 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_DBAPITypeObject_cmp): fixed memory leak by + using PyTuple_GET_ITEM (we are sure the tuple has at least one + element, we built it, after all...) (many thanks to Scott Leerssen + for reporting the *exact line* for this one.) + +2001-12-13 Federico Di Gregorio <fog@debian.org> + + * cursor.c: fixed memory leak due to extra strdup (thanks + to Leonardo Rochael Almeida.) + +2001-11-14 Federico Di Gregorio <fog@debian.org> + + * Release 1.0. + + * doc/README: added explanation about guide work in progess but + examples working. + + * debian/*: lots of changes to release 1.0 in debian too. + +2001-11-12 Federico Di Gregorio <fog@debian.org> + + * RELEASE-1.0: added release file, to celebrate 1.0. + + * tests/zope/typecheck.zexp: regression test on types for zope. + +2001-11-11 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (cast_Interval): removed typecast of interval + into zope DateTime. intervals are reported as strings when using + zope DateTime and as DateTimeDeltas when using mx. + +2001-11-09 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): complete rewrite of the + interval parsing code. now we don't use sscanf anymore and all is + done with custom code in a very tight and fast loop. + +2001-11-08 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/DA.py (Connection.set_type_casts): added mx INTERVAL + type restore. + + * ZPsycopgDA/db.py (DB.query): now we return column names even if + there are no rows in the result set. also, cleaned up a little bit + the code. + +2001-11-7 Federico Di Gregorio, <fog@debian.org> + + * Makefile.pre.in: fixed small problem with zcat on True64 + (thank you stefan.) + +2001-11-06 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (DB.query): added fix for concurrent update + from Chris Kratz. + +2001-11-05 Federico Di Gregorio <fog@debian.org> + + * cursor.c: now we include postgres.h if InvalidOid is still + undefined after all other #includes. + + * README: clarified use of configure args related to python + versions. + + * aclocal.m4: patched to work with symlinks installations (thanks + to Stuart Bishop.) + + * cursor.c (_psyco_curs_execute): now reset the keeper's status to + the old value and not to BEGIN (solve problem with autocommit not + switching back.) + +2001-11-01 Federico Di Gregorio <fog@debian.org> + + * doc/examples/dt.py: added example on how to use the date and + time constructors. + + * Makefile.pre.in (dist-zope): removed dependencies on GNU install + and tar commands. Also a little general cleanup on various targets. + + * ZPsycopgDA/DA.py: fixed mx.DateTime importing. + +2001-10-31 Federico Di Gregorio <fog@debian.org> + + * typemod.c (psyco_xxxFromMx): fixed bug in argument parsing (we + weren't usigng the right type object.) + + * aclocal.m4: now builds OPT and LDFLAGS on the values of the env + variables instead of overwriting them. + + * Makefile.pre.in (CFLAGS): removed -Wall, you can add it back at + compile time with OPT="-Wall" ./configure ... + + * Setup.in (OPT): removed -Wall. + +2001-10-30 Michele Comitini <mcm@initd.net> + + * module.h: ANSI C compatibility patch from Daniel Plagge. + +2001-10-30 Federico Di Gregorio <fog@debian.org> + + * README: added common building problems and solutions. + + * configure.in: removed check for install command, already done by + james's aclocal.m4 for python. removed install-sh. removed -s from + INSTALLOPTS. + +2001-10-29 Federico Di Gregorio <fog@debian.org> + + * Makefile.pre.in (dist): removed examples/ directory from + distribution. + + * merge with cvs head. preparing to fork again on PSYCOPG-1-0 (i + admit BRANCH_1_0 was quite a silly name.) + + * doc/examples/usercast.py: now works. + + * connection.c (curs_rollbackall): fixed little bug (exposed by + the deadlock below) by changing KEEPER_READY to KEEPER_READY. + + * doc/examples/commit.py: deadlock problem solved, was the + example script, _not_ psycopg. pew... :) + + * examples/*: removed the examples moved to doc/examples/. + + * doc/examples/commit.py,dictfetch.py: moved from examples/ and + changed to work for 1.0. unfortunately commit.py locks psycopg!!! + +2001-10-24 Federico Di Gregorio <fog@debian.org> + + * modified all files neede for the 1.0 release. + + * configure.in (MXFLAGS): removed electric fence support. + + * Makefile.pre.in (dist): now we remove CVS working files before + packing the tarball. + + * tests: files in this directory are not coding examples, but + regression tests. we need a sufficient number of tests to follow + every single code path in psycopg at least once. first test is + about datatypes. + + * doc/examples: moved new example code to examples directory, old + tests and code samples will stay in examples/ until the manual will + be finished. + +2001-10-16 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): completely revised interval + casting code. (psyco_TIME_cast): we use the unix epoch when the + date is undefined. + + * cursor.c (psyco_curs_executemany): modified sanity check to + accept sequences of tuples too and not just dictionaries. + +2001-10-15 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): fixed bug caused by wrong + parsing on '1 day' (no hours, minutes and seconds.) + +2001-10-15 Michele Comitini <mcm@initd.net> + + * cursor.c (_execute): use the correct cast functions even on + retrival of binary cursors. + +2001-10-12 Federico Di Gregorio <fog@debian.org> + + * typemod.c (new_psyco_bufferobject): space not quoted anymore, + smarter formula to calculate realloc size. + + * cursor.c (psyco_curs_fetchone): removed static tuple (using + static variable in multithreaded code is *crazy*, why did i do it? + who knows...) + + * typeobj.c (psyco_init_types): exports the binary converter (will + be used in cursor.c:_execute.) + + * typeobj.h: added export of psyco_binary_cast object. + +2001-10-05 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_psyco_curs_execute): added missing Py_XDECREF on + casts list. + + * Makefile.pre.in (dist): added install-sh file to the + distribution. + + * replaced PyMem_DEL with PyObject_Del where necessary. + + * connection.c (psyco_conn_destroy): added missing + pthread_mutex_destroy on keeper lock. + +2001-10-01 Michele Comitini <mcm@initd.net> + + * typemod.c(new_psyco_bufferobject()): using unsigned char for + binary objects to avoid too many chars escaped. A quick and + simple formula to avoid memory wasting and too much reallocating + for the converted object. Needs _testing_, but it is faster. + + * cursor.c: #include <postgres.h> + + * module.h: now debugging should be active only when asked by + ./configure --enable-devel + +2001-09-29 Federico Di Gregorio <fog@debian.org> + + * cursor.c (new_psyco_cursobject): added locking of connection, + still unsure if necessary. + +2001-09-26 Federico Di Gregorio <fog@debian.org> + + * configure.in: changed DEBUG into PSYCOTIC_DEBUG, to allow other + includes (postgres.h) to use the former. better compiler checks: + inline, ansi, gcc specific extensions. removed MXMODULE: we don't + need it anymore. + + * general #include cleanup, should compile on MacOS X too. + + * typeobj.c (psyco_DATE_cast): uses sscanf. should be faster too. + (psyco_TIME_cast): dixit. + + * applied patch from Daniel Plagge (SUN cc changes.) + +2001-09-22 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py (DB._finish, DB._begin): fix for the + self.db == None problem. + +2001-09-19 Michele Comitini <mcm@initd.net> + + * typemod.c (new_psyco_bufferobject): better memory managment + (now it allocates only needed space dinamically). + + * typeobj.c (psyco_BINARY_cast): ripped a useless check, now + it assumes that binary streams come out from the db correctly + escaped. Should be a lot faster. + +2001-09-18 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): fixed interval conversion + (hours were incorrectly converted into seconds.) + +2001-09-17 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_mogrify_seq, _mogrify_dict): added check for None + value and conversion of None -> NULL (fixes bug reported by Hamish + Lawson.) + +2001-09-12 Federico Di Gregorio <fog@debian.org> + + * module.c: added handles to new date and time conversion + functions (see below.) + + * typemod.c (psyco_XXXFromMx): added conversion functions that + simply wrap the mxDateTime objects instead of creating + them. DBAPI-2.0 extension, off-curse. + +2001-09-10 Federico Di Gregorio <fog@debian.org> + + * buildtypes.py: solved hidden bug by changing from dictionary to + list, to maintain ordering of types. sometimes (and just + sometimes) the type definitions were printed unsorted, resulting + is psycopg initializing the type system using the type objects in + the wrong order. you were getting float values from an int4 + column? be happy, this is now fixed... + + * cursor.c (psyco_curs_lastoid): added method to get oid of the + last inserted row (it was sooo easy, it even works...) + +2001-09-08 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_INTERVAL_cast): added casting function for the + postgres INTERVAL and TINTERVAL types (create a DateTimeDelta + object.) + +2001-09-05 Federico Di Gregorio <fog@debian.org> + + * cursor.c: moved all calls to begin_pgconn to a single call in + _psyco_curs_execute, to leave the connection in a not-idle status + after a commit or a rollback. this should free a lot of resources + on the backend side. kudos to the webware-discuss mailing list + members and to Clark C. Evans who suggested a nice solution. + + * connection.c (curs_rollbackall, curs_commitall): removed calls + to begin_pgconn, see above. + + * module.c (initpsycopg): cleaned up mxDateTime importing; we now + use the right function from mxDateTime.h. Is not necessary anymore + to include our own mx headers. This makes psycopg to depend on + mxDateTime >= 2.0.0. + +2001-09-04 Federico Di Gregorio <fog@debian.org> + + * doc/*.tex: added documentation directory and skeleton of the + psycopg guide. + +2001-09-03 Federico Di Gregorio <fog@debian.org> + + * merged in changes from HEAD (mostly mcm fixes to binary + objects.) + + * preparing for release 0.99.6. + +2001-09-03 Michele Comitini <mcm@initd.net> + + * typemod.c: much faster Binary encoding routine. + + * typeobj.c: much faster Binary decoding routine. + +2001-08-28 Michele Comitini <mcm@initd.net> + + * typemod.c: Working binary object to feed data to bytea type + fields. + + * typeobj.c: Added BINARY typecast to extract data from + bytea type fields. + + * cursor.c: Added handling for SQL binary cursors. + +2001-08-3 Michele Comitini <mcm@initd.net> + + * cursor.c: fixed DATESTYLE problem thanx to Steve Drees. + +2001-07-26 Federico Di Gregorio <fog@debian.org> + + * Makefile.pre.in: applied change suggested by Stefan H. Holek to + clobber and distclean targets. + +2001-07-23 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py: fixed little bugs exposed by multiple select + changes, not we correctly import ListType and we don't override + the type() function with a variable. + +2001-07-17 Federico Di Gregorio <fog@debian.org> + + * configure.in: Release 0.99.5. + +2001-07-12 Federico Di Gregorio <fog@debian.org> + + * debian/* fixed some little packaging problems. + +2001-07-11 Federico Di Gregorio <fog@debian.org> + + * cursor.c, typeobj.c: removed some Py_INCREF on PyDict_SetItem + keys and values to avoid memory leaks. + +2001-07-03 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_mogrify_dict): added dictionary mogrification: all + Strings in the dictionary are translated into QuotedStrings. it + even works... (_mogrify_seq): added sequence mogrification and + code to automagically mogrify all strings passed to .execute(). + +2001-07-02 Federico Di Gregorio <fog@debian.org> + + * Release 0.99.4. + + * typemod.c: added QuotedString class and methods. + + * module.c: added QuotedString method to module psycopg. + + * typemod.c: changed Binary objects into something usefull. now + the buffer object quotes the input by translatin every char into + its octal representation. this consumes 4x memory but guarantees + that even binary data containing '\0' can go into the Binary + object. + + * typemod.h: added definition of QuotedString object. + +2001-06-28 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py, ZPsycopgDA/DABase.py: applied patch sent by + yury to fix little buglet. + +2001-06-22 Federico Di Gregorio <fog@debian.org> + + * Release 0.99.3. + + * connection.c (new_psyco_connobject): now we strdup dsn, as a fix + for the problem reported by Jack Moffitt. + + * Ok, this will be the stable branch from now on... + + * Merged in stuff from 0.99.3. About to re-branch with a better + name (BRANCH_1_0) + +2001-06-20 Federico Di Gregorio <fog@debian.org> + + * Release 0.99.3. Showstoppers for 1.0 are: + - documentation + - mxDateTime module loading + - bug reported by Yury. + + * Integrated patches from Michele: + - searching for libcrypt in configure now works + - removed memory leak in asprintf.c + +2001-06-15 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/__init__.py (initialize): applied patch from Jelle to + resolve problem with Zope 2.4.0a1. + +2001-06-14 Federico Di Gregorio <fog@debian.org> + + * configure.in: added code to check for missing functions (only + asprintf at now.) + + * asprintf.c: added compatibility code for oses that does not have + the asprintf() function. + +2001-06-10 Federico Di Gregorio <fog@debian.org> + + * Branched PSYCOPG_0_99_3. Development will continue on the cvs + HEAD, final adjustements and bugfixing should go to this newly + created branch. + +2001-06-08 Michele Comitini <mcm@initd.net> + + * ZPsycopgDA/DA.py: DateTime casts simplified and corrected + as suggested by Yury. + +2001-06-05 Federico Di Gregorio <fog@debian.org> + + * Release 0.99.2. + + * Makefile.pre.in (dist): added typemod.h and typemod.c to + distribution. + + * cursor.c (commit_pgconn, abort_pgconn, begin_pgconn): resolved + segfault reported by Andre by changing PyErr_SetString invokations + into pgconn_set_critical. the problem was that the python + interpreter simply segfaults when we touch its internal data (like + exception message) inside an ALLOW_THREADS wrapper. + + * now that we are 100% DBAPI-2.0 compliant is time for the + one-dot-o release (at last!) Para-pa-pa! This one is tagged + PSYCOPG_0_99_1 but you can call it 1.0pre1, if you better like. + (A very long text just to say 'Release 0.99.1') + + * typemod.[ch]: to reach complete DBAPI-2.0 compliance we + introduce some new objects returned by the constructors Date(), + Time(), Binary(), etc. Those objects are module-to-database only, + the type system still takes care of the database-to-python + conversion. + +2001-06-01 Federico Di Gregorio <fog@debian.org> + + * Release 0.5.5. + + * module.h: better error message when trying to commit on a + cursor derived from serialized connection. + + * ZPsycopgDA/db.py (DB.close): now self.cursor is set to None when + the connection is closed. + + * module.c (initpsycopg): added missing (sic) DBAPI module + parameters (paramstyle, apilevel, threadsafety, etc...) + +2001-05-24 Michele Comitini <mcm@initd.net> + + * ZPsycopgDA: Support for Zope's internal DateTime, option + to leave mxDateTime is available on the management interface so + to switch with little effort :). + + * cursor.c: more aggressive cleanup of postgres results + to avoid the risk of memory leaking. + + * typeobj.c, connection.c: deleted some Py_INCREF which + wasted memory. + +2001-05-18 Federico Di Gregorio <fog@debian.org> + + * Release 0.5.4. + +2001-05-17 Michele Comitini <mcm@initd.net> + + * ZPsycopgDA/db.py: The connection closed by the management + interface of zope now raises error instead of reopening itself. + + * cursor.c (psyco_curs_close): does not try to free the cursor + list, as it caused a segfault on subsequent operations on the same + cursor. + +2001-05-07 Federico Di Gregorio <fog@debian.org> + + * Release 0.5.3. + + * Merged in changes from me and mcm. + +2001-05-06 Michele Comitini <mcm@initd.net> + + * ZPsycopgDA/db.py (DB.close): Fixes a bug report by Andre + Shubert, which was still open since there was a tiny typo in + method definition. + + * ZPsycopgDA/DA.py (Connection.sql_quote__): overriding standard + sql_quote__ method to provide correct quoting (thank to Philip + Mayers and Casey Duncan for this bug report). + +2001-05-04 Federico Di Gregorio <fog@debian.org> + + * ZPsycopgDA/db.py: added .close() method (as suffested by Andre + Schubert.) + +2001-05-04 Michele Comitini <mcm@initd.net> + + * module.h: working on a closed object now raises an + InterfaceError. + + * ZPsycopgDA/db.py: fixed problems with dead connections detection. + + * ZPsycopgDA/__init__.py: corrected SOFTWARE_HOME bug for zope + icon. + +2001-05-04 Federico Di Gregorio <fog@debian.org> + + * examples/thread_test.py: now that the serialization bug is + fixed, it is clear that thread_test.py is bugged! added a commit() + after the creation of the first table to avoid loosing it on the + exception raised by the CREATE of an existing table_b. + +2001-05-03 Federico Di Gregorio <fog@debian.org> + + * connection.c (psyco_conn_cursor): reverted to old locking + policy, the new caused a nasty deadlock. apparently the multiple + connection problem has been solved as a side-effect of the other + fixes... (?!) + + * module.h: removes stdkeeper field from connobject, we don't need + it anymore. + + * cursor.c (dispose_pgconn): now sets self->keeper to NULL to + avoid decrementing the keeper refcnt two times when the cursor is + first closed and then destroyed. + + * connection.c (psyco_conn_cursor): fixed little bug in cursor + creation: now the connection is locked for the entire duration of + the cursor creation, to avoid a new cursor to be created with a + new keeper due to a delay in assigning the stdmanager cursor. + + * cursor.c: added calls to pgconn_set_critical() and to + EXC_IFCRITICAL() where we expect problems. Still segfaults but at + least raise an exception... + + * cursor.c (psyco_curs_autocommit): added exception if the + cursor's keeper is shared between more than 1 cursor. + + * module.h (EXC_IFCRITICAL): added this macro that call + pgconn_resolve_critical) on critical errors. + + * cursor.c (alloc_keeper): added check for pgres == NULL. + + * cursor.c (psyco_curs_destroy): merged psyco_curs_destroy() and + psyco_curs_close(): now both call _psyco_curs_close() and destroy + does only some extra cleanup. + +2001-05-03 Michele Comitini <mcm@initd.net> + + * ZPsycopgDA/db.py: Some cleanup to bring the zope product up to + date with the python module. Some bugs found thanks to Andre + Schubert. Now the ZDA should rely on the new serialized version + of psycopg. + + * cursor.c: while looking for problems in the ZDA some come out + here, with the inability to handle dropping connection correctly. + This leads to segfaults and is not fixed yet for lack of time. + Some problems found in cursors not willing to share the same + connection even if they should. Hopefully it should be fixed + soon. + +2001-04-26 Federico Di Gregorio <fog@debian.org> + + * fixed bug reported by Andre Schubert by adding a new cast + function for long integers (int8 postgresql type.) at now they are + converted to python LongIntegers: not sure f simply convert to + floats. + + * michele applied patch from Ivo van der Wijk to make zpsycopgda + behave better when INSTANCE_HOME != SOFTWARE_HOME. + + * cursor.c (_psyco_curs_execute): also fill the 'columns' field. + + * module.h: added a 'columns' field to cursobject, to better + support the new dictionary fetch functions (dictfetchone(), + dictfetchmany(), dictfetchall().) + + * cursor.c: added the afore-mentioned functions (function names + are not definitive, they will follow decisions on the DBAPI SIG.) + +2001-04-03 Federico Di Gregorio <fog@debian.org> + + * Release 0.5.1. + + * mcm fixed a nasty bug by correcting a typo in module.h. + +2001-03-30 Federico Di Gregorio <fog@debian.org> + + * module.c (psyco_connect): added `serialized' named argument to + the .connect() method (takes 1 or 0 and initialize the connection + to the right serialization state.) + + * Makefile.pre.in (dist): fixed little bug, a missing -f argument + to rm. + + * examples/thread_test.py: removed all extension cruft. + + * examples/thread_test_x.py: this one uses extensions like the + per-cursor commit, autocommit, etc. + + * README (psycopg): added explanation on how .serialize() works. + + * connection.c (psyco_conn_serialize): added cursor serialization + and .serialize() method on the connection object. now we are + definitely DBAPI-2.0 compliant. + +2001-03-20 Federico Di Gregorio <fog@debian.org> + + * cursor.c (_psyco_curs_execute): replaced some fields in + description with None, as suggested on the DB-SIG ML. + + * something like one hundred of little changes to allow cursors + share the same postgres connection. added connkeeper object and + pthread mutexes (both in connobject and connkeeper.) apparently it + works. this one will be 0.5.0, i think. + +2001-03-19 Michele Comitini <mcm@initd.net> + + * cursor.c: added mutexes, they do not interact well with python + threads :(. + +2001-03-16 Michele Comitini <mcm@initd.net> + + * ZPsycopgDA/db.py (ZDA): some fixes in table browsing. + +2001-03-16 Federico Di Gregorio <fog@debian.org> + + * suite/tables.postgresql (TABLE_DESCRIPTIONS): fixed some typos + introduced by copying by hand the type values from pg_type.h. + + * suite/*: added some (badly) structured code to test for + DBAPI-2.0 compliance. + + * cursor.c (pgconn_notice_callback): now the NOTICE processor only + prints NOTICEs when psycopg has been compiled with the + --enable-devel switch. + + * connection.c: removed 'autocommit' attribute, now is a method as + specified in the DBAPI-2.0 document. + +2001-03-15 Federico Di Gregorio <fog@debian.org> + + * connection.c (curs_commitall): splitted for cycle in two to + avoid the "bad snapshot" problem. + +2001-03-14 Federico Di Gregorio <fog@debian.org> + + * Release 0.4.6. + + * cursor.c (_psyco_curs_execute): fixed nasty bug, there was an + free(query) left from before the execute/callproc split. + + * Preparing for 0.4.6. + +2001-03-13 Federico Di Gregorio <fog@debian.org> + + * cursor.c (psyco_curs_execute): fixed some memory leaks in + argument parsing (the query string was not free()ed.) + (psyco_curs_callproc): implemented callproc() method on cursors. + (_psyco_curs_execute): this is the function that does the real + stuff for callproc() and execute(). + (pgconn_notice_*): added translation of notices into python + exceptions (do we really want that?) + + * configure.in: removed some cruft (old comments and strncasecmp() + check) + +2001-03-12 Federico Di Gregorio <fog@debian.org> + + * examples/thread_test.py: added moronic argument parsing: now you + can give the dsn string on the command line... :( + + * Release 0.4.5. + +2001-03-10 Federico Di Gregorio <fog@debian.org> + + * cursor.c (request_pgconn): added code to set datestyle to ISO on + new connections (many thanks to Yury <yura@vpcit.ru> for the code, + i changed it just a little bit to raise an exception on error.) + +2001-03-09 Federico Di Gregorio <fog@debian.org> + + * Release 0.4.4. + + * ZPsycopgDA/db.py: michele fixed a nasty bug here. + +2001-03-08 Federico Di Gregorio <fog@debian.org> + + * Release 0.4.3. + +2001-03-07 Federico Di Gregorio <fog@debian.org> + + * Makefile.pre.in (dist): typeobj_builtins.c included for people + without pg_type.h. if you encounter type-casting problems like + results cast to the wrong type, simply "rm typeobj_builtins.c" and + rebuild. + + * typeobj.c (psyco_*_cast): removed RETURNIFNULL() macro from all + the builtin casting functions. (psyco_STRING_cast) does not create + a new string anymore, simply Py_INCREF its argument and return it. + + * cursor.c (psyco_curs_fetchone): removed strdup() call. added + PQgetisnull() test to differentiate between real NULLs and empty + strings. + + * Removed cursor.py (mcm, put tests in examples) and fixed some + typos in the dtml code. + +2001-03-04 Michele Comitini <mcm@initd.net> + + * examples/commit_test.py: Modifications to test argument passing + and string substitution to cursor functions, nothing more. + + * ZPsycopgDA/db.py: now it exploits some of the good features of + the psycopg driver, such as connection reusage and type + comparison. Code is smaller although it handles (and + reports) errors much better. + + * cursor.c: corrected a bug that left a closed cursor in the + cursor list of the connection. Now cursors are removed from the + lists either when they are close or when they are destroyed. + Better connection (TCP) error reporting and handling. + + +2001-03-02 Federico Di Gregorio <fog@debian.org> + + * examples commit_test.py: added code to test autocommit. + + * examples/thread_test.py (ab_select): modified select thread to + test autocommit mode. + + * Release 0.4.1. + + * module.h, connection.c, cursor.c: added autocommit support. + +2001-02-28 Federico Di Gregorio <fog@debian.org> + + * Release 0.4. + +2001-02-27 Michele Comitini <mcm@initd.net> + + * cursor.py: cut some unuseful code in psyco_curs_fetchmany() and + psyco_curs_fetchall() inserted an assert in case someting goes + wrong. + +2001-02-27 Federico Di Gregorio <fog@debian.org> + + * debian/*: various changes to build both the python module and + the zope db adapter in different packages (respectively + python-psycopg and zope-psycopgda.) + + * examples/type_test.py: better and more modular tests. + + * typeobj.c: added DATE, TIME, DATETIME, BOOLEAN, BINARY and ROWID + types. (RETURNIFNULL) added NULL-test to builtin conversion + functions (using the RETURNIFNULL macro.) + +2001-02-26 Federico Di Gregorio <fog@debian.org> + + * releasing 0.3 (added NEWS file.) + +2001-02-26 Michele Comitini <mcm@initd.net> + + * cursor.c: fetchmany() some cleanup done. + + * ZPsycopgDA/db.py, ZPsycopgDA/__init__.py, : fixes to make the + ZDA work some way. WARNING WARNING WARNING the zda is still + alpha code, but we need some feed back on it so please give it + a try. + +2001-02-26 Federico Di Gregorio <fog@debian.org> + + * typeobj.c (psyco_STRING_cast): fixed bad bad bad bug. we + returned the string without coping it and the type-system was more + than happy to Py_DECREF() it and trash the whole system. fixed at + last! + + * module.h (Dprintf): added pid to every Dprintf() call, to + facilitate multi-threaded debug. + +2001-02-26 Michele Comitini <mcm@initd.net> + + * module.c: added code so that DateTime package need not to be + loaded to have mxDateTime. This should avoid clashing with + DateTime from the zope distribution. + + * cursor.c: setting error message in fetchmany when no more tuples + are left. This has to be fixed in fetch and fetchall to. + +2001-02-26 Federico Di Gregorio <fog@debian.org> + + * configure.in: stepped up version to 0.3, ready to release + tomorrow morning. added check for path to DateTime module. + + * examples/usercast_test.py: generate some random boxes and + points, select the boxes with at least one point inside and print + them converting the PostgeSQL output using a user-specified cast + object. nice. + +2001-02-24 Federico Di Gregorio <fog@debian.org> + + * cursor.c (psyco_curs_fetchone): now an error in the python + callback when typecasting results raise the correct exception. + + * typeobj.c (psyco_DBAPITypeObject_call): removed extra Py_INCREF(). + +2001-02-23 Federico Di Gregorio <fog@debian.org> + + * replaced every single instance of the string 'pgpy' with 'psyco' + (this was part of the general cleanup.) + + * type_test.py: added this little test program to the distribution + (use the new_type() method to create new instances of the type + objects.) + + * typeobj.c: general cleanup. fixed some bugs related to + refcounting (again!) + + * cursor.c: general cleanup. (request_pgconn) simplified by adding + a support function (_extract_pgconn.) + + * connection.c: general cleanup. replaced some ifs with asserts() + in utility functions when errors depend on programming errors and + not on runtime exceptions. (pgpy_conn_destroy) fixed little bug + when deleting available connections from the list. + + * module.h: general cleanup. + + * typeobj.h: general cleanup, better comments, made some function + declarations extern. + + * module.c: general cleanup, double-checked every function for + memory leaks. (pgpy_connect) removed unused variable 'connection'. + +2001-02-22 Federico Di Gregorio <fog@debian.org> + + * typeobj.c: fixed lots of bugs, added NUMBER type object. now the + basic tests in type_test.py work pretty well. + + * cursor.c (pgpy_curs_fetchmany): fixed little bug, fetchmany() + reported one less row than available. + + * fixed lots of bugs in typeobj.c, typeobj.h, cursor.c. apparently + now the type system works. it is time to clean up things a little + bit. + +2001-02-21 Federico Di Gregorio <fog@debian.org> + + * typeobj.c: separated type objects stuff from module.c + + * typeobj.h: separated type objects stuff from module.h + +2001-02-19 Federico Di Gregorio <fog@debian.org> + + * cursor.c (pgpy_curs_fetchmany): now check size and adjust it to + be lesser or equal than the nuber of available rows. + +2001-02-18 Michele Comitini <mcm@initd.net> + + * module.c, module.h: added optional args maxconn and minconn to + connection functions + + * cursor.c: better error checking in request_pgconn. + + * connection.c: changed new_connect_obj to take as optional args + maxconn and minconn. Added the corresponding ro attributes to + connection objects. + + * cursor.py: added some code to stress test cursor reusage. + + * cursor.c: some fixes on closed cursors. + + * connection.c: corrections on some assert calls. + +2001-02-16 Federico Di Gregorio <fog@debian.org> + + * configure.in: added --enable-priofile sqitch. changed VERSION to + 0.2: preparing for a new release. + + * cursor.c: added a couple of asserts. + +2001-02-16 Michele Comitini <mcm@initd.net> + + * cursor.c, connection.c: fixed the assert problem: assert must + take just the value to be tested! no assignemente must be done in + the argument of assert() otherwise is wiped when NDEBUG is set. + + * module.h: some syntax error fixed. Error in allocating a tuple + corrected in macro DBAPITypeObject_NEW(). + + * module.c: pgpy_DBAPITypeObject_init() is not declared static anymore. + + * cursor.c: executemany() now does not create and destroy tuples + for each list item, so it is much faster. + +2001-02-14 Michele Comitini <mcm@initd.net> + + * cursor.c: added again Py_DECREF on the cpcon after disposing + it. assert() with -DNDEBUG makes the driver segfault while it + should not. + + +2001-02-13 Federico Di Gregorio <fog@debian.org> + + * some of the memory leak were memprof errors, bleah. resumed some + old code, fixed segfault, fixed other bugs, improved speed. almost + ready for a new release. + + * connection.c (pgpy_conn_destroy): replaced some impossible ifs + with aseert()s. + + * cursor.c (pgpy_curs_close): added Py_DECREF() to + self->descritpion to prevent a memory leak after an execute(). + + * connection.c (pgpy_conn_destroy): always access first element of + lists inside for cycles because removing items from the list makes + higher indices invalid. + + * cursor.c (dispose_pgconn): fixed memory leak, there was a + missing Py_DECREF() after the addition of the C object wrapping + the postgresql connection to the list of available connections. + + * cursor.c (dispose_pgconn): fixed another memory leak: an + orphaned cursor should call PQfinish() on its postgresql + connection because it has no python connection to give the + postgresql ine back. + + * cursor.c (pgpy_curs_execute): added Py_DECREF() of description + tuple after adding it to self->description. this one fixes the + execute() memory leak. + + * cursor.c (pgpy_curs_fetchall): added missing Py_DECREF() on row + data (obtained from fetchone().) this fixes the last memory leak. + (thread_test.py now runs without leaking memory!) + +2001-02-12 Federico Di Gregorio <fog@debian.org> + + * INSTALL: removed wok cruft from head of this file. + + * debian/rules: debianized the sources. python-psycopg is about to + enter debian. mxDateTime header locally included until the + maintainer of python-mxdatetime includes them in his package + (where they do belong.) + + * autogen.sh: added option --dont-run-configure. + +2001-02-09 Federico Di Gregorio <fog@debian.org> + + * module.c (initpsycopg): changed name of init function to match + new module name (also changed all the exception definitions.) + + * README: updated psycopg description (we have a new name!) + + * Ready for 0.1 release. + +2001-02-07 Michele Comitini <mcm@initd.net> + + * cursor.c: now executemany takes sequences and not just + tuples + +2001-02-07 Federico Di Gregorio <fog@debian.org> + + * Makefile.pre.in: now dist target includes test programs + (thread_test.py) and README and INSTALL files. + + * configure.in: changed --with-devel to --enable-devel. little + cosmetical fixes to the option management. + + * connection.c, module.c, cursor.c, module.h: removed 'postgres/' + from #include directive. it is ./configure task to find the right + directory. + + * thread_test.py: added thread testing program. + +2001-02-07 Michele Comitini <mcm@initd.net> + + * cursor.c: added code to allow threads during PQexec() calls. + + * cursor.c: added begin_pgconn to rollback() and commit() + so that the cursror is not in autocommit mode. + + * cursor.c: added rollback() and commit() methods to cursor + objects. + + +2001-02-07 Federico Di Gregorio <fog@debian.org> + + * connection.c (pgpy_conn_destroy): always delete item at index + 0 and not i (because items shift in the list while deleting and + accessing items at len(list)/2 segfaults.) + +2001-02-07 Michele Comitini <mcm@initd.net> + + * connection.c: added some more checking to avoid + clearing of already cleared pgresults. Calling curs_closall() + in conn_destroy() since cursors have to live even without + their parent connection, otherwise explicit deletion of + object referencing to those cursors can cause arbitrary code + to be executed. + + * cursor.c: some more checking to avoid trying to close + already close pgconnections. + +2001-02-06 Federico Di Gregorio <fog@debian.org> + + * Makefile.pre.in (CFLAGS): added -Wall to catch bad programming + habits. + + * cursor.c, connection.c: lots of fixes to the destroy stuff. now + all the cursor are destroyed *before* the connection goes away. + + * cursor.c (request_pgconn): another idiot error done by not + replacing dsn with owner_conn->dsn. fixed. + (dispose_pgconn): commented if to guarantee that the connection is + returned to the pool of available connections. + + * merged changes done by mcm. + + * cursor.c: general cleanup and better debugging/error + messages. changed xxx_conn into xxx_pgconn where still + missing. some pretty big changes to the way pgconn_request() + allocates new connections. + + * connection.c: removed all 'register' integers. obsolete, gcc + does a much better job optimizing cycles than a programmer + specifying how to use registers. + + * module.h: some general cleanup and better definition of DPrintf + macro. now the DEBUG variable can be specified at configure time by + the --with-devel switch to ./configure. + +2001-02-02 Michele Comitini <mcm@initd.net> + + * cursor.c (Repository): Added functions for managing a connection + pool. Segfaults. + + * configure.in (Repository): removed check for mxdatetime headers. + +2001-01-24 Federico Di Gregorio <fog@debian.org> + + * first checkout from shinning new init.d cvs. + + * autotoolized build system. note that the mx headers are missing + from the cvs, you should get them someplace else (this is the + right way to do it, just require the headers in the configure + script.) + +2001-01-21 Michele Comitini <mcm@initd.net> + + * cursor.c (Repository): commit, abort, begin functions now check + the right exit status of the command. + + * connection.c (Repository): working commit() and rollback() + methods. + +2001-01-20 Michele Comitini <mcm@initd.net> + + * module.h (Repository): added member to cursor struct to handle + queries without output tuples. + + * cursor.c (Repository): new working methods: executemany, + fetchone, fetchmany, fetchall. + +2001-01-18 Michele Comitini <mcm@initd.net> + + * cursor.c (Repository): close working. destroy calling close. + close frees pg structures correctly. + + * connection.c (Repository): close method working. destroy seems + working. + +2001-01-17 Michele Comitini <mcm@initd.net> + + * cursor.c (Repository): now each python cursor has its own + connection. Each cursor works in a transaction block. + + * connection.c (Repository): added cursor list to connection + object + +2001-01-14 Michele Comitini <mcm@initd.net> + + * cursor.c (Repository): Beginning of code to implement cursor + functionalities as specified in DBA API 2.0, through the use of + transactions not cursors. + + * connection.c (Repository): Added some error checking code for pg + connection (will be moved to cursor?). + +2001-01-13 Michele Comitini <mcm@initd.net> + + * connection.c (Repository): Added error checking in connection + code to fail if connection to the db could not be opened. + + * module.h (Repository): New macro to help creating + DBAPITypeObjects. + + * module.c (Repository): DBAPITypeObject __cmp__ function is now + very simplified using recursion. + + * module.h (Repository): "DBAPIObject" changed to + "DBAPITypeObject". + + * module.c (Repository): Fixes for coerce function of DBAPIObjects + by Federico Di Gregorio <fog@initd.net>. + (Repository): Clean up and better naming for DBAPITypeObjects. + +2001-01-08 Michele Comitini <mcm@initd.net> + + * module.c (Repository): Corrected the exception hierarcy + + * connection.c (Repository): Begun to use the connection objects + of libpq + +2001-01-07 Michele Comitini <mcm@initd.net> + + * module.c (Repository): Added the Date/Time functions. + +2001-01-06 Michele Comitini <mcm@initd.net> + + * cursor.c (Repository): Skeleton of cursor interface. All + methods and attributes of cursor objects are now available + in python. They do nothing now. + +2001-01-05 Michele Comitini <mcm@initd.net> + + * module.c (Repository): Test version; module loaded with + exception defined. + +2001-01-05 Michele Comitini <mcm@initd.net> + + * Setup.in (Repository): Setup file. + + * Makefile.pre.in (Repository): from the python source. + +2001-01-05 Michele Comitini <mcm@initd.net> + + * module.c: Written some code for defining exceptions. + + * module.h: Static variable for exceptions. + +2001-01-04 Michele Comitini <mcm@initd.net> + + * Changelog: pre-release just a few prototypes to get started. + + diff --git a/doc/HACKING b/doc/HACKING new file mode 100644 index 0000000..f60474c --- /dev/null +++ b/doc/HACKING @@ -0,0 +1,43 @@ +General information +******************* + +Some help to people wanting to hack on psycopg. First of all, note that +*every* function in the psycopg module source code is prefixed by one of the +following words: + + psyco is used for function directly callable from python (i.e., functions + in the psycopg module itself.) the only notable exception is the + source code for the module itself, that uses "psyco" even for C-only + functions. + + conn is used for functions related to connection objects. + + curs is used for functions related to cursor objects. + + typecast is used for typecasters and utility function related to + typecaster creation and registration. + +Pythonic definition of types and functions available from python are defined +in *_type.c files. Internal functions, callable only from C are located in +*_int.c files and extensions to the DBAPI can be found in the *_ext.c files. + + +Patches +******* + +If you submit a patch, please send a diff generated with the "-u" switch. +Also note that I don't like that much cosmetic changes (like renaming +already existing variables) and I will rewrap the patch to 78 columns +anyway, so it is much better if you do that beforehand. + + +The type system +*************** + +Simple types, like integers and strings, are converted to python base types +(the conversion functions are in typecast_base.c). Complex types are +converted to ad-hoc types, defined in the typeobj_*.{c,h} files. The +conversion function are in the other typecast_*.c files. typecast.c defines +the basic utility functions (available through the psycopg module) used when +defining new typecasters from C and python. + diff --git a/doc/SUCCESS b/doc/SUCCESS new file mode 100644 index 0000000..9ae91f5 --- /dev/null +++ b/doc/SUCCESS @@ -0,0 +1,114 @@ +From: Jack Moffitt <jack@xiph.org> +To: Psycopg Mailing List <psycopg@lists.initd.org> +Subject: Re: [Psycopg] preparing for 1.0 +Date: 22 Oct 2001 11:16:21 -0600 + +www.vorbis.com is serving from 5-10k pages per day with psycopg serving +data for most of that. + +I plan to use it for several of our other sites, so that number will +increase. + +I've never had a single problem (that wasn't my fault) besides those +segfaults, and those are now gone as well, and I've been using psycopg +since June (around 0.99.2?). + +jack. + + +From: Yury Don <gercon@vpcit.ru> +To: Psycopg Mailing List <psycopg@lists.initd.org> +Subject: Re: [Psycopg] preparing for 1.0 +Date: 23 Oct 2001 09:53:11 +0600 + +We use psycopg and psycopg zope adapter since fisrt public +release (it seems version 0.4). Now it works on 3 our sites and in intranet +applications. We had few problems, but all problems were quckly +solved. The strong side of psycopg is that it's code is well organized +and easy to understand. When I found a problem with non-ISO datestyle in first +version of psycopg, it took for me 15 or 20 minutes to learn code and +to solve the problem, even thouth my knowledge of c were poor. + +BTW, segfault with dictfetchall on particular data set (see [Psycopg] +dictfetchXXX() problems) disappeared in 0.99.8pre2. + +-- +Best regards, +Yury Don + + +From: Tom Jenkins <tjenkins@devis.com> +To: Federico Di Gregorio <fog@debian.org> +Cc: Psycopg Mailing List <psycopg@lists.initd.org> +Subject: Re: [Psycopg] preparing for 1.0 +Date: 23 Oct 2001 08:25:52 -0400 + +The US Govt Department of Labor's Office of Disability Employment +Policy's DisabilityDirect website is run on zope and zpsycopg. + + +From: Scott Leerssen <sleerssen@racemi.com> +To: Federico Di Gregorio <fog@debian.org> +Subject: Re: [Psycopg] preparing for 1.0 +Date: 23 Oct 2001 09:56:10 -0400 + +Racemi's load management software infrastructure uses psycopg to handle +complex server allocation decisions, plus storage and access of +environmental conditions and accounting records for potentially +thousands of servers. Psycopg has, to this point, been the only +Python/PostGreSQL interface that could handle the scaling required for +our multithreaded applications. + +Scott + + +From: Andre Schubert <andre.schubert@geyer.kabeljournal.de> +To: Federico Di Gregorio <fog@debian.org> +Cc: Psycopg Mailing List <psycopg@lists.initd.org> +Subject: Re: [Psycopg] preparing for 1.0 +Date: 23 Oct 2001 11:46:07 +0200 + +i have changed the psycopg version to 0.99.8pre2 on all devel-machines +and all segfaults are gone. after my holiday i wil change to 0.99.8pre2 +or 1.0 on our production-server. +this server contains several web-sites which are all connected to +postgres over ZPsycopgDA. + +thanks as + + +From: Fred Wilson Horch <fhorch@ecoaccess.org> +To: <psycopg@lists.initd.org> +Subject: [Psycopg] Success story for psycopg +Date: 23 Oct 2001 10:59:17 -0400 + +Due to various quirks of PyGreSQL and PoPy, EcoAccess has been looking for +a reliable, fast and relatively bug-free Python-PostgreSQL interface for +our project. + +Binary support in psycopg, along with the umlimited tuple size in +PostgreSQL 7.1, allowed us to quickly prototype a database-backed file +storage web application, which we're using for file sharing among our +staff and volunteers. Using a database backend instead of a file system +allows us to easily enrich the meta-information associated with each file +and simplifies our data handling routines. + +We've been impressed by the responsiveness of the psycopg team to bug +reports and feature requests, and we're looking forward to using psycopg +as the Python interface for additional database-backed web applications. + +Keep up the good work! +-- +Fred Wilson Horch mailto:fhorch@ecoaccess.org +Executive Director, EcoAccess http://ecoaccess.org/ + + +From: Damon Fasching <fasching@design.lbl.gov> +To: Michele Comitini <mcm@glisco.it> +Cc: fog@debian.org +Subject: Re: How does one create a database within Python using psycopg? +Date: 25 Feb 2002 17:39:41 -0800 + +[snip] +btw I checked out 4 different Python-PostgreSQL packages. psycopg is the +only one which built and imported w/o any trouble! (At least for me.) diff --git a/doc/TODO b/doc/TODO new file mode 100644 index 0000000..b20b276 --- /dev/null +++ b/doc/TODO @@ -0,0 +1,33 @@ +TODO list for psycopg 2 or later +******************************** + +Move items to the DONE section only after writing a test for the +implementation. Also add a note on how the item was resolved. +(Obviously I was joking about the test..) + +* Find a better way to compile the type-casting code instead of including it + in typecast.c directy. (Including is not that bad, but the need to touch + psycopg/typecast.c every time is bad bad bad.) + +* executemany() should _not_ take the async flag, remove it and force multiple + queries to be synchronous. + +* Fix all the docstrings. + +* Support the protocols API fully. + +* Unify the common code in typecast_datetime.c and typecast_mxdatetime.c. + +* Port typecasters to new-style classes. + +* Write a complete postgresql<->python encodings table. + +* Implement binary typecasters (should be easy, but it will take time.) + +DONE +==== + +* Convert type-casters to new-style types in Python 2.2+. + +* callproc() never worked, fix it or remove it and raise right exception. + [Removed callproc code, now an exception is raised.] diff --git a/examples/binary.py b/examples/binary.py new file mode 100644 index 0000000..3b543e5 --- /dev/null +++ b/examples/binary.py @@ -0,0 +1,88 @@ +# binary.py - working with binary data +# +# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below tis line (except for experimenting) + +import sys, psycopg + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +print "Encoding for this connection is", conn.encoding + +curs = conn.cursor() +try: + curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)") +except: + conn.rollback() + curs.execute("DROP TABLE test_binary") + curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)") +conn.commit() + +# first we try two inserts, one with an explicit Binary call and the other +# using a buffer on a file object. + +data1 = {'id':1, 'name':'somehackers.jpg', + 'img':psycopg.Binary(open('somehackers.jpg').read())} +data2 = {'id':2, 'name':'whereareyou.jpg', + 'img':buffer(open('whereareyou.jpg').read())} + +curs.execute("""INSERT INTO test_binary + VALUES (%(id)d, %(name)s, %(img)s)""", data1) +curs.execute("""INSERT INTO test_binary + VALUES (%(id)d, %(name)s, %(img)s)""", data2) + +# now we try to extract the images as simple text strings + +print "Extracting the images as strings..." +curs.execute("SELECT * FROM test_binary") + +for row in curs.fetchall(): + name, ext = row[1].split('.') + new_name = name + '_S.' + ext + print " writing %s to %s ..." % (name+'.'+ext, new_name), + open(new_name, 'wb').write(row[2]) + print "done" + print " python type of image data is", type(row[2]) + +# extract exactly the same data but using a binary cursor + +print "Extracting the images using a binary cursor:" + +curs.execute("""DECLARE zot CURSOR FOR + SELECT img, name FROM test_binary FOR READ ONLY""") +curs.execute("""FETCH ALL FROM zot""") + +for row in curs.fetchall(): + name, ext = row[1].split('.') + new_name = name + '_B.' + ext + print " writing %s to %s ..." % (name+'.'+ext, new_name), + open(new_name, 'wb').write(row[0]) + print "done" + print " python type of image data is", type(row[0]) + +# this rollback is requires because we can't drop a table with a binary cusor +# declared and still open +conn.rollback() + +curs.execute("DROP TABLE test_binary") +conn.commit() + +print "\nNow try to load the new images, to check it worked!" diff --git a/examples/cursor.py b/examples/cursor.py new file mode 100644 index 0000000..2205278 --- /dev/null +++ b/examples/cursor.py @@ -0,0 +1,64 @@ +# cursor.py - how to subclass the cursor type +# +# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below this line (except for experimenting) + +import sys +import psycopg +import psycopg.extensions + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dsn:", DSN + +conn = psycopg.connect(DSN) +print "Encoding for this connection is", conn.encoding + + +class NoDataError(psycopg.ProgrammingError): + """Exception that will be raised by our cursor.""" + pass + +class Cursor(psycopg.extensions.cursor): + """A custom cursor.""" + + def fetchone(self): + """Like fetchone but raise an exception if no data is available. + + Note that to have .fetchmany() and .fetchall() to raise the same + exception we'll have to override them too; even if internally psycopg + uses the same function to fetch rows, the code path from Python is + different. + """ + d = psycopg.extensions.cursor.fetchone(self) + if d is None: + raise NoDataError("no more data") + return d + +curs = conn.cursor(factory=Cursor) +curs.execute("SELECT 1 AS foo") +print "Result of fetchone():", curs.fetchone() + +# now let's raise the exception +try: + curs.fetchone() +except NoDataError, err: + print "Exception caugth:", err + +conn.rollback() diff --git a/examples/dialtone.py b/examples/dialtone.py new file mode 100644 index 0000000..9e453c3 --- /dev/null +++ b/examples/dialtone.py @@ -0,0 +1,145 @@ +""" +This example/recipe has been contributed by Valentino Volonghi (dialtone) + +Mapping arbitrary objects to a PostgreSQL database with psycopg2 + +- Problem + +You need to store arbitrary objects in a PostgreSQL database without being +intrusive for your classes (don't want inheritance from an 'Item' or +'Persistent' object). + +- Solution +""" + +from datetime import datetime + +import psycopg +from psycopg.extensions import adapters, adapt + +try: sorted() +except NameError: + def sorted(seq): + seq.sort() + return seq + +# Here is the adapter for every object that we may ever need to +# insert in the database. It receives the original object and does +# its job on that instance + +class ObjectMapper(object): + def __init__(self, orig): + self.orig = orig + self.tmp = {} + self.items, self.fields = self._gatherState() + + def _gatherState(self): + adaptee_name = self.orig.__class__.__name__ + fields = sorted([(field, getattr(self.orig, field)) + for field in persistent_fields[adaptee_name]]) + items = [] + for item, value in fields: + items.append(item) + return items, fields + + def getTableName(self): + return self.orig.__class__.__name__ + + def getMappedValues(self): + tmp = [] + for i in self.items: + tmp.append("%%(%s)s"%i) + return ", ".join(tmp) + + def getValuesDict(self): + return dict(self.fields) + + def getFields(self): + return self.items + + def generateInsert(self): + qry = "INSERT INTO" + qry += " " + self.getTableName() + " (" + qry += ", ".join(self.getFields()) + ") VALUES (" + qry += self.getMappedValues() + ")" + return qry, self.getValuesDict() + +# Here are the objects +class Album(object): + id = 0 + def __init__(self): + self.creation_time = datetime.now() + self.album_id = self.id + Album.id = Album.id + 1 + self.binary_data = buffer('12312312312121') + +class Order(object): + id = 0 + def __init__(self): + self.items = ['rice','chocolate'] + self.price = 34 + self.order_id = self.id + Order.id = Order.id + 1 + +adapters.update({Album: ObjectMapper, Order: ObjectMapper}) + +# Describe what is needed to save on each object +# This is actually just configuration, you can use xml with a parser if you +# like to have plenty of wasted CPU cycles ;P. + +persistent_fields = {'Album': ['album_id', 'creation_time', 'binary_data'], + 'Order': ['order_id', 'items', 'price'] + } + +print adapt(Album()).generateInsert() +print adapt(Album()).generateInsert() +print adapt(Album()).generateInsert() +print adapt(Order()).generateInsert() +print adapt(Order()).generateInsert() +print adapt(Order()).generateInsert() + +""" +- Discussion + +Psycopg 2 has a great new feature: adaptation. The big thing about +adaptation is that it enable the programmer to glue most of the +code out there without many difficulties. + +This recipe tries to focus the attention on a way to generate SQL queries to +insert completely new objects inside a database. As you can see objects do +not know anything about the code that is handling them. We specify all the +fields that we need for each object through the persistent_fields dict. + +The most important line of this recipe is this one: + adapters.update({Album: ObjectMapper, Order: ObjectMapper}) + +In this line we notify the system that when we call adapt with an Album instance +as an argument we want it to istantiate ObjectMapper passing the Album instance +as argument (self.orig in the ObjectMapper class). + +adapters is just a python dict with a Key that represents the type +we need to adapt from and a value that is the adapter +which will adapt to the wanted interface. + +The output is something like this (for each call to generateInsert): + +('INSERT INTO Album (album_id, binary_data, creation_time) VALUES + (%(album_id)s, %(binary_data)s, %(creation_time)s)', + + {'binary_data': <read-only buffer for 0x402de070, ...>, + 'creation_time': datetime.datetime(2004, 9, 10, 20, 48, 29, 633728), + 'album_id': 1} +) + +This is a tuple of {SQL_QUERY, FILLING_DICT}, and all the quoting/converting +stuff (from python's datetime to postgres s and from python's buffer to +postgres' blob) is handled with the same adaptation process hunder the hood +by psycopg2. + +At last, just notice that ObjectMapper is working for both Album and Order +instances without any glitches at all, and both classes could have easily been +coming from closed source libraries or C coded ones (which are not easily +modified), whereas a common pattern in todays ORMs or OODBs is to provide +a basic 'Persistent' object that already knows how to store itself in the +database. +""" diff --git a/examples/dt.py b/examples/dt.py new file mode 100644 index 0000000..f285c7a --- /dev/null +++ b/examples/dt.py @@ -0,0 +1,91 @@ +# datetime.py - example of using date and time types +# +# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below tis line (except for experimenting) + +import sys, psycopg +import mx.DateTime +import datetime + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +curs = conn.cursor() + +try: + curs.execute("""CREATE TABLE test_dt (k int4, d date, t time, dt timestamp, + z interval)""") +except: + conn.rollback() + curs.execute("DROP TABLE test_dt") + curs.execute("""CREATE TABLE test_dt (k int4, + d date, t time, dt timestamp, + z interval)""") +conn.commit() + +# build and insert some data using mx.DateTime +mx1 = ( + 1, + mx.DateTime.Date(2004, 10, 19), + mx.DateTime.Time(0, 11, 17.015), + mx.DateTime.Timestamp(2004, 10, 19, 0, 11, 17.5), + mx.DateTime.DateTimeDelta(13, 15, 17, 59.9)) + +print "Inserting mx.DateTime values..." +curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", mx1) + +# build and insert some values using the datetime adapters +dt1 = ( + 2, + datetime.date(2004, 10, 19), + datetime.time(0, 11, 17, 15000), + datetime.datetime(2004, 10, 19, 0, 11, 17, 500000), + datetime.timedelta(13, 15*3600+17*60+59, 900000)) + +print "Inserting Python datetime values..." +curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", dt1) + +# now extract the row from database and print them +print "Extracting values inserted with mx.DateTime wrappers:" +curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 1") +for n, x in zip(mx1[1:], curs.fetchone()): + try: + # this will work only is psycopg has been compiled with datetime + # as the default typecaster for date/time values + s = repr(n) + "\n -> " + repr(x) + "\n -> " + x.isoformat() + except: + s = repr(n) + "\n -> " + repr(x) + "\n -> " + str(x) + print s +print + +print "Extracting values inserted with Python datetime wrappers:" +curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 2") +for n, x in zip(dt1[1:], curs.fetchone()): + try: + # this will work only is psycopg has been compiled with datetime + # as the default typecaster for date/time values + s = repr(n) + "\n -> " + repr(x) + "\n -> " + x.isoformat() + except: + s = repr(n) + "\n -> " + repr(x) + "\n -> " + str(x) + print s +print + +curs.execute("DROP TABLE test_dt") +conn.commit() diff --git a/examples/encoding.py b/examples/encoding.py new file mode 100644 index 0000000..1506252 --- /dev/null +++ b/examples/encoding.py @@ -0,0 +1,102 @@ +# encoding.py - how to change client encoding (and test it works) +# -*- encoding: latin-1 -*- +# +# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below this line (except for experimenting) + +import sys, psycopg +import psycopg.extensions + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +print "Initial encoding for this connection is", conn.encoding + +print "\n** This example is supposed to be run in a UNICODE terminal! **\n" + +print "Available encodings:" +for a, b in psycopg.extensions.encodings.items(): + print " ", a, "<->", b + +print "Using STRING typecaster" +print "Setting backend encoding to LATIN1 and executing queries:" +conn.set_client_encoding('LATIN1') +curs = conn.cursor() +curs.execute("SELECT %s::TEXT AS foo", ('àèìòù',)) +x = curs.fetchone()[0] +print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x) +curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',)) +x = curs.fetchone()[0] +print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x) + +print "Setting backend encoding to UTF8 and executing queries:" +conn.set_client_encoding('UNICODE') +curs = conn.cursor() +curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),)) +x = curs.fetchone()[0] +print " ->", x, type(x) +curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',)) +x = curs.fetchone()[0] +print " ->", x, type(x) + +print "Using UNICODE typecaster" +psycopg.extensions.register_type(psycopg.extensions.UNICODE) + +print "Setting backend encoding to LATIN1 and executing queries:" +conn.set_client_encoding('LATIN1') +curs = conn.cursor() +curs.execute("SELECT %s::TEXT AS foo", ('àèìòù',)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) +curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) + +print "Setting backend encoding to UTF8 and executing queries:" +conn.set_client_encoding('UNICODE') +curs = conn.cursor() +curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) +curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) + +print "Executing full UNICODE queries" + +print "Setting backend encoding to LATIN1 and executing queries:" +conn.set_client_encoding('LATIN1') +curs = conn.cursor() +curs.execute(u"SELECT %s::TEXT AS foo", ('àèìòù',)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) +curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù',)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) + +print "Setting backend encoding to UTF8 and executing queries:" +conn.set_client_encoding('UNICODE') +curs = conn.cursor() +curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) +curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù',)) +x = curs.fetchone()[0] +print " ->", x.encode('utf-8'), ":", type(x) diff --git a/examples/lastrowid.py b/examples/lastrowid.py new file mode 100644 index 0000000..aa57850 --- /dev/null +++ b/examples/lastrowid.py @@ -0,0 +1,59 @@ +# lastrowid.py - example of using .lastrowid attribute +# +# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below tis line (except for experimenting) + +import sys, psycopg + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +curs = conn.cursor() + +try: + curs.execute("CREATE TABLE test_oid (name text, surname text)") +except: + conn.rollback() + curs.execute("DROP TABLE test_oid") + curs.execute("CREATE TABLE test_oid (name text, surname text)") +conn.commit() + +data = ({'name':'Federico', 'surname':'Di Gregorio'}, + {'name':'Pierluigi', 'surname':'Di Nunzio'}) + +curs.execute("""INSERT INTO test_oid + VALUES (%(name)s, %(surname)s)""", data[0]) + +foid = curs.lastrowid +print "Oid for %(name)s %(surname)s" % data[0], "is", foid + +curs.execute("""INSERT INTO test_oid + VALUES (%(name)s, %(surname)s)""", data[1]) +moid = curs.lastrowid +print "Oid for %(name)s %(surname)s" % data[1], "is", moid + +curs.execute("SELECT * FROM test_oid WHERE oid = %d", (foid,)) +print "Oid", foid, "selected %s %s" % curs.fetchone() + +curs.execute("SELECT * FROM test_oid WHERE oid = %d", (moid,)) +print "Oid", moid, "selected %s %s" % curs.fetchone() + +curs.execute("DROP TABLE test_oid") +conn.commit() diff --git a/examples/mogrify.py b/examples/mogrify.py new file mode 100644 index 0000000..ad6d11c --- /dev/null +++ b/examples/mogrify.py @@ -0,0 +1,47 @@ +# mogrify.py - test all possible type mogrifications +# -*- encoding: latin1 -*- +# +# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below this line (except for experimenting) + +import sys, psycopg + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN + +conn = psycopg.connect(DSN) +print "Encoding for this connection is", conn.encoding + +curs = conn.cursor() +curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'}) +curs.execute("SELECT %(foo)s AS foo", {'foo':None}) +curs.execute("SELECT %(foo)s AS foo", {'foo':True}) +curs.execute("SELECT %(foo)f AS foo", {'foo':42}) +curs.execute("SELECT %(foo)s AS foo", {'foo':u'yattà!'}) +curs.execute("SELECT %(foo)s AS foo", {'foo':u'bar'}) + +print curs.mogrify("SELECT %(foo)s AS foo", {'foo':'bar'}) +print curs.mogrify("SELECT %(foo)s AS foo", {'foo':None}) +print curs.mogrify("SELECT %(foo)s AS foo", {'foo':True}) +print curs.mogrify("SELECT %(foo)f AS foo", {'foo':42}) +print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'yattà!'}) +print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'bar'}) + +conn.rollback() diff --git a/examples/myfirstrecipe.py b/examples/myfirstrecipe.py new file mode 100644 index 0000000..8457c67 --- /dev/null +++ b/examples/myfirstrecipe.py @@ -0,0 +1,117 @@ +""" +Using a tuple as a bound variable in "SELECT ... IN (...)" clauses +in PostgreSQL using psycopg 2 + +Some time ago someone asked on the psycopg mailing list how to have a +bound variable expand to the right SQL for an SELECT IN clause: + + SELECT * FROM atable WHERE afield IN (value1, value2, value3) + +with the values to be used in the IN clause to be passed to the cursor +.execute() method in a tuple as a bound variable, i.e.: + + in_values = ("value1", "value2", "value3") + curs.execute("SELECT ... IN %s", (in_values,)) + +psycopg 1 does support typecasting from Python to PostgreSQL (and back) +only for simple types and this problem has no elegant solution (short or +writing a wrapper class returning the pre-quoted text in an __str__ +method. + +But psycopg 2 offers a simple and elegant solution by partially +implementing the Object Adaptation from PEP 246. psycopg 2 (still in +beta and currently labeled as 1.99.9) moves the type-casting logic into +external adapters and a somehow broken adapt() function. + +While the original adapt() takes 3 arguments, psycopg's one only takes +1: the bound variable to be adapted. The result is an object supporting +a not-yet well defined protocol that we can call IPsycopgSQLQuote: + + class IPsycopgSQLQuote: + + def getquoted(self): + "Returns a quoted string representing the bound variable." + + def getbinary(self): + "Returns a binary quoted string representing the bound variable." + + def getbuffer(self): + "Returns the wrapped object itself." + + __str__ = getquoted + +Then one of the functions (usually .getquoted()) is called by psycopg at +the right time to obtain the right, sql-quoted representation for the +corresponding bound variable. + +The nice part is that the default, built-in adapters, derived from +psycopg 1 tyecasting code can be overridden by the programmer, simply +replacing them in the psycopg.extensions.adapters dictionary. + +Then the solution to the original problem is now obvious: write an +adapter that adapts tuple objects into the right SQL string, by calling +recursively adapt() on each element. + +Note: psycopg 2 adapter code is still very young and will probably move +to a more 'standard' (3 arguments) implementation for the adapt() +function; as long as that does not slow down too much query execution. + +Psycopg 2 development can be tracked on the psycopg mailing list: + + http://lists.initd.org/mailman/listinfo/psycopg + +and on the psycopg 2 wiki: + + http://wiki.initd.org/Projects/Psycopg2 + +""" + +import psycopg +import psycopg.extensions +from psycopg.extensions import adapt as psycoadapt + +class AsIs(object): + """An adapter that just return the object 'as is'. + + psycopg 1.99.9 has some optimizations that make impossible to call + adapt() without adding some basic adapters externally. This limitation + will be lifted in a future release. + """ + def __init__(self, obj): + self.__obj = obj + def getquoted(self): + return self.__obj + +class SQL_IN(object): + """Adapt a tuple to an SQL quotable object.""" + + def __init__(self, seq): + self._seq = seq + + def getquoted(self): + # this is the important line: note how every object in the + # list is adapted and then how getquoted() is called on it + + qobjs = [str(psycoadapt(o).getquoted()) for o in self._seq] + + return '(' + ', '.join(qobjs) + ')' + + __str__ = getquoted + +# add our new adapter class to psycopg list of adapters +psycopg.extensions.adapters[tuple] = SQL_IN +psycopg.extensions.adapters[float] = AsIs +psycopg.extensions.adapters[int] = AsIs + +# usually we would call: +# +# conn = psycopg.connect("...") +# curs = conn.cursor() +# curs.execute("SELECT ...", (("this", "is", "the", "tuple"),)) +# +# but we have no connection to a database right now, so we just check +# the SQL_IN class by calling psycopg's adapt() directly: + +if __name__ == '__main__': + print "Note how the string will be SQL-quoted, but the number will not:" + print psycoadapt(("this is an 'sql quoted' str\\ing", 1, 2.0)) diff --git a/examples/simple.py b/examples/simple.py new file mode 100644 index 0000000..08a3607 --- /dev/null +++ b/examples/simple.py @@ -0,0 +1,52 @@ +# simple.py - very simple example of plain DBAPI-2.0 usage +# currently used as test-me-stress-me script for psycopg 2.0 +# +# Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below this line (except for experimenting) + +class SimpleQuoter(object): + def sqlquote(x=None): + return "'bar'" + +import sys, psycopg + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +print "Encoding for this connection is", conn.encoding + +curs = conn.cursor() +curs.execute("SELECT 1 AS foo") +print curs.fetchone() +curs.execute("SELECT 1 AS foo") +print curs.fetchmany() +curs.execute("SELECT 1 AS foo") +print curs.fetchall() + +conn.rollback() + +sys.exit(0) + +curs.execute("SELECT 1 AS foo", async=1) + +curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'}) +curs.execute("SELECT %(foo)s AS foo", {'foo':None}) +curs.execute("SELECT %(foo)f AS foo", {'foo':42}) +curs.execute("SELECT %(foo)s AS foo", {'foo':SimpleQuoter()}) diff --git a/examples/somehackers.jpg b/examples/somehackers.jpg Binary files differnew file mode 100644 index 0000000..8bb6e01 --- /dev/null +++ b/examples/somehackers.jpg diff --git a/examples/threads.py b/examples/threads.py new file mode 100644 index 0000000..ca67104 --- /dev/null +++ b/examples/threads.py @@ -0,0 +1,160 @@ +# threads.py -- example of multiple threads using psycopg +# -*- encoding: latin1 -*- +# +# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## some others parameters +INSERT_THREADS = ('A', 'B', 'C') +SELECT_THREADS = ('1', '2') + +ROWS = 1000 + +COMMIT_STEP = 20 +SELECT_SIZE = 10000 +SELECT_STEP = 500 +SELECT_DIV = 250 + +# the available modes are: +# 0 - one connection for all insert and one for all select threads +# 1 - connections generated using the connection pool + +MODE = 1 + +## don't modify anything below tis line (except for experimenting) + +import sys, psycopg, threading +from psycopg.pool import ThreadedConnectionPool + +if len(sys.argv) > 1: + DSN = sys.argv[1] +if len(sys.argv) > 2: + MODE = int(sys.argv[2]) + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +curs = conn.cursor() + +try: + curs.execute("""CREATE TABLE test_threads ( + name text, value1 int4, value2 float)""") +except: + conn.rollback() + curs.execute("DROP TABLE test_threads") + curs.execute("""CREATE TABLE test_threads ( + name text, value1 int4, value2 float)""") +conn.commit() + + +## this function inserts a big number of rows and creates and destroys +## a large number of cursors + +def insert_func(conn_or_pool, rows): + name = threading.currentThread().getName() + + if MODE == 0: + conn = conn_or_pool + else: + conn = conn_or_pool.getconn() + + for i in range(rows): + if divmod(i, COMMIT_STEP)[1] == 0: + conn.commit() + if MODE == 1: + conn_or_pool.putconn(conn) + s = name + ": COMMIT STEP " + str(i) + print s + if MODE == 1: + conn = conn_or_pool.getconn() + c = conn.cursor() + try: + c.execute("INSERT INTO test_threads VALUES (%s, %d, %f)", + (str(i), i, float(i))) + except psycopg.ProgrammingError, err: + print name, ": an error occurred; skipping this insert" + print err + conn.commit() + +## a nice select function that prints the current number of rows in the +## database (and transefer them, putting some pressure on the network) + +def select_func(conn_or_pool, z): + name = threading.currentThread().getName() + + if MODE == 0: + conn = conn_or_pool + conn.set_isolation_level(0) + + for i in range(SELECT_SIZE): + if divmod(i, SELECT_STEP)[1] == 0: + try: + if MODE == 1: + conn = conn_or_pool.getconn() + conn.set_isolation_level(0) + c = conn.cursor() + c.execute("SELECT * FROM test_threads WHERE value2 < %s", + (int(i/z),)) + l = c.fetchall() + if MODE == 1: + conn_or_pool.putconn(conn) + s = name + ": number of rows fetched: " + str(len(l)) + print s + except psycopg.ProgrammingError, err: + print name, ": an error occurred; skipping this select" + print err + +## create the connection pool or the connections +if MODE == 0: + conn_insert = psycopg.connect(DSN) + conn_select = psycopg.connect(DSN) +else: + m = len(INSERT_THREADS) + len(SELECT_THREADS) + n = m/2 + conn_insert = conn_select = ThreadedConnectionPool(n, m, DSN) + +## create the threads +threads = [] + +print "Creating INSERT threads:" +for name in INSERT_THREADS: + t = threading.Thread(None, insert_func, 'Thread-'+name, + (conn_insert, ROWS)) + t.setDaemon(0) + threads.append(t) + +print "Creating SELECT threads:" +for name in SELECT_THREADS: + t = threading.Thread(None, select_func, 'Thread-'+name, + (conn_select, SELECT_DIV)) + t.setDaemon(0) + threads.append(t) + +## really start the threads now +for t in threads: + t.start() + +# and wait for them to finish +for t in threads: + t.join() + print t.getName(), "exited OK" + + +conn.commit() +curs.execute("SELECT count(name) FROM test_threads") +print "Inserted", curs.fetchone()[0], "rows." + +curs.execute("DROP TABLE test_threads") +conn.commit() diff --git a/examples/tz.py b/examples/tz.py new file mode 100644 index 0000000..ba760f2 --- /dev/null +++ b/examples/tz.py @@ -0,0 +1,62 @@ +# tz.py - example of datetime objects with time zones +# -*- encoding: latin1 -*- +# +# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +## put in DSN your DSN string + +DSN = 'dbname=test' + +## don't modify anything below this line (except for experimenting) + +import sys, psycopg +import datetime + +from psycopg.tz import ZERO, LOCAL, FixedOffsetTimezone + +if len(sys.argv) > 1: + DSN = sys.argv[1] + +print "Opening connection using dns:", DSN +conn = psycopg.connect(DSN) +curs = conn.cursor() + +try: + curs.execute("CREATE TABLE test_tz (t timestamp with time zone)") +except: + conn.rollback() + curs.execute("DROP TABLE test_tz") + curs.execute("CREATE TABLE test_tz (t timestamp with time zone)") +conn.commit() + +d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=LOCAL) +curs.execute("INSERT INTO test_tz VALUES (%s)", (d,)) +print "Inserted timestamp with timezone:", d +print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d) + +tz = FixedOffsetTimezone(-5*60, "EST") +d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=tz) +curs.execute("INSERT INTO test_tz VALUES (%s)", (d,)) +print "Inserted timestamp with timezone:", d +print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d) + +curs.tzinfo_factory = FixedOffsetTimezone +curs.execute("SELECT * FROM test_tz") +for d in curs: + u = d[0].utcoffset() or ZERO + print "UTC time: ", d[0] - u + print "Local time:", d[0] + print "Time zone:", d[0].tzinfo.tzname(d[0]), d[0].tzinfo.utcoffset(d[0]) + +curs.execute("DROP TABLE test_tz") +conn.commit() diff --git a/examples/whereareyou.jpg b/examples/whereareyou.jpg Binary files differnew file mode 100644 index 0000000..f508c0b --- /dev/null +++ b/examples/whereareyou.jpg diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..59d0d39 --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,28 @@ +# psycopg/__init__.py - initialization of the psycopg module +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +__all__ = ['extensions', 'extras', 'tz', 'pool'] + +# import the DBAPI-2.0 stuff into top-level module +from _psycopg import BINARY, NUMBER, STRING, DATETIME, ROWID + +from _psycopg import Binary, Date, Time, Timestamp +from _psycopg import DateFromTicks, TimeFromTicks, TimestampFromTicks + +from _psycopg import Error, Warning, DataError, DatabaseError, ProgrammingError +from _psycopg import IntegrityError, InterfaceError, InternalError +from _psycopg import NotSupportedError, OperationalError + +from _psycopg import connect, apilevel, threadsafety, paramstyle +from _psycopg import __version__ diff --git a/lib/extensions.py b/lib/extensions.py new file mode 100644 index 0000000..0424d43 --- /dev/null +++ b/lib/extensions.py @@ -0,0 +1,31 @@ +# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +from _psycopg import UNICODE, INTEGER, LONGINTEGER, BOOLEAN, FLOAT +from _psycopg import TIME, DATE, INTERVAL + +from _psycopg import Boolean, QuotedString +try: + from _psycopg import DateFromMx, TimeFromMx, TimestampFromMx + from _psycopg import IntervalFromMx +except: + pass +try: + from _psycopg import DateFromPy, TimeFromPy, TimestampFromPy + from _psycopg import IntervalFromPy +except: + pass + +from _psycopg import adapt, adapters, encodings, connection, cursor +from _psycopg import string_types, binary_types, new_type, register_type diff --git a/lib/extras.py b/lib/extras.py new file mode 100644 index 0000000..b9532f9 --- /dev/null +++ b/lib/extras.py @@ -0,0 +1,59 @@ +# psycopg/extras.py - miscellaneous extra goodies for psycopg +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +from psycopg.extensions import cursor as _cursor + +class DictCursor(_cursor): + """A cursor that keeps a list of column name -> index mappings.""" + + __query_executed = 0 + + def execute(self, query, vars=None, async=0): + self.tuple_factory = DictRow + self.index = {} + self.__query_executed = 1 + return _cursor.execute(self, query, vars, async) + + def _build_index(self): + if self.description: + for i in range(len(self.description)): + self.index[self.description[i][0]] = i + + def fetchone(self): + if self.__query_executed: + self._build_index() + return _cursor.fetchone(self) + + def fetchmany(self, size=None): + if self.__query_executed: + self._build_index() + return _cursor.fetchmany(self, size) + + def fetchall(self): + if self.__query_executed: + self._build_index() + return _cursor.fetchall(self) + +class DictRow(list): + """A row object that allow by-colun-name access to data.""" + + def __init__(self, cursor): + self._cursor = cursor + self[:] = [None] * len(cursor.description) + print cursor, self + + def __getitem__(self, x): + if type(x) != int: + x = self._cursor.index[x] + return list.__getitem__(self, x) diff --git a/lib/pool.py b/lib/pool.py new file mode 100644 index 0000000..119a0a8 --- /dev/null +++ b/lib/pool.py @@ -0,0 +1,184 @@ +# psycopg/pool.py - pooling code for psycopg +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +import psycopg + +try: + from zLOG import LOG, DEBUG, INFO + def dbg(*args): + LOG('ZPsycopgDA', DEBUG, "", + ' '.join([str(x) for x in args])+'\n') + LOG('ZPsycopgDA', INFO, "Installed", "Logging using Zope's zLOG\n") +except: + import sys + def dbg(*args): + sys.stderr.write(' '.join(args)+'\n') + + + +class PoolError(psycopg.Error): + pass + + + +class AbstractConnectionPool(object): + """Generic key-based pooling code.""" + + def __init__(self, minconn, maxconn, *args, **kwargs): + """Initialize the connection pool. + + New 'minconn' connections are created immediately calling 'connfunc' + with given parameters. The connection pool will support a maximum of + about 'maxconn' connections. + """ + self.minconn = minconn + self.maxconn = maxconn + self.closed = False + + self._args = args + self._kwargs = kwargs + + self._pool = [] + self._used = {} + self._keys = 0 + + for i in range(self.minconn): + self._connect() + + def _connect(self, key=None): + """Create a new connection and assign it to 'key' if not None.""" + conn = psycopg.connect(*self._args, **self._kwargs) + if key is not None: + self._used[key] = conn + else: + self._pool.append(conn) + return conn + + def _getkey(self): + """Return a new unique key.""" + self._keys += 1 + return self._keys + + def _findkey(self, conn): + """Return the key associated with a connection or None.""" + for o, k in self._used.items(): + if o == conn: + return k + + def _getconn(self, key=None): + """Get a free connection and assign it to 'key' if not None.""" + if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._getkey() + + if not self._used.has_key(key): + if not self._pool: + if len(self._used) == self.maxconn: + raise PoolError("connection pool exausted") + return self._connect(key) + else: + self._used[key] = self._pool.pop() + return self._used[key] + + def _putconn(self, conn, key=None, close=False): + """Put away a connection.""" + if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._findkey(conn) + + if not key: + raise PoolError("trying to put unkeyed connection") + + if len(self._pool) < self.minconn and not close: + self._pool.append(conn) + else: + conn.close() + + # here we check for the presence of key because it can happen that a + # thread tries to put back a connection after a call to close + if not self.closed or key in self._used: + del self._used[key] + + def _closeall(self): + """Close all connections. + + Note that this can lead to some code fail badly when trying to use + an already closed connection. If you call .closeall() make sure + your code can deal with it. + """ + if self.closed: raise PoolError("connection pool is closed") + for conn in self._pool + list(self._used.values()): + try: + print "Closing connection", conn + conn.close() + except: + pass + self.closed = True + + + +class SimpleConnectionPool(AbstractConnectionPool): + """A connection pool that can't be shared across different threads.""" + + getconn = AbstractConnectionPool._getconn + putconn = AbstractConnectionPool._putconn + closeall = AbstractConnectionPool._closeall + + + +class ThreadedConnectionPool(AbstractConnectionPool): + """A connection pool that works with the threading module. + + Note that this connection pool generates by itself the required keys + using the current thread id. This means that untill a thread put away + a connection it will always get the same connection object by successive + .getconn() calls. + """ + + def __init__(self, minconn, maxconn, *args, **kwargs): + """Initialize the threading lock.""" + import threading + AbstractConnectionPool.__init__( + self, minconn, maxconn, *args, **kwargs) + self._lock = threading.Lock() + + # we we'll need the thread module, to determine thread ids, so we + # import it here and copy it in an instance variable + import thread + self.__thread = thread + + def getconn(self): + """Generate thread id and return a connection.""" + key = self.__thread.get_ident() + self._lock.acquire() + try: + return self._getconn(key) + finally: + self._lock.release() + + def putconn(self, conn=None, close=False): + """Put away an unused connection.""" + key = self.__thread.get_ident() + self._lock.acquire() + try: + if not conn: conn = self._used[key] + self._putconn(conn, key, close) + finally: + self._lock.release() + + def closeall(self): + """Close all connections (even the one currently in use.""" + self._lock.acquire() + try: + self._closeall() + finally: + self._lock.release() diff --git a/lib/tz.py b/lib/tz.py new file mode 100644 index 0000000..c7a855b --- /dev/null +++ b/lib/tz.py @@ -0,0 +1,94 @@ +# psycopg/tz.py - tzinfo implementation +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +import datetime +import time + + +ZERO = datetime.timedelta(0) + +class FixedOffsetTimezone(datetime.tzinfo): + """Fixed offset in minutes east from UTC. + + This is exactly the implementation found in Python 2.3.x documentation, + 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.) + """ + _name = None + _offset = ZERO + + 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 utcoffset(self, dt): + return self._offset + + def tzname(self, dt): + if self._name is not None: + return self._name + else: + seconds = self._offset.seconds + self._offset.days * 86400 + hours, seconds = divmod(seconds, 3600) + minutes = seconds/60 + if minutes: + return "%+03d:%d" % (hours, minutes) + else: + return "%+03d" % hours + + def dst(self, dt): + return ZERO + + +STDOFFSET = datetime.timedelta(seconds = -time.timezone) +if time.daylight: + DSTOFFSET = datetime.timedelta(seconds = -time.altzone) +else: + DSTOFFSET = STDOFFSET +DSTDIFF = DSTOFFSET - STDOFFSET + +class LocalTimezone(datetime.tzinfo): + """Platform idea of local timezone. + + This is the exact implementation from the Pyhton 2.3 documentation. + """ + + def utcoffset(self, dt): + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + return time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + tt = (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.weekday(), 0, -1) + stamp = time.mktime(tt) + tt = time.localtime(stamp) + return tt.tm_isdst > 0 + +LOCAL = LocalTimezone() + +# TODO: pre-generate some interesting time zones? diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c new file mode 100644 index 0000000..8976b4f --- /dev/null +++ b/psycopg/adapter_binary.c @@ -0,0 +1,336 @@ +/* adapter_binary.c - Binary objects + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> + +#include <libpq-fe.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_binary.h" + +/** the quoting code */ + +#ifndef PSYCOPG_OWN_QUOTING +#define binary_escape PQescapeBytea +#else +static unsigned char * +binary_escape(char *from, size_t from_length, size_t *to_length) +{ + unsigneed char *quoted, *chptr, *newptr; + int i, space, new_space; + + space = from_length + 2; + + Py_BEGIN_ALLOW_THREADS; + + quoted = (unsigned char*)calloc(space, sizeof(char)); + if (quoted == NULL) return NULL; + + chptr = quoted; + + for (i=0; i < len; i++) { + if (chptr - quoted > space - 6) { + new_space = space * ((space) / (i + 1)) + 2 + 6; + if (new_space - space < 1024) space += 1024; + else space = new_space; + newptr = (unsigned char *)realloc(quoted, space); + if (newptr == NULL) { + free(quoted); + return NULL; + } + /* chptr has to be moved to the new location*/ + chptr = newptr + (chptr - quoted); + quoted = newptr; + Dprintf("binary_escape: reallocated %i bytes at %p", space,quoted); + } + if (from[i]) { + if (from[i] >= ' ' && from[i] <= '~') { + if (from[i] == '\'') { + *chptr = '\\'; + chptr++; + *chptr = '\''; + chptr++; + } + else if (from[i] == '\\') { + memcpy(chptr, "\\\\\\\\", 4); + chptr += 4; + } + else { + /* leave it as it is if ascii printable */ + *chptr = from[i]; + chptr++; + } + } + else { + unsigned char c; + + /* escape to octal notation \nnn */ + *chptr++ = '\\'; + *chptr++ = '\\'; + c = from[i]; + *chptr = ((c >> 6) & 0x07) + 0x30; chptr++; + *chptr = ((c >> 3) & 0x07) + 0x30; chptr++; + *chptr = ( c & 0x07) + 0x30; chptr++; + } + } + else { + /* escape null as \\000 */ + memcpy(chptr, "\\\\000", 5); + chptr += 5; + } + } + *chptr = '\0'; + + Py_END_ALLOW_THREADS; + + *to_size = chptr - quoted + 1; + return quoted; +} +#endif + +/* binary_quote - do the quote process on plain and unicode strings */ + +static PyObject * +binary_quote(binaryObject *self) +{ + char *to; + const char *buffer; + int buffer_len; + size_t len = 0; + + /* if we got a plain string or a buffer we escape it and save the buffer */ + if (PyString_Check(self->wrapped) || PyBuffer_Check(self->wrapped)) { + /* escape and build quoted buffer */ + PyObject_AsCharBuffer(self->wrapped, &buffer, &buffer_len); + to = (char *)binary_escape(buffer, buffer_len, &len); + if (to == NULL) { + PyErr_NoMemory(); + return NULL; + } + + self->buffer = PyString_FromFormat("'%s'", to); + PQfreemem(to); + } + + /* if the wrapped object is not a string or a buffer, this is an error */ + else { + PyErr_SetString(PyExc_TypeError, "can't escape non-string object"); + return NULL; + } + + return self->buffer; +} + +/* binary_str, binary_getquoted - return result of quoting */ + +static PyObject * +binary_str(binaryObject *self) +{ + if (self->buffer == NULL) { + binary_quote(self); + } + Py_INCREF(self->buffer); + return self->buffer; +} + +PyObject * +binary_getquoted(binaryObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return binary_str(self); +} + +PyObject * +binary_prepare(binaryObject *self, PyObject *args) +{ + PyObject *fake; + + if (!PyArg_ParseTuple(args, "O", &fake)) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/** the Binary object **/ + +/* object member list */ + +static struct PyMemberDef binaryObject_members[] = { + {"adapted", T_OBJECT, offsetof(binaryObject, wrapped), RO}, + {"buffer", T_OBJECT, offsetof(binaryObject, buffer), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef binaryObject_methods[] = { + {"getquoted", (PyCFunction)binary_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL-quoted binary string"}, + {"prepare", (PyCFunction)binary_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +binary_setup(binaryObject *self, PyObject *str) +{ + Dprintf("binary_setup: init binary object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->buffer = NULL; + self->wrapped = str; + Py_INCREF(self->wrapped); + + Dprintf("binary_setup: good binary object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +binary_dealloc(PyObject* obj) +{ + binaryObject *self = (binaryObject *)obj; + + Py_XDECREF(self->wrapped); + Py_XDECREF(self->buffer); + + Dprintf("binary_dealloc: deleted binary object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +binary_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *str; + + if (!PyArg_ParseTuple(args, "O", &str)) + return -1; + + return binary_setup((binaryObject *)obj, str); +} + +static PyObject * +binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +binary_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +binary_repr(binaryObject *self) +{ + return PyString_FromFormat("<psycopg.Binary object at %p>", self); +} + +/* object type */ + +#define binaryType_doc \ +"psycopg.Binary(buffer) -> new binary object" + +PyTypeObject binaryType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.Binary", + sizeof(binaryObject), + 0, + binary_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + (reprfunc)binary_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)binary_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + + binaryType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + binaryObject_methods, /*tp_methods*/ + binaryObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + binary_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + binary_new, /*tp_new*/ + (freefunc)binary_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_Binary(PyObject *module, PyObject *args) +{ + PyObject *str; + + if (!PyArg_ParseTuple(args, "O", &str)) + return NULL; + + return PyObject_CallFunction((PyObject *)&binaryType, "O", str); +} diff --git a/psycopg/adapter_binary.h b/psycopg/adapter_binary.h new file mode 100644 index 0000000..adc57c6 --- /dev/null +++ b/psycopg/adapter_binary.h @@ -0,0 +1,52 @@ +/* adapter_binary.h - definition for the Binary type + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_BINARY_H +#define PSYCOPG_BINARY_H 1 + +#include <Python.h> +#include <libpq-fe.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject binaryType; + +typedef struct { + PyObject HEAD; + + PyObject *wrapped; + PyObject *buffer; + char *encoding; +} binaryObject; + +/* functions exported to psycopgmodule.c */ + +extern PyObject *psyco_Binary(PyObject *module, PyObject *args); +#define psyco_Binary_doc \ + "psycopg.Binary(buffer) -> new binary object" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_BINARY_H) */ diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c new file mode 100644 index 0000000..03729ba --- /dev/null +++ b/psycopg/adapter_datetime.c @@ -0,0 +1,439 @@ +/* adapter_datetime.c - python date/time objects + * + * Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> +#include <datetime.h> + +#include <time.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_datetime.h" + + +/* the pointer to the datetime module API is initialized by the module init + code, we just need to grab it */ +extern PyObject* pyDateTimeModuleP; +extern PyObject *pyDateTypeP; +extern PyObject *pyTimeTypeP; +extern PyObject *pyDateTimeTypeP; +extern PyObject *pyDeltaTypeP; + +/* datetime_str, datetime_getquoted - return result of quoting */ + +static PyObject * +pydatetime_str(pydatetimeObject *self) +{ + if (self->type <= PSYCO_DATETIME_TIMESTAMP) { + PyObject *res = NULL; + PyObject *iso = PyObject_CallMethod(self->wrapped, "isoformat", NULL); + if (iso) { + res = PyString_FromFormat("'%s'", PyString_AsString(iso)); + Py_DECREF(iso); + } + return res; + } + else { + PyDateTime_Delta *obj = (PyDateTime_Delta*)self->wrapped; + + char buffer[8]; + int i, j, x; + int a = obj->microseconds; + + for (i=1000000, j=0; i > 0 ; i /= 10) { + x = a/i; + a -= x*i; + buffer[j++] = '0'+x; + } + buffer[j] = '\0'; + + return PyString_FromFormat("'%d days %d.%s seconds'", + obj->days, obj->seconds, buffer); + } +} + +PyObject * +pydatetime_getquoted(pydatetimeObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return pydatetime_str(self); +} + +PyObject * +pydatetime_prepare(pydatetimeObject *self, PyObject *args) +{ + PyObject *fake; + + if (!PyArg_ParseTuple(args, "O", &fake)) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/** the DateTime wrapper object **/ + +/* object member list */ + +static struct PyMemberDef pydatetimeObject_members[] = { + {"adapted", T_OBJECT, offsetof(pydatetimeObject, wrapped), RO}, + {"type", T_INT, offsetof(pydatetimeObject, type), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pydatetimeObject_methods[] = { + {"getquoted", (PyCFunction)pydatetime_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL date/time"}, + {"prepare", (PyCFunction)pydatetime_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pydatetime_setup(pydatetimeObject *self, PyObject *obj, int type) +{ + Dprintf("pydatetime_setup: init datetime object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->type = type; + self->wrapped = obj; + Py_INCREF(self->wrapped); + + Dprintf("pydatetime_setup: good pydatetime object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +pydatetime_dealloc(PyObject* obj) +{ + pydatetimeObject *self = (pydatetimeObject *)obj; + + Py_XDECREF(self->wrapped); + + Dprintf("mpydatetime_dealloc: deleted pydatetime object at %p, " + "refcnt = %d", obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +pydatetime_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *dt; + int type = -1; /* raise an error if type was not passed! */ + + if (!PyArg_ParseTuple(args, "O|i", &dt, &type)) + return -1; + + return pydatetime_setup((pydatetimeObject *)obj, dt, type); +} + +static PyObject * +pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +pydatetime_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +pydatetime_repr(pydatetimeObject *self) +{ + return PyString_FromFormat("<psycopg.datetime object at %p>", self); +} + +/* object type */ + +#define pydatetimeType_doc \ +"psycopg.Pydatetime(datetime, type) -> new datetime wrapper object" + +PyTypeObject pydatetimeType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.datetime", + sizeof(pydatetimeObject), + 0, + pydatetime_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + (reprfunc)pydatetime_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)pydatetime_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + + pydatetimeType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + pydatetimeObject_methods, /*tp_methods*/ + pydatetimeObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + pydatetime_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + pydatetime_new, /*tp_new*/ + (freefunc)pydatetime_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +#ifdef PSYCOPG_DEFAULT_PYDATETIME + +PyObject * +psyco_Date(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + int year, month, day; + + PyObject* obj = NULL; + + if (!PyArg_ParseTuple(args, "iii", &year, &month, &day)) + return NULL; + + obj = PyObject_CallFunction(pyDateTypeP, "iii", year, month, day); + + if (obj) { + res = PyObject_CallFunction((PyObject *)&pydatetimeType, + "Oi", obj, PSYCO_DATETIME_DATE); + Py_DECREF(obj); + } + + return res; +} + +PyObject * +psyco_Time(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + int hours, minutes=0; + double micro, seconds=0.0; + + PyObject* obj = NULL; + + if (!PyArg_ParseTuple(args, "iid", &hours, &minutes, &seconds)) + return NULL; + + micro = (seconds - floor(seconds)) * 1000000.0; + + obj = PyObject_CallFunction(pyTimeTypeP, "iiii", + hours, minutes, (int)round(seconds), (int)round(micro)); + + if (obj) { + res = PyObject_CallFunction((PyObject *)&pydatetimeType, + "Oi", obj, PSYCO_DATETIME_TIME); + Py_DECREF(obj); + } + + return res; +} + +PyObject * +psyco_Timestamp(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + int year, month, day; + int hour=0, minute=0; /* default to midnight */ + double micro, second=0.0; + + PyObject* obj = NULL; + + if (!PyArg_ParseTuple(args, "lii|iid", &year, &month, &day, + &hour, &minute, &second)) + return NULL; + + micro = (second - floor(second)) * 1000000.0; + + obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii", + year, month, day, hour, minute, (int)round(second), (int)round(micro)); + + if (obj) { + res = PyObject_CallFunction((PyObject *)&pydatetimeType, + "Oi", obj, PSYCO_DATETIME_TIMESTAMP); + Py_DECREF(obj); + } + + return res; +} + +PyObject * +psyco_DateFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + struct tm tm; + time_t t; + double ticks; + + if (!PyArg_ParseTuple(args, "d", &ticks)) + return NULL; + + t = (time_t)round(ticks); + if (gmtime_r(&t, &tm)) { + args = Py_BuildValue("iii", tm.tm_year, tm.tm_mon, tm.tm_mday); + if (args) { + res = psyco_Date(self, args); + Py_DECREF(args); + } + } + return res; +} + +PyObject * +psyco_TimeFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + struct tm tm; + time_t t; + double ticks; + + if (!PyArg_ParseTuple(args,"d", &ticks)) + return NULL; + + t = (time_t)round(ticks); + if (gmtime_r(&t, &tm)) { + args = Py_BuildValue("iid", tm.tm_hour, tm.tm_min, (double)tm.tm_sec); + if (args) { + res = psyco_Time(self, args); + Py_DECREF(args); + } + } + return res; +} + +PyObject * +psyco_TimestampFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + struct tm tm; + time_t t; + double ticks; + + if (!PyArg_ParseTuple(args,"d", &ticks)) + return NULL; + + t = (time_t)round(ticks); + if (gmtime_r(&t, &tm)) { + args = Py_BuildValue("iiiiid", + tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, (double)tm.tm_sec); + if (args) { + res = psyco_Timestamp(self, args); + Py_DECREF(args); + } + } + return res; +} + +#endif + +PyObject * +psyco_DateFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", pyDateTypeP, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_DATE); +} + +PyObject * +psyco_TimeFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", pyTimeTypeP, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_TIME); +} + +PyObject * +psyco_TimestampFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", pyDateTimeTypeP, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_TIMESTAMP); +} + +PyObject * +psyco_IntervalFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", pyDeltaTypeP, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_INTERVAL); +} diff --git a/psycopg/adapter_datetime.h b/psycopg/adapter_datetime.h new file mode 100644 index 0000000..2616382 --- /dev/null +++ b/psycopg/adapter_datetime.h @@ -0,0 +1,95 @@ +/* adapter_datetime.h - definition for the python date/time types + * + * Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_DATETIME_H +#define PSYCOPG_DATETIME_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject pydatetimeType; + +typedef struct { + PyObject HEAD; + + PyObject *wrapped; + int type; +#define PSYCO_DATETIME_TIME 0 +#define PSYCO_DATETIME_DATE 1 +#define PSYCO_DATETIME_TIMESTAMP 2 +#define PSYCO_DATETIME_INTERVAL 3 + +} pydatetimeObject; + + +/* functions exported to psycopgmodule.c */ +#ifdef PSYCOPG_DEFAULT_PYDATETIME + +extern PyObject *psyco_Date(PyObject *module, PyObject *args); +#define psyco_Date_doc \ + "psycopg.Date(year, month, day) -> new date" + +extern PyObject *psyco_Time(PyObject *module, PyObject *args); +#define psyco_Time_doc \ + "psycopg.Time(hour, minutes, seconds) -> new time" + +extern PyObject *psyco_Timestamp(PyObject *module, PyObject *args); +#define psyco_Timestamp_doc \ + "psycopg.Time(year, month, day, hour, minutes, seconds) -> new timestamp" + +extern PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args); +#define psyco_DateFromTicks_doc \ + "psycopg.DateFromTicks(ticks) -> new date" + +extern PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args); +#define psyco_TimeFromTicks_doc \ + "psycopg.TimeFromTicks(ticks) -> new date" + +extern PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args); +#define psyco_TimestampFromTicks_doc \ + "psycopg.TimestampFromTicks(ticks) -> new date" + +#endif /* PSYCOPG_DEFAULT_PYDATETIME */ + +extern PyObject *psyco_DateFromPy(PyObject *module, PyObject *args); +#define psyco_DateFromPy_doc \ + "psycopg.DateFromPy(datetime.date) -> new wrapper" + +extern PyObject *psyco_TimeFromPy(PyObject *module, PyObject *args); +#define psyco_TimeFromPy_doc \ + "psycopg.TimeFromPy(datetime.time) -> new wrapper" + +extern PyObject *psyco_TimestampFromPy(PyObject *module, PyObject *args); +#define psyco_TimestampFromPy_doc \ + "psycopg.TimestampFromPy(datetime.datetime) -> new wrapper" + +extern PyObject *psyco_IntervalFromPy(PyObject *module, PyObject *args); +#define psyco_IntervalFromPy_doc \ + "psycopg.IntervalFromPy(datetime.timedelta) -> new wrapper" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_DATETIME_H) */ diff --git a/psycopg/adapter_mxdatetime.c b/psycopg/adapter_mxdatetime.c new file mode 100644 index 0000000..aa7f78f --- /dev/null +++ b/psycopg/adapter_mxdatetime.c @@ -0,0 +1,392 @@ +/* adapter_mxdatetime.c - mx date/time objects + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> +#include <mxDateTime.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_mxdatetime.h" + +/* the pointer to the mxDateTime API is initialized by the module init code, + we just need to grab it */ +extern mxDateTimeModule_APIObject *mxDateTimeP; + + +/* mxdatetime_str, mxdatetime_getquoted - return result of quoting */ + +static char *mxdatetimeObject_str_conv[] = { + PSYCO_MXDATETIME_TIME_CONV, + PSYCO_MXDATETIME_DATE_CONV, + PSYCO_MXDATETIME_TIMESTAMP_CONV +}; + +static PyObject * +mxdatetime_str(mxdatetimeObject *self) +{ + return PyObject_CallMethod(self->wrapped, "strftime", "s", + mxdatetimeObject_str_conv[self->type]); +} + +PyObject * +mxdatetime_getquoted(mxdatetimeObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return mxdatetime_str(self); +} + +PyObject * +mxdatetime_prepare(mxdatetimeObject *self, PyObject *args) +{ + PyObject *fake; + + if (!PyArg_ParseTuple(args, "O", &fake)) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/** the MxDateTime object **/ + +/* object member list */ + +static struct PyMemberDef mxdatetimeObject_members[] = { + {"adapted", T_OBJECT, offsetof(mxdatetimeObject, wrapped), RO}, + {"type", T_INT, offsetof(mxdatetimeObject, type), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef mxdatetimeObject_methods[] = { + {"getquoted", (PyCFunction)mxdatetime_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL date/time"}, + {"prepare", (PyCFunction)mxdatetime_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +mxdatetime_setup(mxdatetimeObject *self, PyObject *obj, int type) +{ + Dprintf("mxdatetime_setup: init mxdatetime object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->type = type; + self->wrapped = obj; + Py_INCREF(self->wrapped); + + Dprintf("mxdatetime_setup: good mxdatetime object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +mxdatetime_dealloc(PyObject* obj) +{ + mxdatetimeObject *self = (mxdatetimeObject *)obj; + + Py_XDECREF(self->wrapped); + + Dprintf("mxdatetime_dealloc: deleted mxdatetime object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +mxdatetime_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *mx; + int type = -1; /* raise an error if type was not passed! */ + + if (!PyArg_ParseTuple(args, "O|i", &mx, &type)) + return -1; + + return mxdatetime_setup((mxdatetimeObject *)obj, mx, type); +} + +static PyObject * +mxdatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +mxdatetime_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +mxdatetime_repr(mxdatetimeObject *self) +{ + return PyString_FromFormat("<psycopg.MxDateTime object at %p>", self); +} + +/* object type */ + +#define mxdatetimeType_doc \ +"psycopg.MxDateTime(mx, type) -> new mx.DateTime wrapper object" + +PyTypeObject mxdatetimeType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.MxDateTime", + sizeof(mxdatetimeObject), + 0, + mxdatetime_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + (reprfunc)mxdatetime_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)mxdatetime_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + + mxdatetimeType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + mxdatetimeObject_methods, /*tp_methods*/ + mxdatetimeObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + mxdatetime_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + mxdatetime_new, /*tp_new*/ + (freefunc)mxdatetime_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +#ifdef PSYCOPG_DEFAULT_MXDATETIME + +PyObject * +psyco_Date(PyObject *self, PyObject *args) +{ + PyObject *res, *mx; + int year, month, day; + + if (!PyArg_ParseTuple(args, "iii", &year, &month, &day)) + return NULL; + + mx = mxDateTimeP->DateTime_FromDateAndTime(year, month, day, 0, 0, 0.0); + if (mx == NULL) return NULL; + + res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_DATE); + Py_DECREF(mx); + return res; +} + +PyObject * +psyco_Time(PyObject *self, PyObject *args) +{ + PyObject *res, *mx; + int hours, minutes=0; + double seconds=0.0; + + if (!PyArg_ParseTuple(args, "iid", &hours, &minutes, &seconds)) + return NULL; + + mx = mxDateTimeP->DateTimeDelta_FromTime(hours, minutes, seconds); + if (mx == NULL) return NULL; + + res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_TIME); + Py_DECREF(mx); + return res; +} + +PyObject * +psyco_Timestamp(PyObject *self, PyObject *args) +{ + PyObject *res, *mx; + int year, month, day; + int hour=0, minute=0; /* default to midnight */ + double second=0.0; + + if (!PyArg_ParseTuple(args, "lii|iid", &year, &month, &day, + &hour, &minute, &second)) + return NULL; + + mx = mxDateTimeP->DateTime_FromDateAndTime(year, month, day, + hour, minute, second); + if (mx == NULL) return NULL; + + res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_TIMESTAMP); + Py_DECREF(mx); + return res; +} + +PyObject * +psyco_DateFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res, *mx; + double ticks; + + if (!PyArg_ParseTuple(args,"d", &ticks)) + return NULL; + + if (!(mx = mxDateTimeP->DateTime_FromTicks(ticks))) + return NULL; + + res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_DATE); + Py_DECREF(mx); + return res; +} + +PyObject * +psyco_TimeFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res, *mx, *dt; + double ticks; + + if (!PyArg_ParseTuple(args,"d", &ticks)) + return NULL; + + if (!(dt = mxDateTimeP->DateTime_FromTicks(ticks))) + return NULL; + + if (!(mx = mxDateTimeP->DateTimeDelta_FromDaysAndSeconds( + 0, ((mxDateTimeObject*)dt)->abstime))) + { + Py_DECREF(dt); + return NULL; + } + + Py_DECREF(dt); + res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_TIME); + Py_DECREF(mx); + return res; +} + +PyObject * +psyco_TimestampFromTicks(PyObject *self, PyObject *args) +{ + PyObject *mx, *res; + double ticks; + + if (!PyArg_ParseTuple(args, "d", &ticks)) + return NULL; + + if (!(mx = mxDateTimeP->DateTime_FromTicks(ticks))) + return NULL; + + res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_TIMESTAMP); + Py_DECREF(mx); + return res; +} + +#endif + +PyObject * +psyco_DateFromMx(PyObject *self, PyObject *args) +{ + PyObject *mx; + + if (!PyArg_ParseTuple(args, "O!", mxDateTimeP->DateTime_Type, &mx)) + return NULL; + + return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_DATE); +} + +PyObject * +psyco_TimeFromMx(PyObject *self, PyObject *args) +{ + PyObject *mx; + + if (!PyArg_ParseTuple(args, "O!", mxDateTimeP->DateTimeDelta_Type, &mx)) + return NULL; + + return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_TIME); +} + +PyObject * +psyco_TimestampFromMx(PyObject *self, PyObject *args) +{ + PyObject *mx; + + if (!PyArg_ParseTuple(args, "O!", mxDateTimeP->DateTime_Type, &mx)) + return NULL; + + return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_TIMESTAMP); +} + +PyObject * +psyco_IntervalFromMx(PyObject *self, PyObject *args) +{ + PyObject *mx; + + if (!PyArg_ParseTuple(args, "O!", mxDateTimeP->DateTime_Type, &mx)) + return NULL; + + return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx, + PSYCO_MXDATETIME_INTERVAL); +} diff --git a/psycopg/adapter_mxdatetime.h b/psycopg/adapter_mxdatetime.h new file mode 100644 index 0000000..b7cd173 --- /dev/null +++ b/psycopg/adapter_mxdatetime.h @@ -0,0 +1,100 @@ +/* adapter_mxdatetime.h - definition for the mx date/time types + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_MXDATETIME_H +#define PSYCOPG_MXDATETIME_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject mxdatetimeType; + +typedef struct { + PyObject HEAD; + + PyObject *wrapped; + int type; +#define PSYCO_MXDATETIME_TIME 0 +#define PSYCO_MXDATETIME_DATE 1 +#define PSYCO_MXDATETIME_TIMESTAMP 2 +#define PSYCO_MXDATETIME_INTERVAL 3 + +} mxdatetimeObject; + +/* the conversion strings */ +#define PSYCO_MXDATETIME_TIME_CONV "'%H:%M:%S'" +#define PSYCO_MXDATETIME_DATE_CONV "'%Y-%m-%d'" +#define PSYCO_MXDATETIME_TIMESTAMP_CONV "'%Y-%m-%d %H:%M:%S'" +#define PSYCO_MXDATETIME_INTERVAL_CONV "'%d:%H:%M:%S'" + +/* functions exported to psycopgmodule.c */ +#ifdef PSYCOPG_DEFAULT_MXDATETIME + +extern PyObject *psyco_Date(PyObject *module, PyObject *args); +#define psyco_Date_doc \ + "psycopg.Date(year, month, day) -> new date" + +extern PyObject *psyco_Time(PyObject *module, PyObject *args); +#define psyco_Time_doc \ + "psycopg.Time(hour, minutes, seconds) -> new time" + +extern PyObject *psyco_Timestamp(PyObject *module, PyObject *args); +#define psyco_Timestamp_doc \ + "psycopg.Time(year, month, day, hour, minutes, seconds) -> new timestamp" + +extern PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args); +#define psyco_DateFromTicks_doc \ + "psycopg.DateFromTicks(ticks) -> new date" + +extern PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args); +#define psyco_TimeFromTicks_doc \ + "psycopg.TimeFromTicks(ticks) -> new time" + +extern PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args); +#define psyco_TimestampFromTicks_doc \ + "psycopg.TimestampFromTicks(ticks) -> new timestamp" + +#endif /* PSYCOPG_DEFAULT_MXDATETIME */ + +extern PyObject *psyco_DateFromMx(PyObject *module, PyObject *args); +#define psyco_DateFromMx_doc \ + "psycopg.DateFromMx(mx) -> new date" + +extern PyObject *psyco_TimeFromMx(PyObject *module, PyObject *args); +#define psyco_TimeFromMx_doc \ + "psycopg.TimeFromMx(mx) -> new time" + +extern PyObject *psyco_TimestampFromMx(PyObject *module, PyObject *args); +#define psyco_TimestampFromMx_doc \ + "psycopg.TimestampFromMx(mx) -> new timestamp" + +extern PyObject *psyco_IntervalFromMx(PyObject *module, PyObject *args); +#define psyco_IntervalFromMx_doc \ + "psycopg.IntervalFromMx(mx) -> new interval" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_MXDATETIME_H) */ diff --git a/psycopg/adapter_pboolean.c b/psycopg/adapter_pboolean.c new file mode 100644 index 0000000..d4c8916 --- /dev/null +++ b/psycopg/adapter_pboolean.c @@ -0,0 +1,223 @@ +/* adapter_pboolean.c - psycopg boolean type wrapper implementation + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_pboolean.h" + + +/** the Boolean object **/ + +static PyObject * +pboolean_str(pbooleanObject *self) +{ + if (PyObject_IsTrue(self->wrapped)) { + return PyString_FromString("'t'"); + } + else { + return PyString_FromString("'f'"); + } +} + +PyObject * +pboolean_getquoted(pbooleanObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return pboolean_str(self); +} + +PyObject * +pboolean_prepare(pbooleanObject *self, PyObject *args) +{ + PyObject *fake; + + if (!PyArg_ParseTuple(args, "O", &fake)) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/** the Boolean object */ + +/* object member list */ + +static struct PyMemberDef pbooleanObject_members[] = { + {"adapted", T_OBJECT, offsetof(pbooleanObject, wrapped), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pbooleanObject_methods[] = { + {"getquoted", (PyCFunction)pboolean_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"prepare", (PyCFunction)pboolean_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pboolean_setup(pbooleanObject *self, PyObject *obj) +{ + Dprintf("pboolean_setup: init pboolean object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->wrapped = obj; + Py_INCREF(self->wrapped); + + Dprintf("pboolean_setup: good pboolean object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +pboolean_dealloc(PyObject* obj) +{ + pbooleanObject *self = (pbooleanObject *)obj; + + Py_XDECREF(self->wrapped); + + Dprintf("pboolean_dealloc: deleted pboolean object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +pboolean_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return pboolean_setup((pbooleanObject *)obj, o); +} + +static PyObject * +pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +pboolean_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +pboolean_repr(pbooleanObject *self) +{ + return PyString_FromFormat("<psycopg.Boolean object at %p>", self); +} + + +/* object type */ + +#define pbooleanType_doc \ +"psycopg.Boolean(str) -> new Boolean adapter object" + +PyTypeObject pbooleanType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.Boolean", + sizeof(pbooleanObject), + 0, + pboolean_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + + (reprfunc)pboolean_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)pboolean_str, /*tp_str*/ + + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + pbooleanType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + pbooleanObject_methods, /*tp_methods*/ + pbooleanObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + pboolean_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + pboolean_new, /*tp_new*/ + (freefunc)pboolean_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_Boolean(PyObject *module, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O", &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pbooleanType, "O", obj); +} diff --git a/psycopg/adapter_pboolean.h b/psycopg/adapter_pboolean.h new file mode 100644 index 0000000..1aa61b5 --- /dev/null +++ b/psycopg/adapter_pboolean.h @@ -0,0 +1,51 @@ +/* adapter_pboolean.h - definition for the psycopg boolean type wrapper + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_PBOOLEAN_H +#define PSYCOPG_PBOOLEAN_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject pbooleanType; + +typedef struct { + PyObject HEAD; + + /* this is the real object we wrap */ + PyObject *wrapped; + +} pbooleanObject; + +/* functions exported to psycopgmodule.c */ + +extern PyObject *psyco_Boolean(PyObject *module, PyObject *args); +#define psyco_Boolean_doc \ + "psycopg.Boolean(obj) -> new boolean value" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_PBOOLEAN_H) */ diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c new file mode 100644 index 0000000..3a92e6b --- /dev/null +++ b/psycopg/adapter_qstring.c @@ -0,0 +1,354 @@ +/* adapter_qstring.c - QuotedString objects + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> + +#include <libpq-fe.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/connection.h" +#include "psycopg/adapter_qstring.h" + + +/** the quoting code */ + +#ifndef PSYCOPG_OWN_QUOTING +#define qstring_escape PQescapeString +#else +static size_t +qstring_escape(char *to, char *from, size_t len) +{ + int i, j; + + for (i=0, j=0; i<len; i++) { + switch(from[i]) { + + case '\'': + to[j++] = '\''; + to[j++] = '\''; + break; + + case '\\': + to[j++] = '\\'; + to[j++] = '\\'; + break; + + case '\0': + /* do nothing, embedded \0 are discarded */ + break; + + default: + to[j++] = from[i]; + } + } + to[j] = '\0'; + + Dprintf("qstring_quote: to = %s", to); + return strlen(to); +} +#endif + +/* qstring_quote - do the quote process on plain and unicode strings */ + +static PyObject * +qstring_quote(qstringObject *self) +{ + PyObject *str; + char *s, *buffer; + size_t len; + + /* if the wrapped object is an unicode object we can encode it to match + self->encoding but if the encoding is not specified we don't know what + to do and we raise an exception */ + + /* TODO: we need a real translation table from postgres encoding names to + python ones here */ + + if (PyUnicode_Check(self->wrapped) && self->encoding) { + PyObject *enc = PyDict_GetItemString(psycoEncodings, self->encoding); + /* note that pgenc is a borrowed reference */ + + if (enc) { + char *s = PyString_AsString(enc); + Dprintf("qstring_quote: encoding unicode object to %s", s); + str = PyUnicode_AsEncodedString(self->wrapped, s, NULL); + Dprintf("qstring_quote: got encoded object at %p", str); + if (str == NULL) return NULL; + } + else { + /* can't find the right encoder, raise exception */ + PyErr_Format(InterfaceError, + "can't encode unicode string to %s", self->encoding); + return NULL; + } + } + + /* if the wrapped object is a simple string, we don't know how to + (re)encode it, so we pass it as-is */ + else if (PyString_Check(self->wrapped)) { + str = self->wrapped; + /* INCREF to make it ref-wise identical to unicode one */ + Py_INCREF(str); + } + + /* if the wrapped object is not a string, this is an error */ + else { + PyErr_SetString(PyExc_TypeError, + "can't quote non-string object (or missing encoding)"); + return NULL; + } + + /* encode the string into buffer */ + s = PyString_AsString(str); + len = strlen(s); + + buffer = (char *)PyMem_Malloc((len*2+3) * sizeof(char)); + if (buffer == NULL) { + Py_DECREF(str); + PyErr_NoMemory(); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS; + len = qstring_escape(buffer+1, s, len); + buffer[0] = '\'' ; buffer[len+1] = '\''; + Py_END_ALLOW_THREADS; + + self->buffer = PyString_FromStringAndSize(buffer, len+2); + PyMem_Free(buffer); + Py_DECREF(str); + + return self->buffer; +} + +/* qstring_str, qstring_getquoted - return result of quoting */ + +static PyObject * +qstring_str(qstringObject *self) +{ + if (self->buffer == NULL) { + qstring_quote(self); + } + Py_XINCREF(self->buffer); + return self->buffer; +} + +PyObject * +qstring_getquoted(qstringObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return qstring_str(self); +} + +PyObject * +qstring_prepare(qstringObject *self, PyObject *args) +{ + connectionObject *conn; + + if (!PyArg_ParseTuple(args, "O", &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->encoding); + Dprintf("qstring_prepare: set encoding to %s", conn->encoding); + } + + Py_INCREF(Py_None); + return Py_None; +} + + +/** the QuotedString object **/ + +/* object member list */ + +static struct PyMemberDef qstringObject_members[] = { + {"adapted", T_OBJECT, offsetof(qstringObject, wrapped), RO}, + {"buffer", T_OBJECT, offsetof(qstringObject, buffer), RO}, + {"encoding", T_STRING, offsetof(qstringObject, encoding), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef qstringObject_methods[] = { + {"getquoted", (PyCFunction)qstring_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"prepare", (PyCFunction)qstring_prepare, METH_VARARGS, + "prepare(conn) -> set encoding to conn->encoding"}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +qstring_setup(qstringObject *self, PyObject *str, char *enc) +{ + Dprintf("qstring_setup: init qstring object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->buffer = NULL; + + /* FIXME: remove this orrible strdup */ + if (enc) self->encoding = strdup(enc); + + self->wrapped = str; + Py_INCREF(self->wrapped); + + Dprintf("qstring_setup: good qstring object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +qstring_dealloc(PyObject* obj) +{ + qstringObject *self = (qstringObject *)obj; + + Py_XDECREF(self->wrapped); + Py_XDECREF(self->buffer); + if (self->encoding) free(self->encoding); + + Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +qstring_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *str; + char *enc = "latin-1"; /* default encoding as in Python */ + + if (!PyArg_ParseTuple(args, "O|s", &str, &enc)) + return -1; + + return qstring_setup((qstringObject *)obj, str, enc); +} + +static PyObject * +qstring_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +qstring_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +qstring_repr(qstringObject *self) +{ + return PyString_FromFormat("<psycopg.QuotedString object at %p>", self); +} + +/* object type */ + +#define qstringType_doc \ +"psycopg.QuotedString(str, enc) -> new quoted object with 'enc' encoding" + +PyTypeObject qstringType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.QuotedString", + sizeof(qstringObject), + 0, + qstring_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + (reprfunc)qstring_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)qstring_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + + qstringType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + qstringObject_methods, /*tp_methods*/ + qstringObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + qstring_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + qstring_new, /*tp_new*/ + (freefunc)qstring_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_QuotedString(PyObject *module, PyObject *args) +{ + PyObject *str; + char *enc = "latin-1"; /* default encoding as in Python */ + + if (!PyArg_ParseTuple(args, "O|s", &str, &enc)) + return NULL; + + return PyObject_CallFunction((PyObject *)&qstringType, "Os", str, enc); +} diff --git a/psycopg/adapter_qstring.h b/psycopg/adapter_qstring.h new file mode 100644 index 0000000..6fd21e7 --- /dev/null +++ b/psycopg/adapter_qstring.h @@ -0,0 +1,51 @@ +/* adapter_qstring.h - definition for the QuotedString type + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_QSTRING_H +#define PSYCOPG_QSTRING_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject qstringType; + +typedef struct { + PyObject HEAD; + + PyObject *wrapped; + PyObject *buffer; + char *encoding; +} qstringObject; + +/* functions exported to psycopgmodule.c */ + +extern PyObject *psyco_QuotedString(PyObject *module, PyObject *args); +#define psyco_QuotedString_doc \ + "psycopg.QuotedString(str, enc) -> new quoted string" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_QSTRING_H) */ diff --git a/psycopg/config.h b/psycopg/config.h new file mode 100644 index 0000000..cc15ad8 --- /dev/null +++ b/psycopg/config.h @@ -0,0 +1,96 @@ +/* config.h - general config and Dprintf macro + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_CONFIG_H +#define PSYCOPG_CONFIG_H 1 + +/* replacement for asprintf() */ +#ifndef HAVE_ASPRINTF +extern int asprintf(char **buffer, char *fmt, ...); +#endif + +/* debug printf-like function */ +#if defined( __GNUC__) && !defined(__APPLE__) +#ifdef PSYCOPG_DEBUG +#include <sys/types.h> +#include <unistd.h> +#define Dprintf(fmt, args...) \ + fprintf(stderr, "[%d] " fmt "\n", getpid() , ## args) +#else +#define Dprintf(fmt, args...) +#endif +#else /* !__GNUC__ or __APPLE__ */ +#ifdef PSYCOPG_DEBUG +#include <stdarg.h> +static void Dprintf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); +} +#else +static void Dprintf(const char *fmt, ...) {} +#endif +#endif + +/* win32 specific stuff */ +#ifndef _WIN32 +#include <pthread.h> +#else +#include <winsock2.h> +#define pthread_mutex_t HANDLE +#define pthread_condvar_t HANDLE +#define pthread_mutex_lock(object) WaitForSingleObject(object, INFINITE) +#define pthread_mutex_unlock(object) ReleaseMutex(object) +#define pthread_mutex_destroy(ref) (CloseHandle(ref)) +/* convert pthread mutex to native mutex */ +static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake) +{ + *mutex = CreateMutex(NULL, FALSE, NULL); + return 0; +} +/* to work around the fact that Windows does not have a gmtime_r function, or + a proper gmtime function */ +static struct tm *gmtime_r(time_t *t, struct tm *tm) +{ + tm = gmtime(t); + return tm; +} +/* remove the inline keyword, since it doesn't work unless C++ file */ +#define inline +#endif + +#if defined(__FreeBSD__) || defined(_WIN32) +/* what's this, we have no round function either? */ +static double round(double num) +{ + return (num >= 0) ? floor(num + 0.5) : ceil(num - 0.5); +} +#endif + +/* postgresql < 7.4 does not have PQfreemem */ +#ifndef HAVE_PQFREEMEM +#define PQfreemem free +#endif + +#endif /* !defined(PSYCOPG_CONFIG_H) */ diff --git a/psycopg/connection.h b/psycopg/connection.h new file mode 100644 index 0000000..56193bd --- /dev/null +++ b/psycopg/connection.h @@ -0,0 +1,98 @@ +/* connection.h - definition for the psycopg connection type + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_CONNECTION_H +#define PSYCOPG_CONNECTION_H 1 + +#include <Python.h> +#include <libpq-fe.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* connection status */ +#define CONN_STATUS_READY 1 +#define CONN_STATUS_BEGIN 2 +#define CONN_STATUS_SYNC 3 +#define CONN_STATUS_ASYNC 4 + +extern PyTypeObject connectionType; + +typedef struct { + PyObject HEAD; + + PyObject *cursors; /* all cursors derived from this connection */ + + pthread_mutex_t lock; /* the global connection lock */ + + char *dsn; /* data source name */ + char *critical; /* critical error on this connection */ + char *encoding; /* current backend encoding */ + + long int closed; /* 2 means connection has been closed */ + long int isolation_level; /* isolation level for this connection */ + int status; /* status of the connection */ + int protocol; /* protocol version */ + + PGconn *pgconn; /* the postgresql connection */ + + PyObject *async_cursor; + + /* notice processing */ + PyObject *notice_list; + PyObject *notice_filter; + + /* notifies */ + PyObject *notifies; + + /* errors (DBAPI-2.0 extension) */ + PyObject *exc_Error; + PyObject *exc_Warning; + PyObject *exc_InterfaceError; + PyObject *exc_DatabaseError; + PyObject *exc_InternalError; + PyObject *exc_OperationalError; + PyObject *exc_ProgrammingError; + PyObject *exc_IntegrityError; + PyObject *exc_DataError; + PyObject *exc_NotSupportedError; + +} connectionObject; + +/* C-callable functions in connection_int.c and connection_ext.c */ +extern int conn_connect(connectionObject *self); +extern void conn_close(connectionObject *self); +extern int conn_commit(connectionObject *self); +extern int conn_rollback(connectionObject *self); +extern int conn_switch_isolation_level(connectionObject *self, int level); +extern int conn_set_client_encoding(connectionObject *self, char *enc); + +/* exception-raising macros */ +#define EXC_IF_CONN_CLOSED(self) if ((self)->closed > 0) { \ + PyErr_SetString(InterfaceError, "connection already closed"); \ + return NULL; } + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_CONNECTION_H) */ diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c new file mode 100644 index 0000000..8e72ff2 --- /dev/null +++ b/psycopg/connection_int.c @@ -0,0 +1,277 @@ +/* connection_int.c - code used by the connection object + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/psycopg.h" +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/pqpath.h" + +/* conn_notice_callback - process notices */ + +void +conn_notice_callback(void *args, const char *message) +{ + connectionObject *self = (connectionObject *)args; + + Dprintf("conn_notice_callback: %s", message); + + /* unfortunately the old protocl return COPY FROM errors only as notices, + so we need to filter them looking for such errors */ + if (strncmp(message, "ERROR", 5) == 0) + pq_set_critical(self, message); + else + PyList_Append(self->notice_list, PyString_FromString(message)); +} + +/* conn_connect - execute a connection to the dataabase */ + +int +conn_connect(connectionObject *self) +{ + PGconn *pgconn; + PGresult *pgres; + + /* we need the initial date style to be ISO, for typecasters; if the user + later change it, she must know what she's doing... */ + const char *datestyle = "SET DATESTYLE TO 'ISO'"; + const char *encoding = "SHOW client_encoding"; + + Py_BEGIN_ALLOW_THREADS; + pgconn = PQconnectdb(self->dsn); + Py_END_ALLOW_THREADS; + + Dprintf("conn_connect: new postgresql connection at %p", pgconn); + + if (pgconn == NULL) + { + Dprintf("conn_connect: PQconnectdb(%s) FAILED", self->dsn); + PyErr_SetString(OperationalError, "PQconnectdb() failed"); + return -1; + } + else if (PQstatus(pgconn) == CONNECTION_BAD) + { + Dprintf("conn_connect: PQconnectdb(%s) returned BAD", self->dsn); + PyErr_SetString(OperationalError, PQerrorMessage(pgconn)); + PQfinish(pgconn); + return -1; + } + + PQsetNoticeProcessor(pgconn, conn_notice_callback, (void*)self); + + Py_BEGIN_ALLOW_THREADS; + pgres = PQexec(pgconn, datestyle); + Py_END_ALLOW_THREADS; + + if (pgres == NULL || PQresultStatus(pgres) != PGRES_COMMAND_OK ) { + Dprintf("conn_connect: setting datestyle to iso FAILED"); + PyErr_SetString(OperationalError, "can't set datestyle to ISO"); + PQfinish(pgconn); + IFCLEARPGRES(pgres); + return -1; + } + CLEARPGRES(pgres); + + Py_BEGIN_ALLOW_THREADS; + pgres = PQexec(pgconn, encoding); + Py_END_ALLOW_THREADS; + + if (pgres == NULL || PQresultStatus(pgres) != PGRES_TUPLES_OK) { + Dprintf("conn_connect: fetching current client_encoding FAILED"); + PyErr_SetString(OperationalError, "can't fetch client_encoding"); + PQfinish(pgconn); + IFCLEARPGRES(pgres); + return -1; + } + self->encoding = strdup(PQgetvalue(pgres, 0, 0)); + CLEARPGRES(pgres); + + if (PQsetnonblocking(pgconn, 1) != 0) { + Dprintf("conn_connect: PQsetnonblocking() FAILED"); + PyErr_SetString(OperationalError, "PQsetnonblocking() failed"); + PQfinish(pgconn); + return -1; + } + +#ifdef HAVE_PQPROTOCOL3 + self->protocol = PQprotocolVersion(pgconn); +#else + self->protocol = 2; +#endif + Dprintf("conn_connect: using protocol %d", self->protocol); + + self->pgconn = pgconn; + return 0; +} + +/* conn_close - do anything needed to shut down the connection */ + +void +conn_close(connectionObject *self) +{ + int len, i; + PyObject *t = NULL; + + /* 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! */ + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + self->closed = 1; + + /* execute a forced rollback on the connection (but don't check the + result, we're going to close the pq connection anyway */ + if (self->pgconn) pq_abort(self); + + /* orphans all the children cursors but do NOT destroy them (note that we + need to lock the connection before orphaning a cursor: we don't want to + remove a connection from a cursor executing a DB operation */ + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + pthread_mutex_lock(&self->lock); + len = PyList_Size(self->cursors); + Dprintf("conn_close: ophaning %d cursors", len); + for (i = len-1; i >= 0; i--) { + t = PySequence_GetItem(self->cursors, i); + Dprintf("conn close: cursor at %p: refcnt = %d", t, t->ob_refcnt); + PySequence_DelItem(self->cursors, i); + ((cursorObject *)t)->conn = NULL; /* orphaned */ + Dprintf("conn_close: -> new refcnt = %d", t->ob_refcnt); + } + pthread_mutex_unlock(&self->lock); + + /* now that all cursors have been orphaned (they can't operate on the + database anymore) we can shut down the connection */ + if (self->pgconn) { + PQfinish(self->pgconn); + Dprintf("conn_close: PQfinish called"); + self->pgconn = NULL; + } +} + +/* conn_commit - commit on a connection */ + +int +conn_commit(connectionObject *self) +{ + int res; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + res = pq_commit(self); + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + return res; +} + +/* conn_rollback - rollback a connection */ + +int +conn_rollback(connectionObject *self) +{ + int res; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + res = pq_abort(self); + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + return res; +} + +/* conn_switch_isolation_level - switch isolation level on the connection */ + +int +conn_switch_isolation_level(connectionObject *self, int level) +{ + int res = 0; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + /* if the current isolation level is > 0 we need to abort the current + transaction before changing; that all folks! */ + if (self->isolation_level != level && self->isolation_level > 0) { + res = pq_abort(self); + } + self->isolation_level = level; + + Dprintf("conn_switch_isolation_level: switched to level %d", level); + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + return res; +} + +/* conn_set_client_encoding - switch client encoding on connection */ + +int +conn_set_client_encoding(connectionObject *self, char *enc) +{ + PGresult *pgres; + char query[48]; + int res = 0; + + /* TODO: check for async query here and raise error if necessary */ + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + /* set encoding, no encoding string is longer than 24 bytes */ + snprintf(query, 47, "SET client_encoding = '%s'", enc); + + /* abort the current transaction, to set the encoding ouside of + transactions */ + res = pq_abort(self); + + if (res == 0) { + pgres = PQexec(self->pgconn, query); + + if (pgres == NULL || PQresultStatus(pgres) != PGRES_COMMAND_OK ) { + PyErr_Format(OperationalError, "can't set encoding to '%s'", enc); + res = -1; + } + IFCLEARPGRES(pgres); + + if (self->encoding) free(self->encoding); + self->encoding = strdup(enc); + } + + Dprintf("conn_set_client_encoding: set encoding to %s", self->encoding); + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + return res; +} diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c new file mode 100644 index 0000000..376b7e6 --- /dev/null +++ b/psycopg/connection_type.c @@ -0,0 +1,401 @@ +/* connection_type.c - python interface to connection objects + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> + +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/connection.h" +#include "psycopg/cursor.h" + +/** DBAPI methods **/ + +/* cursor method - allocate a new cursor */ + +#define psyco_conn_cursor_doc \ +"cursor(factory=psycopg.cursor) -> new cursor\n\n" \ +"Return a new cursor. The 'factory' argument can be used to create\n" \ +"non-standard cursors by passing a class different from the default.\n" \ +"Note that the new class *should* be a sub-class of psycopg.cursor." + +static PyObject * +psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds) +{ + char *name = NULL; + PyObject *obj, *factory = NULL; + + static char *kwlist[] = {"name", "factory", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sO", kwlist, + &name, &factory)) { + return NULL; + } + + EXC_IF_CONN_CLOSED(self); + + Dprintf("psyco_conn_cursor: new cursor for connection at %p", self); + Dprintf("psyco_conn_cursor: parameters: name = %s", name); + + if (factory == NULL) factory = (PyObject *)&cursorType; + obj = PyObject_CallFunction(factory, "O", self); + + /* TODO: added error checking on obj (cursor) here */ + + /* add the cursor to this connection's list (and decref it, so that it has + the right number of references to go away even if still in the list) */ + PyList_Append(self->cursors, obj); + Py_DECREF(obj); + + Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = %d", + obj, obj->ob_refcnt); + return obj; +} + + + +/* close method - close the connection and all related cursors */ + +#define psyco_conn_close_doc "close() -> close the connection" + +static PyObject * +psyco_conn_close(connectionObject *self, PyObject *args) +{ + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTuple(args, "")) return NULL; + + Dprintf("psyco_conn_close: closing connection at %p", self); + conn_close(self); + Dprintf("psyco_conn_close: connection at %p closed", self); + + Py_INCREF(Py_None); + return Py_None; +} + + + +/* commit method - commit all changes to the database */ + +#define psyco_conn_commit_doc "commit() -> commit all changes to database" + +static PyObject * +psyco_conn_commit(connectionObject *self, PyObject *args) +{ + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTuple(args, "")) return NULL; + + /* FIXME: check return status? */ + conn_commit(self); + + Py_INCREF(Py_None); + return Py_None; +} + + + +/* rollback method - roll back all changes done to the database */ + +#define psyco_conn_rollback_doc \ +"rollback() -> roll back all changes done to database" + +static PyObject * +psyco_conn_rollback(connectionObject *self, PyObject *args) +{ + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTuple(args, "")) return NULL; + + /* FIXME: check return status? */ + conn_rollback(self); + + Py_INCREF(Py_None); + return Py_None; +} + + + + +/* set_isolation_level method - switch connection isolation level */ + +#define psyco_conn_set_isolation_level_doc \ +"set_isolation_level(level) -> swicth isolation level to 'level'" + +static PyObject * +psyco_conn_set_isolation_level(connectionObject *self, PyObject *args) +{ + int level = 1; + + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTuple(args, "i", &level)) return NULL; + + if (level < 0 || level > 3) { + PyErr_SetString(PyExc_ValueError, + "isolation level out of bounds (0,3)"); + return NULL; + } + + /* FIXME: check return status? */ + conn_switch_isolation_level(self, level); + + Py_INCREF(Py_None); + return Py_None; +} + + + +/* set_isolation_level method - switch connection isolation level */ + +#define psyco_conn_set_client_encoding_doc \ +"set_client_encoding(level) -> swicth isolation level to 'level'" + +static PyObject * +psyco_conn_set_client_encoding(connectionObject *self, PyObject *args) +{ + char *enc = NULL; + + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTuple(args, "s", &enc)) return NULL; + + if (conn_set_client_encoding(self, enc) == 0) { + Py_INCREF(Py_None); + return Py_None; + } + else { + return NULL; + } +} + + + +/** the connection object **/ + + +/* object method list */ + +static struct PyMethodDef connectionObject_methods[] = { + {"cursor", (PyCFunction)psyco_conn_cursor, + METH_VARARGS|METH_KEYWORDS, psyco_conn_cursor_doc}, + {"close", (PyCFunction)psyco_conn_close, + METH_VARARGS, psyco_conn_close_doc}, + {"commit", (PyCFunction)psyco_conn_commit, + METH_VARARGS, psyco_conn_commit_doc}, + {"rollback", (PyCFunction)psyco_conn_rollback, + METH_VARARGS, psyco_conn_rollback_doc}, +#ifdef PSYCOPG_EXTENSIONS + {"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level, + METH_VARARGS, psyco_conn_set_isolation_level_doc}, + {"set_client_encoding", (PyCFunction)psyco_conn_set_client_encoding, + METH_VARARGS, psyco_conn_set_client_encoding_doc}, +#endif + {NULL} +}; + +/* object member list */ + +static struct PyMemberDef connectionObject_members[] = { + /* DBAPI-2.0 extensions (exception objects) */ + {"Error", T_OBJECT, offsetof(connectionObject,exc_Error), RO}, + {"Warning", T_OBJECT, offsetof(connectionObject, exc_Warning), RO}, + {"InterfaceError", T_OBJECT, + offsetof(connectionObject, exc_InterfaceError), RO}, + {"DatabaseError", T_OBJECT, + offsetof(connectionObject, exc_DatabaseError), RO}, + {"InternalError", T_OBJECT, + offsetof(connectionObject, exc_InternalError), RO}, + {"OperationalError", T_OBJECT, + offsetof(connectionObject, exc_OperationalError), RO}, + {"ProgrammingError", T_OBJECT, + offsetof(connectionObject, exc_ProgrammingError), RO}, + {"IntegrityError", T_OBJECT, + offsetof(connectionObject, exc_IntegrityError), RO}, + {"DataError", T_OBJECT, + offsetof(connectionObject, exc_DataError), RO}, + {"NotSupportedError", T_OBJECT, + offsetof(connectionObject, exc_NotSupportedError), RO}, +#ifdef PSYCOPG_EXTENSIONS + {"closed", T_LONG, offsetof(connectionObject, closed), RO}, + {"isolation_level", T_LONG, + offsetof(connectionObject, isolation_level), RO}, + {"encoding", T_STRING, offsetof(connectionObject, encoding), RO}, + {"cursors", T_OBJECT, offsetof(connectionObject, cursors), RO}, + {"notices", T_OBJECT, offsetof(connectionObject, notice_list), RO}, + {"notifies", T_OBJECT, offsetof(connectionObject, notifies), RO}, + {"dsn", T_STRING, offsetof(connectionObject, dsn), RO}, +#endif + {NULL} +}; + +/* initialization and finalization methods */ + +static int +connection_setup(connectionObject *self, char *dsn) +{ + Dprintf("connection_setup: init connection object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->dsn = strdup(dsn); + self->cursors = PyList_New(0); + self->notice_list = PyList_New(0); + self->closed = 0; + self->isolation_level = 1; + self->status = CONN_STATUS_READY; + self->critical = NULL; + self->async_cursor = NULL; + self->pgconn = NULL; + + pthread_mutex_init(&(self->lock), NULL); + + if (conn_connect(self) != 0) { + Py_XDECREF(self->cursors); + pthread_mutex_destroy(&(self->lock)); + Dprintf("connection_init: FAILED"); + return -1; + } + + Dprintf("connection_setup: good connection object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +connection_dealloc(PyObject* obj) +{ + connectionObject *self = (connectionObject *)obj; + + if (self->closed == 0) conn_close(self); + + Py_XDECREF(self->cursors); + if (self->dsn) free(self->dsn); + if (self->encoding) free(self->encoding); + if (self->critical) free(self->critical); + + pthread_mutex_destroy(&(self->lock)); + + Dprintf("connection_dealloc: deleted connection object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +connection_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + char *dsn; + + if (!PyArg_ParseTuple(args, "s", &dsn)) + return -1; + + return connection_setup((connectionObject *)obj, dsn); +} + +static PyObject * +connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +connection_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +connection_repr(connectionObject *self) +{ + return PyString_FromFormat( + "<connection object at %p; dsn: '%s', closed: %ld>", + self, self->dsn, self->closed); +} + + +/* object type */ + +#define connectionType_doc \ +"connection(dsn, ...) -> new connection object" + +PyTypeObject connectionType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.connection", + sizeof(connectionObject), + 0, + connection_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)connection_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)connection_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + connectionType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + connectionObject_methods, /*tp_methods*/ + connectionObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + connection_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + connection_new, /*tp_new*/ + (freefunc)connection_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; diff --git a/psycopg/cursor.h b/psycopg/cursor.h new file mode 100644 index 0000000..2c2983e --- /dev/null +++ b/psycopg/cursor.h @@ -0,0 +1,87 @@ +/* cursor.h - definition for the psycopg cursor type + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_CURSOR_H +#define PSYCOPG_CURSOR_H 1 + +#include <Python.h> +#include <libpq-fe.h> + +#include "psycopg/connection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject cursorType; + +typedef struct { + PyObject HEAD; + + connectionObject *conn; /* connection owning the cursor */ + + int closed:1; /* 1 if the cursor is closed */ + int notuples:1; /* 1 if the command was not a SELECT query */ + + long int rowcount; /* number of rows affected by last execute */ + long int columns; /* number of columns fetched from the db */ + long int arraysize; /* how many rows should fetchmany() return */ + long int row; /* the row counter for fetch*() operations */ + + PyObject *description; /* read-only attribute: sequence of 7-item + sequences.*/ + + /* postgres connection stuff */ + PGresult *pgres; /* result of last query */ + PyObject *pgstatus; /* last message from the server after an execute */ + Oid lastoid; /* last oid from an insert or InvalidOid */ + + PyObject *casts; /* an array (tuple) of typecast functions */ + + PyObject *copyfile; /* file-like used during COPY TO/FROM ops */ + long int copysize; /* size of the copy buffer during COPY TO/FROM ops */ +#define DEFAULT_COPYSIZE 16384 + + PyObject *tuple_factory; /* factory for result tuples */ + PyObject *tzinfo_factory; /* factory for tzinfo objects */ + + char *qattr; /* quoting attr, used when quoting strings */ + char *notice; /* a notice from the backend */ + char *query; /* last query executed */ +} cursorObject; + +/* C-callable functions in cursor_int.c and cursor_ext.c */ +extern void curs_reset(cursorObject *self); + +/* exception-raising macros */ +#define EXC_IF_CURS_CLOSED(self) if ((self)->closed) { \ + PyErr_SetString(InterfaceError, "cursor already closed"); \ + return NULL; } + +#define EXC_IF_NO_TUPLES(self) if ((self)->notuples) { \ + PyErr_SetString(ProgrammingError, "no results to fetch"); \ + return NULL; } + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_CURSOR_H) */ diff --git a/psycopg/cursor_int.c b/psycopg/cursor_int.c new file mode 100644 index 0000000..8036268 --- /dev/null +++ b/psycopg/cursor_int.c @@ -0,0 +1,47 @@ +/* cursor_int.c - code used by the cursor object + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/psycopg.h" +#include "psycopg/cursor.h" +#include "psycopg/pqpath.h" + +/* curs_reset - reset the cursor to a clean state */ + +void +curs_reset(cursorObject *self) +{ + /* initialize some variables to default values */ + self->notuples = 1; + self->rowcount = -1; + self->row = 0; + + Py_XDECREF(self->description); + Py_INCREF(Py_None); + self->description = Py_None; + + Py_XDECREF(self->casts); + self->casts = NULL; +} diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c new file mode 100644 index 0000000..3998292 --- /dev/null +++ b/psycopg/cursor_type.c @@ -0,0 +1,1204 @@ +/* cursor_type.c - python interface to cursor objects + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/cursor.h" +#include "psycopg/connection.h" +#include "psycopg/pqpath.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" +#include "pgversion.h" + +/** DBAPI methods **/ + +/* close method - close the cursor */ + +#define psyco_curs_close_doc \ +"close() -> close the cursor" + +static PyObject * +psyco_curs_close(cursorObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + + EXC_IF_CURS_CLOSED(self); + + self->closed = 1; + Dprintf("psyco_curs_close: cursor at %p closed", self); + + Py_INCREF(Py_None); + return Py_None; +} + + + +/* execute method - executes a query */ + +/* mogrify a query string and build argument array or dict */ + +static int +_mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) +{ + PyObject *key, *value, *n, *item; + char *d, *c; + int index = 0; + + /* from now on we'll use n and replace its value in *new only at the end, + just before returning. we also init *new to NULL to exit with an error + if we can't complete the mogrification */ + n = *new = NULL; + c = PyString_AsString(fmt); + + while(*c) { + /* handle plain percent symbol in format string */ + if (c[0] == '%' && c[1] == '%') { + c+=2; + } + + /* if we find '%(' then this is a dictionary, we: + 1/ find the matching ')' and extract the key name + 2/ locate the value in the dictionary (or return an error) + 3/ mogrify the value into something usefull (quoting)... + 4/ ...and add it to the new dictionary to be used as argument + */ + else if (c[0] == '%' && c[1] == '(') { + + /* let's have d point the end of the argument */ + for (d = c + 2; *d && *d != ')'; d++); + + if (*d == ')') { + key = PyString_FromStringAndSize(c+2, d-c-2); + value = PyObject_GetItem(var, key); + /* key has refcnt 1, value the original value + 1 */ + + /* if value is NULL we did not find the key (or this is not a + dictionary): let python raise a KeyError */ + if (value == NULL) { + Py_DECREF(key); /* destroy key */ + Py_XDECREF(n); /* destroy n */ + return -1; + } + + Dprintf("_mogrify: value refcnt: %d (+1)", value->ob_refcnt); + + if (n == NULL) { + n = PyDict_New(); + } + + if ((item = PyObject_GetItem(n, key)) == NULL) { + PyObject *t = NULL; + + PyErr_Clear(); + + /* None is always converted to NULL; this is an + optimization over the adapting code and can go away in + the future if somebody finds a None adapter usefull. */ + if (value == Py_None) { + t = PyString_FromString("NULL"); + PyDict_SetItem(n, key, t); + /* t is a new object, refcnt = 1, key is at 2 */ + + /* if the value is None we need to substitute the + formatting char with 's' (FIXME: this should not be + necessary if we drop support for formats other than + %s!) */ + while (*d && !isalpha(*d)) d++; + if (*d) *d = 's'; + } + else { + t = microprotocols_adapt(value, + (PyObject*)&isqlquoteType, + NULL); + if (t != NULL) { + /* t is a new object, refcnt = 1 */ + Dprintf("_mogrify: adapted to %s", + t->ob_type->tp_name); + + /* prepare the object passing it the connection */ + PyObject_CallMethod(t, "prepare", "O", + (PyObject*)conn); + + PyDict_SetItem(n, key, t); + /* both key and t refcnt +1, key is at 2 now */ + } + else { + /* we did not found an adapter but we don't raise + an exception; just pass the original value */ + PyErr_Clear(); + PyDict_SetItem(n, key, value); + Dprintf("_mogrify: set value refcnt: %d", + value->ob_refcnt); + } + } + + Py_XDECREF(t); /* t dies here */ + /* after the DECREF value has the original refcnt plus 1 + if it was added to the dictionary directly; good */ + Py_XDECREF(value); + } + else { + /* we have an item with one extra refcnt here, zap! */ + Py_DECREF(item); + } + Py_DECREF(key); /* key has the original refcnt now */ + Dprintf("_mogrify: after value refcnt: %d",value->ob_refcnt); + } + c = d; + } + + else if (c[0] == '%' && c[1] != '(') { + /* this is a format that expects a tuple; it is much easier, + because we don't need to check the old/new dictionary for + keys */ + + value = PySequence_GetItem(var, index); + /* value has refcnt inc'ed by 1 here */ + + /* if value is NULL this is not a sequence or the index is wrong; + anyway we let python set its own exception */ + if (value == NULL) { + Py_XDECREF(n); + return -1; + } + + if (n == NULL) { + n = PyTuple_New(PyObject_Length(var)); + } + + /* let's have d point just after the '%' */ + d = c+1; + + if (value == Py_None) { + PyTuple_SET_ITEM(n, index, PyString_FromString("NULL")); + while (*d && !isalpha(*d)) d++; + if (*d) *d = 's'; + Py_DECREF(value); + } + else { + PyObject *t = microprotocols_adapt(value, + (PyObject*)&isqlquoteType, + NULL); + if (t != NULL) { + /* prepare the object passing it the connection */ + PyObject_CallMethod(t, "prepare", "O", (PyObject*)conn); + + PyTuple_SET_ITEM(n, index, t); + Py_DECREF(value); + } + else { + PyErr_Clear(); + PyTuple_SET_ITEM(n, index, value); + /* here we steal value ref, no need to DECREF */ + } + } + c = d; + index += 1; + } + else { + c++; + } + } + + *new = n; + return 0; +} + +#define psyco_curs_execute_doc \ +"execute(query, vars=None, async=0) -> execute query with bound vars" + +static PyObject * +psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + int res; + long int async = 0; + PyObject *vars = NULL, *cvt = NULL, *operation = NULL; + PyObject *fquery, *uoperation = NULL; + + static char *kwlist[] = {"query", "vars", "async", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Oi", kwlist, + &operation, &vars, &async)) { + return NULL; + } + + if (PyUnicode_Check(operation)) { + PyObject *enc = PyDict_GetItemString(psycoEncodings, + self->conn->encoding); + /* enc is a borrowed reference, we won't decref it */ + + if (enc) { + operation = PyUnicode_AsEncodedString( + operation, PyString_AsString(enc), NULL); + /* we clone operation in uoperation to be sure to free it later */ + uoperation = operation; + } + else { + PyErr_Format(InterfaceError, "can't encode unicode query to %s", + self->conn->encoding); + return NULL; + } + } + + EXC_IF_CURS_CLOSED(self); + IFCLEARPGRES(self->pgres); + + if (self->query) { + free(self->query); + self->query = NULL; + } + + Dprintf("psyco_curs_execute: starting execution of new query"); + + /* here we are, and we have a sequence or a dictionary filled with + objects to be substituted (bound variables). we try to be smart and do + the right thing (i.e., what the user expects), so: + + 1. if the bound variable is None the format string is changed into a %s + (just like now) and the variable substituted with the "NULL" string; + + 2. if a bound variable has the .sqlquote method, we suppose it is able + to do the required quoting by itself: we call the method and + substitute the result in the sequence/dictionary. if the result of + calling .sqlquote is not a string object or the format string is not + %s we raise an error; + + 3. if a bound variable does not have the .sqlquote method AND the + format string is %s str() is called on the variable and the result + wrapped in a psycopg.QuotedString object; + + 4. if the format string is not %s we suppose the object is capable to + format itself accordingly, so we don't touch it. + + let's go... */ + + if (vars) + { + if(_mogrify(vars, operation, self->conn, &cvt) == -1) { + Py_XDECREF(uoperation); + return NULL; + } + } + + if (vars && cvt) { + /* if PyString_Format() return NULL an error occured: if the error is + a TypeError we need to check the exception.args[0] string for the + values: + + "not enough arguments for format string" + "not all arguments converted" + + and return the appropriate ProgrammingError. we do that by grabbing + the curren exception (we will later restore it if the type or the + strings do not match.) */ + + if (!(fquery = PyString_Format(operation, cvt))) { + PyObject *err, *arg, *trace; + int pe = 0; + + PyErr_Fetch(&err, &arg, &trace); + + if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) { + Dprintf("psyco_curs_execute: TypeError exception catched"); + PyErr_NormalizeException(&err, &arg, &trace); + + if (PyObject_HasAttrString(arg, "args")) { + PyObject *args = PyObject_GetAttrString(arg, "args"); + PyObject *str = PySequence_GetItem(args, 0); + char *s = PyString_AS_STRING(str); + + Dprintf("psyco_curs_execute: -> %s", s); + + if (!strcmp(s, "not enough arguments for format string") + || !strcmp(s, "not all arguments converted")) { + Dprintf("psyco_curs_execute: -> got a match"); + PyErr_SetString(ProgrammingError, s); + pe = 1; + } + + Py_DECREF(args); + Py_DECREF(str); + } + } + + /* if we did not manage our own exception, restore old one */ + if (pe == 1) { + Py_XDECREF(err); Py_XDECREF(arg); Py_XDECREF(trace); + } + else { + PyErr_Restore(err, arg, trace); + } + Py_XDECREF(uoperation); + return NULL; + } + self->query = strdup(PyString_AS_STRING(fquery)); + + Dprintf("psyco_curs_execute: cvt->refcnt = %d, fquery->refcnt = %d", + cvt->ob_refcnt, fquery->ob_refcnt); + Py_DECREF(fquery); + Py_DECREF(cvt); + } + else { + self->query = strdup(PyString_AS_STRING(operation)); + } + + res = pq_execute(self, self->query, async); + + Dprintf("psyco_curs_execute: res = %d, pgres = %p", res, self->pgres); + + Py_XDECREF(uoperation); + + if (res == -1) { + return NULL; + } + else { + Py_INCREF(Py_None); + return Py_None; + } +} + +#ifdef PSYCOPG_EXTENSIONS +#define psyco_curs_mogrify_doc \ +"mogrify(query, vars=None) -> return query after binding vars" + +static PyObject * +psyco_curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *vars = NULL, *cvt = NULL, *operation = NULL; + PyObject *fquery; + + static char *kwlist[] = {"query", "vars", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + &operation, &vars)) { + return NULL; + } + + if (PyUnicode_Check(operation)) { + PyErr_SetString(NotSupportedError, + "unicode queries not yet supported"); + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + IFCLEARPGRES(self->pgres); + + /* note that we don't overwrite the last query executed on the cursor, we + just *return* the new query with bound variables + + TODO: refactor the common mogrification code (see psycopg_curs_execute + for comments, the code is amost identical) */ + + if (vars) + { + if(_mogrify(vars, operation, self->conn, &cvt) == -1) return NULL; + } + + if (vars && cvt) { + if (!(fquery = PyString_Format(operation, cvt))) { + PyObject *err, *arg, *trace; + int pe = 0; + + PyErr_Fetch(&err, &arg, &trace); + + if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) { + Dprintf("psyco_curs_execute: TypeError exception catched"); + PyErr_NormalizeException(&err, &arg, &trace); + + if (PyObject_HasAttrString(arg, "args")) { + PyObject *args = PyObject_GetAttrString(arg, "args"); + PyObject *str = PySequence_GetItem(args, 0); + char *s = PyString_AS_STRING(str); + + Dprintf("psyco_curs_execute: -> %s", s); + + if (!strcmp(s, "not enough arguments for format string") + || !strcmp(s, "not all arguments converted")) { + Dprintf("psyco_curs_execute: -> got a match"); + PyErr_SetString(ProgrammingError, s); + pe = 1; + } + + Py_DECREF(args); + Py_DECREF(str); + } + } + + /* if we did not manage our own exception, restore old one */ + if (pe == 1) { + Py_XDECREF(err); Py_XDECREF(arg); Py_XDECREF(trace); + } + else { + PyErr_Restore(err, arg, trace); + } + return NULL; + } + + Dprintf("psyco_curs_execute: cvt->refcnt = %d, fquery->refcnt = %d", + cvt->ob_refcnt, fquery->ob_refcnt); + Py_DECREF(cvt); + } + else { + fquery = operation; + Py_INCREF(operation); + } + + return fquery; +} +#endif + + + +/* fetchone method - fetch one row of results */ + +#define psyco_curs_fetchone_doc \ +"fetchone() -> next tuple of data or None\n\n" \ +"Return the next row of a query result set in the form of a tuple (by\n" \ +"default) or using the sequence factory previously set in the tuplefactory\n" \ +"attribute. Return None when no more data is available.\n" + +static int +_psyco_curs_prefetch(cursorObject *self) +{ + int i = 0; + + /* check if the fetching cursor is the one that did the asynchronous query + and raise an exception if not */ + if (self->conn->async_cursor != NULL + && self->conn->async_cursor != (PyObject*)self) { + PyErr_SetString(ProgrammingError, + "asynchronous fetch by wrong cursor"); + return -2; + } + + if (self->pgres == NULL) { + Dprintf("_psyco_curs_prefetch: trying to fetch data"); + do { + i = pq_fetch(self); + Dprintf("_psycopg_curs_prefetch: result = %d", i); + } while(i == 1); + } + + Dprintf("_psyco_curs_prefetch: result = %d", i); + return i; +} + +static PyObject * +_psyco_curs_buildrow_fill(cursorObject *self, PyObject *res, int row, int n) +{ + int i; + PyObject *str, *val; + + for (i=0; i < n; i++) { + if (PQgetisnull(self->pgres, row, i)) { + Py_INCREF(Py_None); + str = Py_None; + } + else { + char *s = PQgetvalue(self->pgres, row, i); + int l = PQgetlength(self->pgres, row, i); + str = PyString_FromStringAndSize(s, l); + + Dprintf("_psyco_curs_buildrow: row %ld, element %d, len %i", + self->row, i, l); + } + + Dprintf("_psyco_curs_buildrow: str->refcnt = %d", str->ob_refcnt); + val = PyObject_CallFunction(PyTuple_GET_ITEM(self->casts, i), "OO", + str, self); + /* here we DECREF str because the typecaster should already have + INCREFfed it, if necessary and we don't need it anymore */ + Py_DECREF(str); + Dprintf("_psyco_curs_buildrow: str->refcnt = %d", str->ob_refcnt); + + if (val) { + Dprintf("_psyco_curs_buildrow: val->refcnt = %d", val->ob_refcnt); + PySequence_SetItem(res, i, val); + Py_DECREF(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; + } + } + return res; + +} + +static PyObject * +_psyco_curs_buildrow(cursorObject *self, int row) +{ + int n; + + n = PQnfields(self->pgres); + return _psyco_curs_buildrow_fill(self, PyTuple_New(n), row, n); +} + +static PyObject * +_psyco_curs_buildrow_with_factory(cursorObject *self, int row) +{ + int n; + PyObject *res; + + n = PQnfields(self->pgres); + if ((res = PyObject_CallFunction(self->tuple_factory, "O", self))== NULL) + return NULL; + + return _psyco_curs_buildrow_fill(self, res, row, n); +} + + +PyObject * +psyco_curs_fetchone(cursorObject *self, PyObject *args) +{ + PyObject *res; + + if (args && !PyArg_ParseTuple(args, "")) return NULL; + + EXC_IF_CURS_CLOSED(self) + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + Dprintf("psyco_curs_fetchone: fetching row %ld", self->row); + Dprintf("psyco_curs_fetchone: rowcount = %ld", self->rowcount); + + if (self->row >= self->rowcount) { + /* we exausted available data: return None */ + Py_INCREF(Py_None); + 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); + + self->row++; /* move the counter to next line */ + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount + + && self->conn->async_cursor == (PyObject*)self) + IFCLEARPGRES(self->pgres); + + return res; +} + + + +/* fetch many - fetch some results */ + +#define psyco_curs_fetchmany_doc \ +"fetchone(size=10000) -> next size tuples of data or None\n\n" \ +"Return the next 'size' rows of a query result set in the form of a tuple\n" \ +"of tuples (by default) or using the sequence factory previously set in\n" \ +"the tuplefactory attribute. Return None when no more data is available.\n" + +PyObject * +psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) +{ + int i; + PyObject *list, *res; + + long int size = self->arraysize; + static char *kwlist[] = {"size", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwords, "|l", kwlist, &size)) { + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + /* make sure size is not > than the available number of rows */ + if (size > self->rowcount - self->row || size < 0) { + size = self->rowcount - self->row; + } + + Dprintf("psyco_curs_fetchmany: size = %ld", size); + + if (size <= 0) { + return PyList_New(0); + } + + list = PyList_New(size); + + 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); + + self->row++; + + if (res == NULL) { + Py_DECREF(list); + return NULL; + } + + PyList_SET_ITEM(list, i, res); + } + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount && self->conn->async_cursor) + IFCLEARPGRES(self->pgres); + + return list; +} + + +/* fetch all - fetch all results */ + +#define psyco_curs_fetchall_doc \ +"fetchall() -> all remaining tuples of data or None\n\n" \ +"Return all the remaining rows of a query result set in the form of a\n" \ +"tuple of tuples (by default) or using the sequence factory previously\n" \ +"set in the tuplefactory attribute. Return None when no more data is\n" \ +"available.\n" + +PyObject * +psyco_curs_fetchall(cursorObject *self, PyObject *args) +{ + int i, size; + PyObject *list, *res; + + if (!PyArg_ParseTuple(args, "")) { + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + size = self->rowcount - self->row; + + if (size <= 0) { + return PyList_New(0); + } + + list = PyList_New(size); + + 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); + + self->row++; + + if (res == NULL) { + Py_DECREF(list); + return NULL; + } + + PyList_SET_ITEM(list, i, res); + } + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount && self->conn->async_cursor) + IFCLEARPGRES(self->pgres); + + return list; +} + + + +/* callproc method - execute a stored procedure (not YET supported) */ + +#define psyco_curs_callproc_doc \ +"callproc(procname, [parameters]) -> execute stored procedure\n\n" \ +"This method is not (yet) impelemented and calling it raise an exception." + +static PyObject * +psyco_curs_callproc(cursorObject *self, PyObject *args) +{ + PyObject *procname, *procargs; + + if (!PyArg_ParseTuple(args, "O|O", &procname, &procargs)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + PyErr_SetString(NotSupportedError, "not yet implemented"); + return NULL; +} + + + +/* nextset method - return the next set of data (not supported) */ + +#define psyco_curs_nextset_doc \ +"nextset() -> skip to next set of data\n\n" \ +"This method is not supported (PostgreSQL does not have multiple data \n" \ +"sets) and will raise a NotSupportedError exception." + +static PyObject * +psyco_curs_nextset(cursorObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + + EXC_IF_CURS_CLOSED(self); + + PyErr_SetString(NotSupportedError, "not supported by PostgreSQL"); + return NULL; +} + + + +/* setinputsizes - predefine memory areas for execute (does nothing) */ + +#define psyco_curs_setinputsizes_doc \ +"setinputsizes(sizes) -> set memory areas before execute\n\n" \ +"This method currently does nothing but it is safe to call it." + +static PyObject * +psyco_curs_setinputsizes(cursorObject *self, PyObject *args) +{ + PyObject *sizes; + + if (!PyArg_ParseTuple(args, "O", &sizes)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + Py_INCREF(Py_None); + return Py_None; +} + + + +/* setoutputsize - predefine memory areas for execute (does nothing) */ + +#define psyco_curs_setoutputsize_doc \ +"setoutputsize(size, [column]) -> set column buffer size\n\n" \ +"This method currently does nothing but it is safe to call it." + +static PyObject * +psyco_curs_setoutputsize(cursorObject *self, PyObject *args) +{ + long int size, column; + + if (!PyArg_ParseTuple(args, "l|l", &size, &column)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + Py_INCREF(Py_None); + return Py_None; +} + + + +/* scroll - scroll position in result list */ + +#define psyco_curs_scroll_doc \ +"scroll(value, mode='relative') -> scroll to new position according to mode" + +static PyObject * +psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + int value, newpos; + char *mode = "relative"; + + static char *kwlist[] = {"value", "mode", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|s", + kwlist, &value, &mode)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + if (strcmp(mode, "relative") == 0) { + newpos = self->row + value; + } else if ( strcmp( mode, "absolute") == 0) { + newpos = value; + } else { + PyErr_SetString(ProgrammingError, + "scroll mode must be 'relative' or 'absolute'"); + return NULL; + } + + if (newpos < 0 || newpos >= self->rowcount ) { + PyErr_SetString(PyExc_IndexError, + "scroll destination out of bounds"); + return NULL; + } + + self->row = newpos; + + Py_INCREF(Py_None); + return Py_None; +} + + + +#ifdef PSYCOPG_EXTENSIONS + +/* extension: copy_from - implements COPY FROM */ + +#define psyco_curs_copy_from_doc \ +"copy_from(file, table, sep='\\t', null='NULL') -> copy file to table." + +static PyObject * +psyco_curs_copy_from(cursorObject *self, PyObject *args) +{ + char *table_name, *query = NULL; + char *sep = "\t", *null ="NULL"; + long int bufsize = DEFAULT_COPYSIZE; + PyObject *file, *res = NULL; + + if (!PyArg_ParseTuple(args, "O!s|ssi", + &PyFile_Type, &file, &table_name, + &sep, &null, &bufsize)) { + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + + asprintf(&query, "COPY %s FROM stdin USING DELIMITERS '%s'" + " WITH NULL AS '%s'", table_name, sep, null); + Dprintf("psyco_curs_copy_from: query = %s", query); + + self->copysize = bufsize; + self->copyfile = file; + Py_INCREF(file); + + if (pq_execute(self, query, 0) == 1) { + res = Py_None; + Py_INCREF(Py_None); + } + + free(query); + + return res; +} + +/* extension: fileno - return the file descripor of the connection */ + +#define psyco_curs_fileno_doc \ +"fileno() -> return file descriptor associated to database connection" + +static PyObject * +psyco_curs_fileno(cursorObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + EXC_IF_CURS_CLOSED(self); + + /* note how we call PQflush() to make sure the user will use + select() in the safe way! */ + PQflush(self->conn->pgconn); + return PyInt_FromLong((long int)PQsocket(self->conn->pgconn)); +} + +/* extension: isready - return true if data from async execute is ready */ + +#define psyco_curs_isready_doc \ +"isready() -> return True if data is ready after an async query" + +static PyObject * +psyco_curs_isready(cursorObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + EXC_IF_CURS_CLOSED(self); + + if (pq_is_busy(self->conn)) { + Py_INCREF(Py_False); + return Py_False; + } + else { + Py_INCREF(Py_True); + return Py_True; + } +} + +#endif + + + +/** the cursor object **/ + +/* iterator protocol */ + +static PyObject * +cursor_iter(PyObject *self) +{ + EXC_IF_CURS_CLOSED((cursorObject*)self); + Py_INCREF(self); + return self; +} + +static PyObject * +cursor_next(PyObject *self) +{ + PyObject *res; + + /* we don't parse argumente: psyco_curs_fetchone will do that for us */ + res = psyco_curs_fetchone((cursorObject*)self, NULL); + + /* convert a None to NULL to signal the end of iteration */ + if (res && res == Py_None) { + Py_DECREF(res); + res = NULL; + } + return res; +} + +/* object method list */ + +static struct PyMethodDef cursorObject_methods[] = { + /* DBAPI-2.0 core */ + {"close", (PyCFunction)psyco_curs_close, + METH_VARARGS, psyco_curs_close_doc}, + {"execute", (PyCFunction)psyco_curs_execute, + METH_VARARGS|METH_KEYWORDS, psyco_curs_execute_doc}, + {"fetchone", (PyCFunction)psyco_curs_fetchone, + METH_VARARGS, psyco_curs_fetchone_doc}, + {"fetchmany", (PyCFunction)psyco_curs_fetchmany, + METH_VARARGS|METH_KEYWORDS, psyco_curs_fetchmany_doc}, + {"fetchall", (PyCFunction)psyco_curs_fetchall, + METH_VARARGS, psyco_curs_fetchall_doc}, + {"callproc", (PyCFunction)psyco_curs_callproc, + METH_VARARGS, psyco_curs_callproc_doc}, + {"nextset", (PyCFunction)psyco_curs_nextset, + METH_VARARGS, psyco_curs_nextset_doc}, + {"setinputsizes", (PyCFunction)psyco_curs_setinputsizes, + METH_VARARGS, psyco_curs_setinputsizes_doc}, + {"setoutputsize", (PyCFunction)psyco_curs_setoutputsize, + METH_VARARGS, psyco_curs_setoutputsize_doc}, + /* DBAPI-2.0 extensions */ + {"scroll", (PyCFunction)psyco_curs_scroll, + METH_VARARGS|METH_KEYWORDS, psyco_curs_scroll_doc}, + /* psycopg extensions */ +#ifdef PSYCOPG_EXTENSIONS + {"mogrify", (PyCFunction)psyco_curs_mogrify, + METH_VARARGS|METH_KEYWORDS, psyco_curs_mogrify_doc}, + {"fileno", (PyCFunction)psyco_curs_fileno, + METH_VARARGS, psyco_curs_fileno_doc}, + {"isready", (PyCFunction)psyco_curs_isready, + METH_VARARGS, psyco_curs_isready_doc}, + {"copy_from", (PyCFunction)psyco_curs_copy_from, + METH_VARARGS, psyco_curs_copy_from_doc}, +#endif + {NULL} +}; + +/* object member list */ + +#define OFFSETOF(x) offsetof(cursorObject, x) + +static struct PyMemberDef cursorObject_members[] = { + /* DBAPI-2.0 basics */ + {"rowcount", T_LONG, OFFSETOF(rowcount), RO}, + {"arraysize", T_LONG, OFFSETOF(arraysize), 0}, + {"description", T_OBJECT, OFFSETOF(description), RO}, + {"lastrowid", T_LONG, OFFSETOF(lastoid), RO}, + /* DBAPI-2.0 extensions */ + {"rownumber", T_LONG, OFFSETOF(row), RO}, + {"connection", T_OBJECT, OFFSETOF(conn), RO}, +#ifdef PSYCOPG_EXTENSIONS + {"statusmessage", T_OBJECT, OFFSETOF(pgstatus), RO}, + {"query", T_STRING, OFFSETOF(query), RO}, + {"tuple_factory", T_OBJECT, OFFSETOF(tuple_factory), 0}, + {"tzinfo_factory", T_OBJECT, OFFSETOF(tzinfo_factory), 0}, +#endif + {NULL} +}; + +/* initialization and finalization methods */ + +static int +cursor_setup(cursorObject *self, connectionObject *conn) +{ + Dprintf("cursor_setup: init cursor object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->conn = conn; + self->closed = 0; + + self->pgres = NULL; + self->notuples = 1; + self->arraysize = 1; + self->rowcount = -1; + self->lastoid = InvalidOid; + + self->casts = NULL; + self->notice = NULL; + self->query = NULL; + + self->description = Py_None; + Py_INCREF(Py_None); + self->pgstatus = Py_None; + Py_INCREF(Py_None); + self->tuple_factory = Py_None; + Py_INCREF(Py_None); + self->tzinfo_factory = Py_None; + Py_INCREF(Py_None); + + Dprintf("cursor_setup: good cursor object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +cursor_dealloc(PyObject* obj) +{ + cursorObject *self = (cursorObject *)obj; + + /* if necessary remove cursor from connection */ + if (self->conn != NULL) { + PyObject *t; + int len, i; + + if ((len = PyList_Size(self->conn->cursors)) > 0) { + for (i = 0; i < len; i++) { + t = PyList_GET_ITEM(self->conn->cursors, i); + if (self == (cursorObject *)t) { + Dprintf("cursor_dealloc: found myself in cursor list"); + PySequence_DelItem(self->conn->cursors, i); + break; + } + } + } + } + + if (self->query) free(self->query); + + Py_XDECREF(self->casts); + Py_XDECREF(self->description); + Py_XDECREF(self->pgstatus); + Py_XDECREF(self->tuple_factory); + Py_XDECREF(self->tzinfo_factory); + + IFCLEARPGRES(self->pgres); + + Dprintf("cursor_dealloc: deleted cursor object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +cursor_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *conn; + if (!PyArg_ParseTuple(args, "O", &conn)) + return -1; + + return cursor_setup((cursorObject *)obj, (connectionObject *)conn); +} + +static PyObject * +cursor_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +cursor_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +cursor_repr(cursorObject *self) +{ + return PyString_FromFormat( + "<cursor object at %p; closed: %d>", self, self->closed); +} + + +/* object type */ + +#define cursorType_doc \ +"cursor(conn) -> new cursor object" + +PyTypeObject cursorType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.cursor", + sizeof(cursorObject), + 0, + cursor_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)cursor_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)cursor_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER, /*tp_flags*/ + cursorType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + cursor_iter, /*tp_iter*/ + cursor_next, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + cursorObject_methods, /*tp_methods*/ + cursorObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + cursor_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + cursor_new, /*tp_new*/ + (freefunc)cursor_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c new file mode 100644 index 0000000..c9c2af8 --- /dev/null +++ b/psycopg/microprotocols.c @@ -0,0 +1,121 @@ +/* microprotocols.c - minimalist and non-validating protocols implementation + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/cursor.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" + + +/** the adapters registry **/ + +PyObject *psyco_adapters; + +/* microprotocols_init - initialize the adapters dictionary */ + +int +microprotocols_init(PyObject *dict) +{ + /* create adapters dictionary and put it in module namespace */ + if ((psyco_adapters = PyDict_New()) == NULL) { + return -1; + } + + PyDict_SetItemString(dict, "adapters", psyco_adapters); + + return 0; +} + + +/* microprotocols_add - add a reverse type-caster to the dictionary */ + +int +microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast) +{ + if (proto == NULL) proto = (PyObject*)&isqlquoteType; + + Dprintf("microprotocols_add: cast %p for (%s, ?)", + cast, type->tp_name); + + PyDict_SetItem(psyco_adapters, + Py_BuildValue("(OO)", (PyObject*)type, proto), + cast); + return 0; +} + +/* microprotocols_adapt - adapt an object to the built-in protocol */ + +PyObject * +microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) +{ + PyObject *adapter, *key; + + /* we don't check for exact type conformance as specified in PEP 246 + because the ISQLQuote type is abstract and there is no way to get a + quotable object to be its instance */ + + /* look for an adapter in the registry */ + key = Py_BuildValue("(OO)", (PyObject*)obj->ob_type, proto); + adapter = PyDict_GetItem(psyco_adapters, key); + Py_DECREF(key); + if (adapter) { + PyObject *adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); + return adapted; + } + + /* try to have the protocol adapt this object*/ + if (PyObject_HasAttrString(proto, "__adapt__")) { + PyObject *adapted = PyObject_CallMethod(proto, "__adapt__", "O", obj); + if (adapted && adapted != Py_None) return adapted; + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)) + return NULL; + } + + /* and finally try to have the object adapt itself */ + if (PyObject_HasAttrString(obj, "__conform__")) { + PyObject *adapted = PyObject_CallMethod(proto, "__conform__","O", obj); + if (adapted && adapted != Py_None) return adapted; + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)) + return NULL; + } + + /* else set the right exception and return NULL */ + PyErr_SetString(ProgrammingError, "can't adapt"); + return NULL; +} + +/** module-level functions **/ + +PyObject * +psyco_microprotocols_adapt(cursorObject *self, PyObject *args) +{ + PyObject *obj, *alt = NULL; + PyObject *proto = (PyObject*)&isqlquoteType; + + if (!PyArg_ParseTuple(args, "O|OO", &obj, &proto, &alt)) return NULL; + return microprotocols_adapt(obj, proto, alt); +} diff --git a/psycopg/microprotocols.h b/psycopg/microprotocols.h new file mode 100644 index 0000000..4cdeaad --- /dev/null +++ b/psycopg/microprotocols.h @@ -0,0 +1,55 @@ +/* microprotocols.c - definitions for minimalist and non-validating protocols + * + * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_MICROPROTOCOLS_H +#define PSYCOPG_MICROPROTOCOLS_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** adapters registry **/ + +extern PyObject *psyco_adapters; + +/** the names of the three mandatory methods **/ + +#define MICROPROTOCOLS_GETQUOTED_NAME "getquoted" +#define MICROPROTOCOLS_GETSTRING_NAME "getstring" +#define MICROPROTOCOLS_GETBINARY_NAME "getbinary" + +/** exported functions **/ + +/* used by module.c to init the microprotocols system */ +extern int microprotocols_init(PyObject *dict); +extern int microprotocols_add( + PyTypeObject *type, PyObject *proto, PyObject *cast); +extern PyObject *microprotocols_adapt( + PyObject *obj, PyObject *proto, PyObject *alt); + +extern PyObject * + psyco_microprotocols_adapt(cursorObject *self, PyObject *args); +#define psyco_microprotocols_adapt_doc \ + "psycopg.adapt(obj, protocol, alternate) -> adapt obj to given protocol" + +#endif /* !defined(PSYCOPG_MICROPROTOCOLS_H) */ diff --git a/psycopg/microprotocols_proto.c b/psycopg/microprotocols_proto.c new file mode 100644 index 0000000..0c63e2c --- /dev/null +++ b/psycopg/microprotocols_proto.c @@ -0,0 +1,211 @@ +/* microprotocol_proto.c - psycopg protocols + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> +#include <stringobject.h> + +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/microprotocols_proto.h" + + +/** void protocol implementation **/ + +/* getquoted - return quoted representation for object */ + +#define psyco_isqlquote_getquoted_doc \ +"getquoted() -> return SQL-quoted representation of this object" + +static PyObject * +psyco_isqlquote_getquoted(isqlquoteObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/* getbinary - return quoted representation for object */ + +#define psyco_isqlquote_getbinary_doc \ +"getbinary() -> return SQL-quoted binary representation of this object" + +static PyObject * +psyco_isqlquote_getbinary(isqlquoteObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/* getbuffer - return quoted representation for object */ + +#define psyco_isqlquote_getbuffer_doc \ +"getbuffer() -> return this object" + +static PyObject * +psyco_isqlquote_getbuffer(isqlquoteObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + + + +/** the ISQLQuote object **/ + + +/* object method list */ + +static struct PyMethodDef isqlquoteObject_methods[] = { + {"getquoted", (PyCFunction)psyco_isqlquote_getquoted, + METH_VARARGS, psyco_isqlquote_getquoted_doc}, + {"getbinary", (PyCFunction)psyco_isqlquote_getbinary, + METH_VARARGS, psyco_isqlquote_getbinary_doc}, + {"getbuffer", (PyCFunction)psyco_isqlquote_getbuffer, + METH_VARARGS, psyco_isqlquote_getbuffer_doc}, + {NULL} +}; + +/* object member list */ + +static struct PyMemberDef isqlquoteObject_members[] = { + /* DBAPI-2.0 extensions (exception objects) */ + {"_wrapped", T_OBJECT, offsetof(isqlquoteObject, wrapped), RO}, + {NULL} +}; + +/* initialization and finalization methods */ + +static int +isqlquote_setup(isqlquoteObject *self, PyObject *wrapped) +{ + self->wrapped = wrapped; + Py_INCREF(wrapped); + + return 0; +} + +static void +isqlquote_dealloc(PyObject* obj) +{ + isqlquoteObject *self = (isqlquoteObject *)obj; + + Py_XDECREF(self->wrapped); + + obj->ob_type->tp_free(obj); +} + +static int +isqlquote_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *wrapped = NULL; + + if (!PyArg_ParseTuple(args, "O", &wrapped)) + return -1; + + return isqlquote_setup((isqlquoteObject *)obj, wrapped); +} + +static PyObject * +isqlquote_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +isqlquote_del(PyObject* self) +{ + PyObject_Del(self); +} + + +/* object type */ + +#define isqlquoteType_doc \ +"Abstract ISQLQuote protocol" + +PyTypeObject isqlquoteType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg.ISQLQuote", + sizeof(isqlquoteObject), + 0, + isqlquote_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + isqlquoteType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + isqlquoteObject_methods, /*tp_methods*/ + isqlquoteObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + isqlquote_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + isqlquote_new, /*tp_new*/ + (freefunc)isqlquote_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; diff --git a/psycopg/microprotocols_proto.h b/psycopg/microprotocols_proto.h new file mode 100644 index 0000000..c671264 --- /dev/null +++ b/psycopg/microprotocols_proto.h @@ -0,0 +1,45 @@ +/* microporotocols_proto.h - definiton for psycopg's protocols + * + * Copyright (C) 2004 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_ISQLQUOTE_H +#define PSYCOPG_ISQLQUOTE_H 1 + +#include <Python.h> +#include <libpq-fe.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject isqlquoteType; + +typedef struct { + PyObject HEAD; + + PyObject *wrapped; + +} isqlquoteObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_ISQLQUOTE_H) */ diff --git a/psycopg/pgtypes.h b/psycopg/pgtypes.h new file mode 100644 index 0000000..968b062 --- /dev/null +++ b/psycopg/pgtypes.h @@ -0,0 +1,60 @@ +#define BOOLOID 16 +#define BYTEAOID 17 +#define CHAROID 18 +#define NAMEOID 19 +#define INT8OID 20 +#define INT2OID 21 +#define INT2VECTOROID 22 +#define INT4OID 23 +#define REGPROCOID 24 +#define TEXTOID 25 +#define OIDOID 26 +#define TIDOID 27 +#define XIDOID 28 +#define CIDOID 29 +#define OIDVECTOROID 30 +#define POINTOID 600 +#define LSEGOID 601 +#define PATHOID 602 +#define BOXOID 603 +#define POLYGONOID 604 +#define LINEOID 628 +#define FLOAT4OID 700 +#define FLOAT8OID 701 +#define ABSTIMEOID 702 +#define RELTIMEOID 703 +#define TINTERVALOID 704 +#define UNKNOWNOID 705 +#define CIRCLEOID 718 +#define CASHOID 790 +#define MACADDROID 829 +#define INETOID 869 +#define CIDROID 650 +#define ACLITEMOID 1033 +#define BPCHAROID 1042 +#define VARCHAROID 1043 +#define DATEOID 1082 +#define TIMEOID 1083 +#define TIMESTAMPOID 1114 +#define TIMESTAMPTZOID 1184 +#define INTERVALOID 1186 +#define TIMETZOID 1266 +#define BITOID 1560 +#define VARBITOID 1562 +#define NUMERICOID 1700 +#define REFCURSOROID 1790 +#define REGPROCEDUREOID 2202 +#define REGOPEROID 2203 +#define REGOPERATOROID 2204 +#define REGCLASSOID 2205 +#define REGTYPEOID 2206 +#define RECORDOID 2249 +#define CSTRINGOID 2275 +#define ANYOID 2276 +#define ANYARRAYOID 2277 +#define VOIDOID 2278 +#define TRIGGEROID 2279 +#define LANGUAGE_HANDLEROID 2280 +#define INTERNALOID 2281 +#define OPAQUEOID 2282 +#define ANYELEMENTOID 2283 diff --git a/psycopg/pgversion.h b/psycopg/pgversion.h new file mode 100644 index 0000000..f874a9a --- /dev/null +++ b/psycopg/pgversion.h @@ -0,0 +1,2 @@ +#define PG_VERSION_MAJOR 7 +#define PG_VERSION_MINOR 4 diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c new file mode 100644 index 0000000..0f5beb1 --- /dev/null +++ b/psycopg/pqpath.c @@ -0,0 +1,777 @@ +/* pqpath.c - single path into libpq + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <string.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/pqpath.h" +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/typecast.h" +#include "psycopg/pgtypes.h" +#include "psycopg/pgversion.h" + +/* pq_raise - raise a python exception of the right kind */ + +void +pq_raise(connectionObject *conn, cursorObject *curs, PyObject *exc, char *msg) +{ + char *err = NULL; + + if ((conn == NULL && curs == NULL) || (curs != NULL && conn == NULL)) { + PyErr_SetString(Error, + "psycopg went psycotic and raised a null error"); + return; + } + + if (curs && curs->pgres) + err = PQresultErrorMessage(curs->pgres); + if (err == NULL) + err = PQerrorMessage(conn->pgconn); + + /* if the is no error message we probably called pq_raise without reason: + we need to set an exception anyway because the caller will probably + raise and a meaningful message is better than an empty one */ + if (err == NULL) { + PyErr_SetString(Error, "psycopg went psycotic without error set"); + return; + } + + /* if exc is NULL, analyze the message and try to deduce the right + exception kind (only if we have a pgres, obviously) */ + if (exc == NULL) { + if (curs && curs->pgres) { + if (conn->protocol == 3) { +#ifdef HAVE_PQPROTOCOL3 + char *pgstate = PQresultErrorField(curs->pgres, + PG_DIAG_SQLSTATE); + if (!strncmp(pgstate, "23", 2)) + exc = IntegrityError; + else + exc = ProgrammingError; +#endif + } + } + } + + /* if exc is still NULL psycopg was not built with HAVE_PQPROTOCOL3 or the + connection is using protocol 2: in both cases we default to comparing + error messages */ + if (exc == NULL) { + if (!strncmp(err, "ERROR: Cannot insert a duplicate key", 37) + || !strncmp(err, "ERROR: ExecAppend: Fail to add null", 36) + || strstr(err, "referential integrity violation")) + exc = IntegrityError; + else + exc = ProgrammingError; + } + + /* try to remove the initial "ERROR: " part from the postgresql error */ + if (err && strlen(err) > 8) err = &(err[8]); + + /* if msg is not NULL, add it to the error message, after a '\n' */ + if (msg) { + PyErr_Format(exc, "%s\n%s", err, msg); + } + else { + PyErr_SetString(exc, err); + } +} + +/* pq_set_critical, pq_resolve_critical - manage critical errors + + this function is invoked when a PQexec() call returns NULL, meaning a + critical condition like out of memory or lost connection. it save the error + message and mark the connection as 'wanting cleanup'. + + both functions do not call any Py_*_ALLOW_THREADS macros. */ + +void +pq_set_critical(connectionObject *conn, const char *msg) +{ + if (msg == NULL) + msg = PQerrorMessage(conn->pgconn); + if (conn->critical) free(conn->critical); + if (msg && msg[0] != '\0') conn->critical = strdup(msg); + else conn->critical = NULL; +} + +PyObject * +pq_resolve_critical(connectionObject *conn, int close) +{ + if (conn->critical) { + char *msg = &(conn->critical[6]); + Dprintf("pq_resolve_critical: error = %s", msg); + /* we can't use pq_raise because the error has already been cleared + from the connection, so we just raise an OperationalError with the + critical message */ + PyErr_SetString(OperationalError, msg); + + /* we don't want to destroy this connection but just close it */ + if (close == 1) conn_close(conn); + } + return NULL; +} + +/* pq_clear_async - clear the effects of a previous async query + + note that this function does block because it needs to wait for the full + result sets of the previous query to clear them. + + this function does not call any Py_*_ALLOW_THREADS macros */ + +void +pq_clear_async(connectionObject *conn) +{ + PGresult *pgres; + + do { + pgres = PQgetResult(conn->pgconn); + IFCLEARPGRES(pgres); + } while (pgres != NULL); +} + +/* pq_begin - send a BEGIN WORK, if necessary + + this function does not call any Py_*_ALLOW_THREADS macros */ + +int +pq_begin(connectionObject *conn) +{ + const char *query[] = { + NULL, + "BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED", + "BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", + "BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"}; + + int pgstatus, retvalue = -1; + PGresult *pgres = NULL; + + Dprintf("pq_begin: pgconn = %p, isolevel = %ld, status = %d", + conn->pgconn, conn->isolation_level, conn->status); + + if (conn->isolation_level == 0 || conn->status != CONN_STATUS_READY) { + Dprintf("pq_begin: transaction in progress"); + return 0; + } + + pq_clear_async(conn); + pgres = PQexec(conn->pgconn, query[conn->isolation_level]); + if (pgres == NULL) { + Dprintf("pq_begin: PQexec() failed"); + pq_set_critical(conn, NULL); + goto cleanup; + } + + pgstatus = PQresultStatus(pgres); + if (pgstatus != PGRES_COMMAND_OK ) { + Dprintf("pq_begin: result is NOT OK"); + pq_set_critical(conn, NULL); + goto cleanup; + } + Dprintf("pq_begin: issued '%s' command", query[conn->isolation_level]); + + retvalue = 0; + conn->status = CONN_STATUS_BEGIN; + + cleanup: + IFCLEARPGRES(pgres); + return retvalue; +} + +/* pq_commit - send an END, if necessary + + this function does not call any Py_*_ALLOW_THREADS macros */ + +int +pq_commit(connectionObject *conn) +{ + const char *query = "END"; + int pgstatus, retvalue = -1; + PGresult *pgres = NULL; + + Dprintf("pq_commit: pgconn = %p, isolevel = %ld, status = %d", + conn->pgconn, conn->isolation_level, conn->status); + + if (conn->isolation_level == 0 || conn->status != CONN_STATUS_BEGIN) { + Dprintf("pq_commit: no transaction to commit"); + return 0; + } + + pq_clear_async(conn); + pgres = PQexec(conn->pgconn, query); + if (pgres == NULL) { + Dprintf("pq_commit: PQexec() failed"); + pq_set_critical(conn, NULL); + goto cleanup; + } + + pgstatus = PQresultStatus(pgres); + if (pgstatus != PGRES_COMMAND_OK ) { + Dprintf("pq_commit: result is NOT OK"); + pq_set_critical(conn, NULL); + goto cleanup; + } + Dprintf("pq_commit: issued '%s' command", query); + + retvalue = 0; + conn->status = CONN_STATUS_READY; + + cleanup: + IFCLEARPGRES(pgres); + return retvalue; +} + +/* pq_abort - send an ABORT, if necessary + + this function does not call any Py_*_ALLOW_THREADS macros */ + +int +pq_abort(connectionObject *conn) +{ + const char *query = "ABORT"; + int pgstatus, retvalue = -1; + PGresult *pgres = NULL; + + Dprintf("pq_abort: pgconn = %p, isolevel = %ld, status = %d", + conn->pgconn, conn->isolation_level, conn->status); + + if (conn->isolation_level == 0 || conn->status != CONN_STATUS_BEGIN) { + Dprintf("pq_abort: no transaction to abort"); + return 0; + } + + pq_clear_async(conn); + pgres = PQexec(conn->pgconn, query); + if (pgres == NULL) { + Dprintf("pq_abort: PQexec() failed"); + pq_set_critical(conn, NULL); + goto cleanup; + } + + pgstatus = PQresultStatus(pgres); + if (pgstatus != PGRES_COMMAND_OK ) { + Dprintf("pq_abort: result is NOT OK"); + pq_set_critical(conn, NULL); + goto cleanup; + } + Dprintf("pq_abort: issued '%s' command", query); + + retvalue = 0; + conn->status = CONN_STATUS_READY; + + cleanup: + IFCLEARPGRES(pgres); + return retvalue; +} + +/* pq_is_busy - consume input and return connection status + + a status of 1 means that a call to pq_fetch will block, while a status of 0 + means that there is data available to be collected. -1 means an error, the + exception will be set accordingly. */ + +int +pq_is_busy(connectionObject *conn) +{ + PGnotify *pgn; + + Dprintf("pq_is_busy: consuming input"); + if (PQconsumeInput(conn->pgconn) == 0) { + Dprintf("pq_is_busy: PQconsumeInput() failed"); + PyErr_SetString(OperationalError, PQerrorMessage(conn->pgconn)); + return -1; + } + + /* now check for notifies */ + while ((pgn = PQnotifies(conn->pgconn)) != NULL) { + PyObject *notify; + + Dprintf("curs_is_busy: got NOTIFY from pid %d, msg = %s", + pgn->be_pid, pgn->relname); + + notify = PyTuple_New(2); + PyTuple_SET_ITEM(notify, 0, PyInt_FromLong((long)pgn->be_pid)); + PyTuple_SET_ITEM(notify, 1, PyString_FromString(pgn->relname)); + pthread_mutex_lock(&(conn->lock)); + PyList_Append(conn->notifies, notify); + pthread_mutex_unlock(&(conn->lock)); + free(pgn); + } + + return PQisBusy(conn->pgconn); +} + +/* pq_execute - execute a query, possibly asyncronously + + this fucntion locks the connection object + this function call Py_*_ALLOW_THREADS macros */ + +int +pq_execute(cursorObject *curs, const char *query, int async) +{ + int err; + + /* 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; + } + + /* check status of connection, raise error if not OK */ + if (PQstatus(curs->conn->pgconn) != CONNECTION_OK) { + Dprintf("pq_execute: connection NOT OK"); + PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn)); + return -1; + } + Dprintf("curs_execute: pg connection at %p OK", curs->conn->pgconn); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(curs->conn->lock)); + + pq_begin(curs->conn); + + if (async == 0) { + IFCLEARPGRES(curs->pgres); + Dprintf("pq_execute: executing SYNC query:"); + Dprintf(" %-.200s", query); + curs->pgres = PQexec(curs->conn->pgconn, query); + } + + else if (async == 1) { + /* first of all, let see if the previous query has already ended, if + not what should we do? just block and discard data or execute + another query? */ + pq_clear_async(curs->conn); + + Dprintf("pq_execute: executing ASYNC query:"); + Dprintf(" %-.200s", query); + + /* then we can go on and send a new query without fear */ + IFCLEARPGRES(curs->pgres); + if (PQsendQuery(curs->conn->pgconn, query) == 0) { + pthread_mutex_unlock(&(curs->conn->lock)); + Py_BLOCK_THREADS; + PyErr_SetString(OperationalError, + PQerrorMessage(curs->conn->pgconn)); + return -1; + } + Dprintf("pq_execute: async query sent to backend"); + } + + pthread_mutex_unlock(&(curs->conn->lock)); + Py_END_ALLOW_THREADS; + + /* if the execute was sync, we call pq_fetch() immediately, + to respect the old DBAPI-2.0 compatible behaviour */ + if (async == 0) { + Dprintf("pq_execute: entering syncronous DBAPI compatibility mode"); + do { + err = pq_fetch(curs); + if (err == -1) return -1; + } while (err == 1); + } + else { + curs->conn->async_cursor = (PyObject*)curs; + } + + return 1-async; +} + + +/* pq_fetch - fetch data after a query + + this fucntion locks the connection object + this function call Py_*_ALLOW_THREADS macros + + return value: + -1 - some error occurred while calling libpq + 0 - no result from the backend but no libpq errors + 1 - result from backend (possibly data is ready) +*/ + +static void +_pq_fetch_tuples(cursorObject *curs) +{ + int i, *dsize = NULL; + + int pgnfields = PQnfields(curs->pgres); + int pgbintuples = PQbinaryTuples(curs->pgres); + + curs->notuples = 0; + + /* create the tuple for description and typecasting */ + Py_XDECREF(curs->description); + Py_XDECREF(curs->casts); + curs->description = PyTuple_New(pgnfields); + curs->casts = PyTuple_New(pgnfields); + curs->columns = pgnfields; + + /* calculate the display size for each column (cpu intensive, can be + switched off at configuration time) */ +#ifdef PSYCOPG_DISPLAY_SIZE + dsize = (int *)calloc(pgnfields, sizeof(int)); + if (dsize != NULL) { + if (curs->rowcount == 0) { + for (i=0; i < pgnfields; i++) + dsize[i] = -1; + } + else { + int j, len; + for (j = 0; j < curs->rowcount; j++) { + for (i = 0; i < pgnfields; i++) { + len = PQgetlength(curs->pgres, j, i); + if (len > dsize[i]) dsize[i] = len; + } + } + } + } +#endif + + /* calculate various parameters and typecasters */ + for (i = 0; i < pgnfields; i++) { + Oid ftype = PQftype(curs->pgres, i); + int fsize = PQfsize(curs->pgres, i); + int fmod = PQfmod(curs->pgres, i); + + PyObject *dtitem = PyTuple_New(7); + PyObject *type = PyInt_FromLong(ftype); + PyObject *cast; + + PyTuple_SET_ITEM(curs->description, i, dtitem); + + /* fill the right cast function by accessing the global dictionary of + casting objects. if we got no defined cast use the default + one */ + if (!(cast = PyDict_GetItem(curs->casts, type))) { + Dprintf("_pq_fetch_tuples: cast %d not in per-cursor dict", ftype); + if (!(cast = PyDict_GetItem(psyco_types, type))) { + Dprintf("_pq_fetch_tuples: cast %d not found, using default", + PQftype(curs->pgres,i)); + cast = psyco_default_cast; + } + } + /* else if we got binary tuples and if we got a field that + is binary use the default cast. + */ + else if (pgbintuples && cast == psyco_default_binary_cast) { + Dprintf("_pq_fetch_tuples: Binary cursor and " + "binary field: %i using default cast", + PQftype(curs->pgres,i)); + cast = psyco_default_cast; + } + Dprintf("_pq_fetch_tuples: using cast at %p (%s) for type %d", + cast, PyString_AS_STRING(((typecastObject*)cast)->name), + PQftype(curs->pgres,i)); + Py_INCREF(cast); + PyTuple_SET_ITEM(curs->casts, i, cast); + + + /* 1/ fill the other fields */ + PyTuple_SET_ITEM(dtitem, 0, + PyString_FromString(PQfname(curs->pgres, i))); + PyTuple_SET_ITEM(dtitem, 1, type); + + /* 2/ display size is the maximum size of this field result tuples. */ + if (dsize && dsize[i] >= 0) { + PyTuple_SET_ITEM(dtitem, 2, PyInt_FromLong(dsize[i])); + } + else { + Py_INCREF(Py_None); + PyTuple_SET_ITEM(dtitem, 2, Py_None); + } + + /* 3/ size on the backend */ + if (fmod > 0) fmod = fmod - sizeof(int); + if (fsize == -1) { + if (ftype == NUMERICOID) { + PyTuple_SET_ITEM(dtitem, 3, + PyInt_FromLong((fmod >> 16) & 0xFFFF)); + } + else { /* If variable length record, return maximum size */ + PyTuple_SET_ITEM(dtitem, 3, PyInt_FromLong(fmod)); + } + } + else { + PyTuple_SET_ITEM(dtitem, 3, PyInt_FromLong(fsize)); + } + + /* 4,5/ scale and precision */ + if (ftype == NUMERICOID) { + PyTuple_SET_ITEM(dtitem, 4, PyInt_FromLong((fmod >> 16) & 0xFFFF)); + PyTuple_SET_ITEM(dtitem, 5, PyInt_FromLong(fmod & 0xFFFF)); + } + else { + Py_INCREF(Py_None); + PyTuple_SET_ITEM(dtitem, 4, Py_None); + Py_INCREF(Py_None); + PyTuple_SET_ITEM(dtitem, 5, Py_None); + } + + /* 6/ FIXME: null_ok??? */ + Py_INCREF(Py_None); + PyTuple_SET_ITEM(dtitem, 6, Py_None); + } + + if (dsize) free(dsize); +} + +#ifdef HAVE_PQPROTOCOL3 +static int +_pq_copy_in(cursorObject *curs) +{ + /* COPY FROM implementation when protocol 3 is available: this function + uses the new PQputCopyData() and can detect errors and set the correct + exception */ + + return -1; +} +#else +static int +_pq_copy_in(cursorObject *curs) +{ + /* COPY FROM implementation when protocol 3 is not available: this + function can't fail but the backend will send an ERROR notice that will + be catched by our notice collector */ + PyObject *o; + + while (1) { + o = PyObject_CallMethod(curs->copyfile, "readline", NULL); + if (!o || o == Py_None || PyString_GET_SIZE(o) == 0) break; + if (PQputline(curs->conn->pgconn, PyString_AS_STRING(o)) != 0) { + Py_DECREF(o); + return -1; + } + Py_DECREF(o); + } + Py_XDECREF(o); + PQputline(curs->conn->pgconn, "\\.\n"); + PQendcopy(curs->conn->pgconn); + + return 1; +} +#endif + +#ifdef HAVE_PQPROTOCOL3 +static int +_pq_copy_out(cursorObject *curs) +{ + char *buffer; + int len; + + while (1) { + Py_BEGIN_ALLOW_THREADS; + len = PQgetCopyData(curs->conn->pgconn, &buffer, 0); + Py_END_ALLOW_THREADS; + + if (len > 0 && buffer) { + PyObject_CallMethod(curs->copyfile, "write", "s", buffer); + PQfreemem(buffer); + } + /* we break on len == 0 but note that that should *not* happen, + because we are not doing an async call (if it happens blame + postgresql authors :/) */ + else if (len <= 0) break; + } + + if (len == -2) { + pq_raise(curs->conn, NULL, NULL, NULL); + return -1; + } + + return 1; +} +#else +static int +_pq_copy_out(cursorObject *curs) +{ + char buffer[4096]; + int status, len; + PyObject *o; + + while (1) { + Py_BEGIN_ALLOW_THREADS; + status = PQgetline(curs->conn->pgconn, buffer, 4096); + if (status == 0) { + if (buffer[0] == '\\' && buffer[1] == '.') break; + + len = strlen(buffer); + buffer[len++] = '\n'; + } + else if (status == 1) { + len = 4096-1; + } + else { + Py_BLOCK_THREADS; + return -1; + } + Py_END_ALLOW_THREADS; + + o = PyString_FromStringAndSize(buffer, len); + PyObject_CallMethod(curs->copyfile, "write", "O", o); + Py_DECREF(o); + } + + if (PQendcopy(curs->conn->pgconn) != 0) return -1; + + return 1; +} +#endif + +int +pq_fetch(cursorObject *curs) +{ + int pgstatus, ex = -1; + + /* even if we fail, we remove any information about the previous query */ + curs_reset(curs); + + /* we check the result from the previous execute; if the result is not + already there, we need to consume some input and go to sleep until we + get something edible to eat */ + if (!curs->pgres) { + + Dprintf("pq_fetch: no data: entering polling loop"); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(curs->conn->lock)); + + while (pq_is_busy(curs->conn) > 0) { + fd_set rfds; + struct timeval tv; + int sval, sock; + + sock = PQsocket(curs->conn->pgconn); + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + + /* set a default timeout of 5 seconds + TODO: make use of the timeout, maybe allowing the user to + make a non-blocking (timeouted) call to fetchXXX */ + tv.tv_sec = 5; + tv.tv_usec = 0; + + Dprintf("pq_fetch: entering PDflush() loop"); + while (PQflush(curs->conn->pgconn) != 0); + sval = select(sock+1, &rfds, NULL, NULL, &tv); + } + + Dprintf("pq_fetch: data is probably ready"); + IFCLEARPGRES(curs->pgres); + curs->pgres = PQgetResult(curs->conn->pgconn); + + pthread_mutex_unlock(&(curs->conn->lock)); + Py_END_ALLOW_THREADS; + } + + /* check for PGRES_FATAL_ERROR result */ + /* FIXME: I am not sure we need to check for critical error here. + if (curs->pgres == NULL) { + 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; + } + else { + return 0; + } + } + */ + + if (curs->pgres == NULL) return 0; + + pgstatus = PQresultStatus(curs->pgres); + Dprintf("pq_fetch: pgstatus = %s", PQresStatus(pgstatus)); + + /* backend status message */ + Py_XDECREF(curs->pgstatus); + curs->pgstatus = PyString_FromString(PQcmdStatus(curs->pgres)); + + /* rowcount has a meaning even for INSERT and UPDATES but to get the right + number we need to check two times, one with PQntuples for SELECts and + one with PQcmdTuples for other queries */ + curs->rowcount = PQntuples(curs->pgres); + if (curs->rowcount == 0) + curs->rowcount = atoi(PQcmdTuples(curs->pgres)); + + switch(pgstatus) { + + case PGRES_COMMAND_OK: + Dprintf("pq_fetch: command returned OK (no tuples)"); + curs->rowcount = 0; + curs->lastoid = PQoidValue(curs->pgres); + CLEARPGRES(curs->pgres); + ex = 1; + break; + + case PGRES_COPY_OUT: + Dprintf("pq_fetch: data from a COPY TO (no tuples)"); + curs->rowcount = 0; + ex = _pq_copy_out(curs); + /* error caught by out glorious notice handler */ + if (PyErr_Occurred()) ex = -1; + IFCLEARPGRES(curs->pgres); + break; + + case PGRES_COPY_IN: + Dprintf("pq_fetch: data from a COPY FROM (no tuples)"); + curs->rowcount = 0; + ex = _pq_copy_in(curs); + /* error caught by out glorious notice handler */ + if (PyErr_Occurred()) ex = -1; + IFCLEARPGRES(curs->pgres); + break; + + case PGRES_TUPLES_OK: + Dprintf("pq_fetch: data from a SELECT (got tuples)"); + _pq_fetch_tuples(curs); ex = 0; + /* don't clear curs->pgres, because it contains the results! */ + break; + + default: + Dprintf("pq_fetch: uh-oh, something FAILED"); + pq_raise(curs->conn, curs, NULL, NULL); + IFCLEARPGRES(curs->pgres); + ex = -1; + break; + } + + /* error checking, close the connection if necessary (some critical errors + are not really critical, like a COPY FROM error: if that's the case we + raise the exception but we avoid to close the connection) */ + if (curs->conn->critical) { + if (ex == -1) { + pq_resolve_critical(curs->conn, 1); + } + else { + pq_resolve_critical(curs->conn, 0); + } + return -1; + } + + return ex; +} diff --git a/psycopg/pqpath.h b/psycopg/pqpath.h new file mode 100644 index 0000000..64d113e --- /dev/null +++ b/psycopg/pqpath.h @@ -0,0 +1,41 @@ +/* pqpath.h - definitions for pqpath.c + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_PQPATH_H +#define PSYCOPG_PQPATH_H 1 + +#include "psycopg/cursor.h" +#include "psycopg/connection.h" + +/* macros to clean the pg result */ +#define IFCLEARPGRES(pgres) if (pgres) {PQclear(pgres); pgres = NULL;} +#define CLEARPGRES(pgres) PQclear(pgres); pgres = NULL + +/* exported functions */ +extern int pq_fetch(cursorObject *curs); +extern int pq_execute(cursorObject *curs, const char *query, int async); +extern int pq_begin(connectionObject *conn); +extern int pq_commit(connectionObject *conn); +extern int pq_abort(connectionObject *conn); +extern int pq_is_busy(connectionObject *conn); +extern void pq_set_critical(connectionObject *conn, const char *msg); + +#endif /* !defined(PSYCOPG_PQPATH_H) */ diff --git a/psycopg/psycopg.h b/psycopg/psycopg.h new file mode 100644 index 0000000..4da312e --- /dev/null +++ b/psycopg/psycopg.h @@ -0,0 +1,107 @@ +/* psycopg.h - definitions for the psycopg python module + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_H +#define PSYCOPG_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* DBAPI compliance parameters */ +#define APILEVEL "2.0" +#define THREADSAFETY 2 +#define PARAMSTYLE "pyformat" + +/* C API functions */ +#define psyco_errors_fill_NUM 0 +#define psyco_errors_fill_RETURN void +#define psyco_errors_fill_PROTO (PyObject *dict) +#define psyco_errors_set_NUM 1 +#define psyco_errors_set_RETURN void +#define psyco_errors_set_PROTO (PyObject *type) + +/* Total number of C API pointers */ +#define PSYCOPG_API_pointers 2 + +#ifdef PSYCOPG_MODULE + /** This section is used when compiling psycopgmodule.c & co. **/ +extern psyco_errors_fill_RETURN psyco_errors_fill psyco_errors_fill_PROTO; +extern psyco_errors_set_RETURN psyco_errors_set psyco_errors_set_PROTO; + +/* global excpetions */ +extern PyObject *Error, *Warning, *InterfaceError, *DatabaseError, + *InternalError, *OperationalError, *ProgrammingError, + *IntegrityError, *DataError, *NotSupportedError; + +/* python versions and compatibility stuff */ +#ifndef PyMODINIT_FUNC +#define PyMODINIT_FUNC void +#endif + +#else + /** This section is used in modules that use psycopg's C API **/ + +static void **PSYCOPG_API; + +#define psyco_errors_fill \ + (*(psyco_errors_fill_RETURN (*)psyco_errors_fill_PROTO) \ + PSYCOPG_API[psyco_errors_fill_NUM]) +#define psyco_errors_set \ + (*(psyco_errors_set_RETURN (*)psyco_errors_set_PROTO) \ + PSYCOPG_API[psyco_errors_set_NUM]) + +/* Return -1 and set exception on error, 0 on success. */ +static int +import_psycopg(void) +{ + PyObject *module = PyImport_ImportModule("psycopg"); + + if (module != NULL) { + PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) return -1; + if (PyCObject_Check(c_api_object)) + PSYCOPG_API = (void **)PyCObject_AsVoidPtr(c_api_object); + Py_DECREF(c_api_object); + } + return 0; +} + +#endif + +/* postgresql<->python encoding map */ +extern PyObject *psycoEncodings; + +typedef struct { + char *pgenc; + char *pyenc; +} encodingPair; + +/* the Decimal type, used by the DECIMAL typecaster */ +extern PyObject *decimalType; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_H) */ diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c new file mode 100644 index 0000000..0d9e40d --- /dev/null +++ b/psycopg/psycopgmodule.c @@ -0,0 +1,477 @@ +/* psycopgmodule.c - psycopg module (will import other C classes) + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/typecast.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" + +#include "psycopg/adapter_qstring.h" +#include "psycopg/adapter_binary.h" +#include "psycopg/adapter_pboolean.h" + + +#ifdef HAVE_MXDATETIME +#include <mxDateTime.h> +#include "psycopg/adapter_mxdatetime.h" +mxDateTimeModule_APIObject *mxDateTimeP = NULL; +#endif + +/* some module-level variables, like the datetime module */ +#ifdef HAVE_PYDATETIME +#include <datetime.h> +#include "psycopg/adapter_datetime.h" +PyObject *pyDateTimeModuleP = NULL; +PyObject *pyDateTypeP = NULL; +PyObject *pyTimeTypeP = NULL; +PyObject *pyDateTimeTypeP = NULL; +PyObject *pyDeltaTypeP = NULL; +#endif + +PyObject *psycoEncodings = NULL; +PyObject *decimalType = NULL; + +/** connect module-level function **/ +#define psyco_connect_doc "connect(dsn, ...) -> new connection object" + +static int +_psyco_connect_fill_dsn(char *dsn, char *kw, char *v, int i) +{ + strcpy(&dsn[i], kw); i += strlen(kw); + strcpy(&dsn[i], v); i += strlen(v); + return i; +} + +static void +_psyco_connect_fill_exc(connectionObject *conn) +{ + /* fill the connection object with the exceptions */ + conn->exc_Error = Error; + Py_INCREF(Error); + conn->exc_Warning = Warning; + Py_INCREF(Warning); + conn->exc_InterfaceError = Error; + Py_INCREF(InterfaceError); + conn->exc_DatabaseError = Error; + Py_INCREF(DatabaseError); + conn->exc_InternalError = Error; + Py_INCREF(InternalError); + conn->exc_ProgrammingError = Error; + Py_INCREF(ProgrammingError); + conn->exc_IntegrityError = Error; + Py_INCREF(IntegrityError); + conn->exc_DataError = Error; + Py_INCREF(DataError); + conn->exc_NotSupportedError = NotSupportedError; + Py_INCREF(NotSupportedError); +} + +static PyObject * +psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *conn, *factory = NULL; + + int idsn=-1; + char *dsn=NULL, *database=NULL, *user=NULL, *password=NULL; + char *host=NULL, *port=NULL, *sslmode=NULL; + + static char *kwlist[] = {"dsn", "database", "host", "port", + "user", "password", "sslmode", "factory", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sssssssO", kwlist, + &dsn, &database, &host, &port, + &user, &password, &sslmode, &factory)) { + return NULL; + } + + if (dsn == NULL) { + int l = 36; /* len("dbname= user= password= host= port=\0") */ + + if (database) l += strlen(database); + if (host) l += strlen(host); + if (port) l += strlen(port); + if (user) l += strlen(user); + if (password) l += strlen(password); + if (sslmode) l += strlen(sslmode); + + dsn = malloc(l*sizeof(char)); + if (dsn == NULL) { + PyErr_SetString(InterfaceError, "dynamic dsn allocation failed"); + return NULL; + } + + idsn = 0; + if (database) + idsn = _psyco_connect_fill_dsn(dsn, " dbname=", database, idsn); + if (host) + idsn = _psyco_connect_fill_dsn(dsn, " host=", host, idsn); + if (port) + idsn = _psyco_connect_fill_dsn(dsn, " port=", port, idsn); + if (user) + idsn = _psyco_connect_fill_dsn(dsn, " user=", user, idsn); + if (password) + idsn = _psyco_connect_fill_dsn(dsn, " password=", password, idsn); + if (sslmode) + idsn = _psyco_connect_fill_dsn(dsn, " sslmode=", sslmode, idsn); + + if (idsn > 0) { + dsn[idsn] = '\0'; + memmove(dsn, &dsn[1], idsn); + } + else { + free(dsn); + PyErr_SetString(InterfaceError, "missing dsn and no parameters"); + return NULL; + } + } + + Dprintf("psyco_connect: dsn = '%s'", dsn); + + /* allocate connection, fill with errors and return it */ + if (factory == NULL) factory = (PyObject *)&connectionType; + conn = PyObject_CallFunction(factory, "s", dsn); + if (conn) _psyco_connect_fill_exc((connectionObject*)conn); + + return conn; +} + +/** type registration **/ +#define psyco_register_type_doc \ +"register_type(obj) -> register obj with psycopg type system" + +static PyObject * +psyco_register_type(PyObject *self, PyObject *args) +{ + PyObject *type; + + if (!PyArg_ParseTuple(args, "O!", &typecastType, &type)) { + return NULL; + } + + typecast_add(type, 0); + + Py_INCREF(Py_None); + return Py_None; +} + + +/* default adapters */ + +static void +psyco_adapters_init(PyObject *mod) +{ + PyObject *call; + + microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType); + microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType); + microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType); + +#ifdef HAVE_MXDATETIME + /* the module has already been initialized, so we can obtain the callable + objects directly from its dictionary :) */ + call = PyMapping_GetItemString(mod, "TimestampFromMx"); + microprotocols_add(mxDateTimeP->DateTime_Type, NULL, call); + call = PyMapping_GetItemString(mod, "TimeFromMx"); + microprotocols_add(mxDateTimeP->DateTimeDelta_Type, NULL, call); +#endif + +#ifdef HAVE_PYDATETIME + /* as above, we use the callable objects from the psycopg module */ + call = PyMapping_GetItemString(mod, "DateFromPy"); + microprotocols_add((PyTypeObject*)pyDateTypeP, NULL, call); + call = PyMapping_GetItemString(mod, "TimeFromPy"); + microprotocols_add((PyTypeObject*)pyTimeTypeP, NULL, call); + call = PyMapping_GetItemString(mod, "TimestampFromPy"); + microprotocols_add((PyTypeObject*)pyDateTimeTypeP, NULL, call); + call = PyMapping_GetItemString(mod, "IntervalFromPy"); + microprotocols_add((PyTypeObject*)pyDeltaTypeP, NULL, call); +#endif + +#ifdef HAVE_PYBOOL + microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType); +#endif +} + +/* psyco_encodings_fill + + Fill the module's postgresql<->python encoding table */ + +static encodingPair encodings[] = { + {"SQL_ASCII", "ascii"}, + {"LATIN1", "latin_1"}, + {"UNICODE", "utf_8"}, + {NULL, NULL} +}; +static void psyco_encodings_fill(PyObject *dict) +{ + encodingPair *enc; + + for (enc = encodings; enc->pgenc != NULL; enc++) { + PyObject *value = PyString_FromString(enc->pyenc); + PyDict_SetItemString(dict, enc->pgenc, value); + Py_DECREF(value); + } +} + +/* psyco_errors_init, psyco_errors_fill (callable from C) + + Initialize the module's exceptions and after that a dictionary with a full + set of exceptions. */ + +PyObject *Error, *Warning, *InterfaceError, *DatabaseError, + *InternalError, *OperationalError, *ProgrammingError, + *IntegrityError, *DataError, *NotSupportedError; + +static void +psyco_errors_init(void) +{ + Error = PyErr_NewException("psycopg.Error", PyExc_StandardError, NULL); + Warning = PyErr_NewException("psycopg.Warning", PyExc_StandardError,NULL); + InterfaceError = PyErr_NewException("psycopg.InterfaceError", Error, NULL); + DatabaseError = PyErr_NewException("psycopg.DatabaseError", Error, NULL); + + InternalError = + PyErr_NewException("psycopg.InternalError", DatabaseError, NULL); + OperationalError = + PyErr_NewException("psycopg.OperationalError", DatabaseError, NULL); + ProgrammingError = + PyErr_NewException("psycopg.ProgrammingError", DatabaseError, NULL); + IntegrityError = + PyErr_NewException("psycopg.IntegrityError", DatabaseError,NULL); + DataError = + PyErr_NewException("psycopg.DataError", DatabaseError, NULL); + NotSupportedError = + PyErr_NewException("psycopg.NotSupportedError", DatabaseError, NULL); +} + +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); +} + +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); +} + +/* psyco_decimal_init + + Initialize the module's pointer to the decimal type. */ + +void +psyco_decimal_init(void) +{ +#ifdef HAVE_DECIMAL + PyObject *decimal = PyImport_ImportModule("decimal"); + if (decimal) { + decimalType = PyObject_GetAttrString(decimal, "Decimal"); + } +#endif +} + + + +/** method table and module initialization **/ + +static PyMethodDef psycopgMethods[] = { + {"connect", (PyCFunction)psyco_connect, + METH_VARARGS|METH_KEYWORDS, psyco_connect_doc}, + {"adapt", (PyCFunction)psyco_microprotocols_adapt, + METH_VARARGS, psyco_microprotocols_adapt_doc}, + + {"register_type", (PyCFunction)psyco_register_type, + METH_VARARGS, psyco_register_type_doc}, + {"new_type", (PyCFunction)typecast_from_python, + METH_VARARGS|METH_KEYWORDS}, + + {"QuotedString", (PyCFunction)psyco_QuotedString, + METH_VARARGS, psyco_QuotedString_doc}, + {"Boolean", (PyCFunction)psyco_Boolean, + METH_VARARGS, psyco_Boolean_doc}, + {"Binary", (PyCFunction)psyco_Binary, + METH_VARARGS, psyco_Binary_doc}, + {"Date", (PyCFunction)psyco_Date, + METH_VARARGS, psyco_Date_doc}, + {"Time", (PyCFunction)psyco_Time, + METH_VARARGS, psyco_Time_doc}, + {"Timestamp", (PyCFunction)psyco_Timestamp, + METH_VARARGS, psyco_Timestamp_doc}, + {"DateFromTicks", (PyCFunction)psyco_DateFromTicks, + METH_VARARGS, psyco_DateFromTicks_doc}, + {"TimeFromTicks", (PyCFunction)psyco_TimeFromTicks, + METH_VARARGS, psyco_TimeFromTicks_doc}, + {"TimestampFromTicks", (PyCFunction)psyco_TimestampFromTicks, + METH_VARARGS, psyco_TimestampFromTicks_doc}, + +#ifdef HAVE_MXDATETIME + {"DateFromMx", (PyCFunction)psyco_DateFromMx, + METH_VARARGS, psyco_DateFromMx_doc}, + {"TimeFromMx", (PyCFunction)psyco_TimeFromMx, + METH_VARARGS, psyco_TimeFromMx_doc}, + {"TimestampFromMx", (PyCFunction)psyco_TimestampFromMx, + METH_VARARGS, psyco_TimestampFromMx_doc}, + {"IntervalFromMx", (PyCFunction)psyco_IntervalFromMx, + METH_VARARGS, psyco_IntervalFromMx_doc}, +#endif + +#ifdef HAVE_PYDATETIME + {"DateFromPy", (PyCFunction)psyco_DateFromPy, + METH_VARARGS, psyco_DateFromPy_doc}, + {"TimeFromPy", (PyCFunction)psyco_TimeFromPy, + METH_VARARGS, psyco_TimeFromPy_doc}, + {"TimestampFromPy", (PyCFunction)psyco_TimestampFromPy, + METH_VARARGS, psyco_TimestampFromPy_doc}, + {"IntervalFromPy", (PyCFunction)psyco_IntervalFromPy, + METH_VARARGS, psyco_IntervalFromPy_doc}, +#endif + + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +PyMODINIT_FUNC +init_psycopg(void) +{ + static void *PSYCOPG_API[PSYCOPG_API_pointers]; + + PyObject *module, *dict; + PyObject *c_api_object; + + Dprintf("initpsycopg: initializing psycopg %s", PSYCOPG_VERSION); + + /* initialize all the new types and then the module */ + connectionType.ob_type = &PyType_Type; + cursorType.ob_type = &PyType_Type; + typecastType.ob_type = &PyType_Type; + qstringType.ob_type = &PyType_Type; + binaryType.ob_type = &PyType_Type; + isqlquoteType.ob_type = &PyType_Type; + + if (PyType_Ready(&connectionType) == -1) return; + if (PyType_Ready(&cursorType) == -1) return; + if (PyType_Ready(&typecastType) == -1) return; + if (PyType_Ready(&qstringType) == -1) return; + if (PyType_Ready(&binaryType) == -1) return; + if (PyType_Ready(&isqlquoteType) == -1) return; + +#ifdef HAVE_PYBOOL + pbooleanType.ob_type = &PyType_Type; + if (PyType_Ready(&pbooleanType) == -1) return; +#endif + + /* import mx.DateTime module, if necessary */ +#ifdef HAVE_MXDATETIME + mxdatetimeType.ob_type = &PyType_Type; + if (PyType_Ready(&mxdatetimeType) == -1) return; + if (mxDateTime_ImportModuleAndAPI() != 0) { + Dprintf("initpsycopg: why marc hide mx.DateTime again?!"); + PyErr_SetString(PyExc_ImportError, "can't import mx.DateTime module"); + return; + } + mxDateTimeP = &mxDateTime; +#endif + + /* import python builtin datetime module, if available */ +#ifdef HAVE_PYDATETIME + pyDateTimeModuleP = PyImport_ImportModule("datetime"); + + pydatetimeType.ob_type = &PyType_Type; + if (PyType_Ready(&pydatetimeType) == -1) return; + + /* now we define the datetime types, this is crazy because python should + be doing that, not us! */ + pyDateTypeP = PyObject_GetAttrString(pyDateTimeModuleP, "date"); + pyTimeTypeP = PyObject_GetAttrString(pyDateTimeModuleP, "time"); + pyDateTimeTypeP = PyObject_GetAttrString(pyDateTimeModuleP, "datetime"); + pyDeltaTypeP = PyObject_GetAttrString(pyDateTimeModuleP, "timedelta"); +#endif + + /* initialize the module and grab module's dictionary */ + module = Py_InitModule("_psycopg", psycopgMethods); + dict = PyModule_GetDict(module); + + /* initialize all the module's exported functions */ + /* PyBoxer_API[PyBoxer_Fake_NUM] = (void *)PyBoxer_Fake; */ + + /* Create a CObject containing the API pointer array's address */ + c_api_object = PyCObject_FromVoidPtr((void *)PSYCOPG_API, NULL); + if (c_api_object != NULL) + PyModule_AddObject(module, "_C_API", c_api_object); + + /* other mixed initializations of module-level variables */ + psycoEncodings = PyDict_New(); + psyco_encodings_fill(psycoEncodings); + psyco_decimal_init(); + + /* set some module's parameters */ + PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION); + PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver"); + PyModule_AddObject(module, "apilevel", PyString_FromString(APILEVEL)); + PyModule_AddObject(module, "threadsafety", PyInt_FromLong(THREADSAFETY)); + PyModule_AddObject(module, "paramstyle", PyString_FromString(PARAMSTYLE)); + + /* put new types in module dictionary */ + PyModule_AddObject(module, "connection", (PyObject*)&connectionType); + PyModule_AddObject(module, "cursor", (PyObject*)&cursorType); + PyModule_AddObject(module, "ISQLQuote", (PyObject*)&isqlquoteType); + + /* encodings dictionary in module dictionary */ + PyModule_AddObject(module, "encodings", psycoEncodings); + + /* initialize default set of typecasters */ + typecast_init(dict); + + /* initialize microprotocols layer */ + microprotocols_init(dict); + psyco_adapters_init(dict); + + /* create a standard set of exceptions and add them to the module's dict */ + psyco_errors_init(); + psyco_errors_fill(dict); + + Dprintf("initpsycopg: module initialization complete"); +} diff --git a/psycopg/python.h b/psycopg/python.h new file mode 100644 index 0000000..1c2b96d --- /dev/null +++ b/psycopg/python.h @@ -0,0 +1,43 @@ +/* python.h - python version compatibility stuff + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_PYTHON_H +#define PSYCOPG_PYTHON_H 1 + +#include <Python.h> +#include <structmember.h> + +/* python < 2.2 does not have PyMemeberDef */ +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 2 +#define PyMemberDef memberlist +#endif + +/* PyObject_TypeCheck introduced in 2.2 */ +#ifndef PyObject_TypeCheck +#define PyObject_TypeCheck(o, t) ((o)->ob_type == (t)) +#endif + +/* python 2.2 does not have freefunc (it has destructor instead) */ +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 3 +#define freefunc destructor +#endif + +#endif /* !defined(PSYCOPG_PYTHON_H) */ diff --git a/psycopg/typecast.c b/psycopg/typecast.c new file mode 100644 index 0000000..ee91011 --- /dev/null +++ b/psycopg/typecast.c @@ -0,0 +1,439 @@ +/* typecast.c - basic utility functions related to typecasting + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <Python.h> +#include <structmember.h> + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/psycopg.h" +#include "psycopg/python.h" +#include "psycopg/typecast.h" +#include "psycopg/cursor.h" + +/* usefull function used by some typecasters */ + +static char * +skip_until_space(char *s) +{ + while (*s && *s != ' ') s++; + return s; +} + + +/** include casting objects **/ +#include "psycopg/typecast_basic.c" + +#ifdef HAVE_MXDATETIME +#include "psycopg/typecast_mxdatetime.c" +#endif + +#ifdef HAVE_PYDATETIME +#include "psycopg/typecast_datetime.c" +#endif + +#include "psycopg/typecast_builtins.c" + +/* a list of initializers, used to make the typecasters accessible anyway */ +#ifdef HAVE_PYDATETIME +typecastObject_initlist typecast_pydatetime[] = { + {"PYDATETIME", typecast_DATETIME_types, typecast_PYDATETIME_cast}, + {"PYTIME", typecast_TIME_types, typecast_PYTIME_cast}, + {"PYDATE", typecast_DATE_types, typecast_PYDATE_cast}, + {"PYINTERVAL", typecast_INTERVAL_types, typecast_PYINTERVAL_cast}, + {NULL, NULL, NULL} +}; +#endif + +/* a list of initializers, used to make the typecasters accessible anyway */ +#ifdef HAVE_MXDATETIME +typecastObject_initlist typecast_mxdatetime[] = { + {"MXDATETIME", typecast_DATETIME_types, typecast_MXDATE_cast}, + {"MXTIME", typecast_TIME_types, typecast_MXTIME_cast}, + {"MXDATE", typecast_DATE_types, typecast_MXDATE_cast}, + {"MXINTERVAL", typecast_INTERVAL_types, typecast_MXINTERVAL_cast}, + {NULL, NULL, NULL} +}; +#endif + + +/** the type dictionary and associated functions **/ + +PyObject *psyco_types; +PyObject *psyco_default_cast; +PyObject *psyco_binary_types; +PyObject *psyco_default_binary_cast; + +static long int typecast_default_DEFAULT[] = {0}; +static typecastObject_initlist typecast_default = { + "DEFAULT", typecast_default_DEFAULT, typecast_STRING_cast}; + + +/* typecast_init - initialize the dictionary and create default types */ + +int +typecast_init(PyObject *dict) +{ + int i; + + /* 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; + } + + PyDict_SetItemString(dict, "string_types", psyco_types); + 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])); + if (t == NULL) return -1; + if (typecast_add((PyObject *)t, 0) != 0) return -1; + + PyDict_SetItem(dict, t->name, (PyObject *)t); + + /* export binary object */ + if (typecast_builtins[i].values == typecast_BINARY_types) { + psyco_default_binary_cast = (PyObject *)t; + } + } + + /* create and save a default cast object (but does not register it) */ + psyco_default_cast = typecast_from_c(&typecast_default); + + /* register the date/time typecasters with their original names */ +#ifdef HAVE_MXDATETIME + 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])); + if (t == NULL) return -1; + PyDict_SetItem(dict, t->name, (PyObject *)t); + } +#endif +#ifdef HAVE_PYDATETIME + 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])); + if (t == NULL) return -1; + PyDict_SetItem(dict, t->name, (PyObject *)t); + } +#endif + + return 0; +} + + +/* typecast_add - add a type object to the dictionary */ +int +typecast_add(PyObject *obj, int binary) +{ + PyObject *val; + int len, i; + + typecastObject *type = (typecastObject *)obj; + + Dprintf("typecast_add: object at %p, values refcnt = %d", + obj, type->values->ob_refcnt); + + len = PyTuple_Size(type->values); + for (i = 0; i < len; i++) { + val = PyTuple_GetItem(type->values, i); + Dprintf("typecast_add: adding val: %ld", PyInt_AsLong(val)); + if (binary) { + PyDict_SetItem(psyco_binary_types, val, obj); + } + else { + PyDict_SetItem(psyco_types, val, obj); + } + } + + return 0; +} + + +/** typecast type **/ + +#define OFFSETOF(x) offsetof(typecastObject, x) + +static struct memberlist typecastObject_memberlist[] = { + {"name", T_OBJECT, OFFSETOF(name), RO}, + {"values", T_OBJECT, OFFSETOF(values), RO}, + {NULL} +}; + +/* numeric methods */ + +static PyObject * +typecast_new(PyObject *name, PyObject *values, PyObject *cast); + +static int +typecast_coerce(PyObject **pv, PyObject **pw) +{ + if (PyObject_TypeCheck(*pv, &typecastType)) { + if (PyInt_Check(*pw)) { + PyObject *coer, *args; + args = PyTuple_New(1); + Py_INCREF(*pw); + PyTuple_SET_ITEM(args, 0, *pw); + coer = typecast_new(NULL, args, NULL); + *pw = coer; + Py_DECREF(args); + Py_INCREF(*pv); + return 0; + } + else if (PyObject_TypeCheck(*pw, &typecastType)){ + Py_INCREF(*pv); + Py_INCREF(*pw); + return 0; + } + } + PyErr_SetString(PyExc_TypeError, "psycopg type coercion failed"); + return -1; +} + +static PyNumberMethods typecastObject_as_number = { + 0, /*nb_add*/ + 0, /*nb_subtract*/ + 0, /*nb_multiply*/ + 0, /*nb_divide*/ + 0, /*nb_remainder*/ + 0, /*nb_divmod*/ + 0, /*nb_power*/ + 0, /*nb_negative*/ + 0, /*nb_positive*/ + 0, /*nb_absolute*/ + 0, /*nb_nonzero*/ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + 0, /*nb_and*/ + 0, /*nb_xor*/ + 0, /*nb_or*/ + typecast_coerce, /*nb_coerce*/ + 0, /*nb_int*/ + 0, /*nb_long*/ + 0, /*nb_float*/ + 0, /*nb_oct*/ + 0, /*nb_hex*/ +}; + + +/* object methods */ + +static int +typecast_cmp(typecastObject *self, typecastObject *v) +{ + int res; + + if (PyObject_Length(v->values) > 1 && PyObject_Length(self->values) == 1) { + /* calls itself exchanging the args */ + return typecast_cmp(v, self); + } + res = PySequence_Contains(self->values, PyTuple_GET_ITEM(v->values, 0)); + + if (res < 0) return res; + else return res == 1 ? 0 : 1; +} + +static struct PyMethodDef typecastObject_methods[] = { + {"__cmp__", (PyCFunction)typecast_cmp, METH_VARARGS, NULL}, + {NULL, NULL} +}; + +/** FIXME: typecast should become a new-style type sometime in the future, but + right now this is not important and we keep going with old class */ + +static PyObject * +typecast_getattr(typecastObject *self, char *name) +{ + PyObject *rv; + + rv = PyMember_Get((char *)self, typecastObject_memberlist, name); + if (rv) return rv; + PyErr_Clear(); + return Py_FindMethod(typecastObject_methods, (PyObject *)self, name); +} + +static void +typecast_destroy(typecastObject *self) +{ + PyObject *name, *cast, *values; + + values = self->values; + name = self->name; + cast = self->pcast; + + PyObject_Del(self); + + Py_XDECREF(name); + Py_XDECREF(values); + Py_XDECREF(cast); + + Dprintf("typecast_destroy: object at %p destroyed", self); +} + +static PyObject * +typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + PyObject *string, *cursor, *res; + + typecastObject *self = (typecastObject *)obj; + + if (!PyArg_ParseTuple(args, "OO", &string, &cursor)) { + return NULL; + } + + if (self->ccast) { + Dprintf("typecast_call: calling C cast function"); + res = self->ccast(string, cursor); + } + else if (self->pcast) { + Dprintf("typecast_call: calling python callable"); + Py_INCREF(string); + res = PyObject_CallFunction(self->pcast, "OO", string, cursor); + } + else { + Py_INCREF(Py_None); + res = Py_None; + } + + Dprintf("typecast_call: string argument has refcnt = %d", + string->ob_refcnt); + return res; +} + + +PyTypeObject typecastType = { + PyObject_HEAD_INIT(NULL) + + 0, /*ob_size*/ + "psycopg.type", /*tp_name*/ + sizeof(typecastObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + + /* methods */ + (destructor)typecast_destroy, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)typecast_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + (cmpfunc)typecast_cmp, /*tp_compare*/ + 0, /*tp_repr*/ + &typecastObject_as_number, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + typecast_call, /*tp_call*/ + 0, /*tp_str*/ + + /* Space for future expansion */ + 0L,0L,0L,0L, + "psycopg type-casting object" /* Documentation string */ +}; + +static PyObject * +typecast_new(PyObject *name, PyObject *values, PyObject *cast) +{ + typecastObject *obj; + + obj = PyObject_NEW(typecastObject, &typecastType); + if (obj == NULL) return NULL; + + Py_INCREF(values); + obj->values = values; + + if (name) { + Py_INCREF(name); + obj->name = name; + } + else { + Py_INCREF(Py_None); + obj->name = Py_None; + } + + obj->pcast = NULL; + obj->ccast = NULL; + + if (cast && cast != Py_None) { + Py_INCREF(cast); + obj->pcast = cast; + } + + Dprintf("typecast_new: typecast object created at %p", obj); + + return (PyObject *)obj; +} + +PyObject * +typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *v, *name, *cast = NULL; + + static char *kwlist[] = {"values", "name", "castobj", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!O", kwlist, + &PyTuple_Type, &v, + &PyString_Type, &name, + &cast)) { + return NULL; + } + + return typecast_new(name, v, cast); +} + +PyObject * +typecast_from_c(typecastObject_initlist *type) +{ + PyObject *tuple; + typecastObject *obj; + int i, len = 0; + + while (type->values[len] != 0) len++; + + tuple = PyTuple_New(len); + if (!tuple) return NULL; + + for (i = 0; i < len ; i++) { + PyTuple_SET_ITEM(tuple, i, PyInt_FromLong(type->values[i])); + } + + obj = (typecastObject *) + typecast_new(PyString_FromString(type->name), tuple, NULL); + + if (obj) { + obj->ccast = type->cast; + obj->pcast = NULL; + } + return (PyObject *)obj; +} + + diff --git a/psycopg/typecast.h b/psycopg/typecast.h new file mode 100644 index 0000000..de31337 --- /dev/null +++ b/psycopg/typecast.h @@ -0,0 +1,77 @@ +/* typecast.h - definitions for typecasters + * + * Copyright (C) 2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_TYPECAST_H +#define PSYCOPG_TYPECAST_H 1 + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* type of type-casting functions (both C and Python) */ +typedef PyObject *(*typecast_function)(PyObject *, PyObject *); + +/** typecast type **/ + +extern PyTypeObject typecastType; + +typedef struct { + PyObject_HEAD + + PyObject *name; /* the name of this type */ + PyObject *values; /* the different types this instance can match */ + + typecast_function ccast; /* the C casting function */ + PyObject *pcast; /* the python casting function */ +} typecastObject; + +/* the initialization values are stored here */ + +typedef struct { + char *name; + long int *values; + typecast_function cast; +} typecastObject_initlist; + +/* the type dictionary, much faster to access it globally */ +extern PyObject *psyco_types; +extern PyObject *psyco_binary_types; + +/* the default casting objects, used when no other objects are available */ +extern PyObject *psyco_default_cast; +extern PyObject *psyco_default_binary_cast; + +/** exported functions **/ + +/* used by module.c to init the type system and register types */ +extern int typecast_init(PyObject *dict); +extern int typecast_add(PyObject *obj, int binary); + +/* the C callable typecastObject creator function */ +extern PyObject *typecast_from_c(typecastObject_initlist *type); + +/* the python callable typecast creator function */ +extern PyObject *typecast_from_python( + PyObject *self, PyObject *args, PyObject *keywds); + +#endif /* !defined(PSYCOPG_TYPECAST_H) */ diff --git a/psycopg/typecast_basic.c b/psycopg/typecast_basic.c new file mode 100644 index 0000000..e698a49 --- /dev/null +++ b/psycopg/typecast_basic.c @@ -0,0 +1,192 @@ +/* pgcasts_basic.c - basic typecasting functions to python types + * + * Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of the psycopg module. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <libpq-fe.h> + +/** INTEGER - cast normal integers (4 bytes) to python int **/ + +static PyObject * +typecast_INTEGER_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyNumber_Int(s); +} + +/** LONGINTEGER - cast long integers (8 bytes) to python long **/ + +static PyObject * +typecast_LONGINTEGER_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyNumber_Long(s); +} + +/** FLOAT - cast floating point numbers to python float **/ + +static PyObject * +typecast_FLOAT_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyNumber_Float(s); +} + +/** STRING - cast strings of any type to python string **/ + +static PyObject * +typecast_STRING_cast(PyObject *s, PyObject *curs) +{ + Py_INCREF(s); + return s; +} + +/** UNICODE - cast strings of any type to a python unicode object **/ + +static PyObject * +typecast_UNICODE_cast(PyObject *s, PyObject *curs) +{ + PyObject *enc; + + if (s == Py_None) {Py_INCREF(s); return s;} + + enc = PyDict_GetItemString(psycoEncodings, + ((cursorObject*)curs)->conn->encoding); + if (enc) { + return PyUnicode_Decode(PyString_AsString(s), + PyString_Size(s), + PyString_AsString(enc), + NULL); + } + else { + PyErr_Format(InterfaceError, + "can't decode into unicode string from %s", + ((cursorObject*)curs)->conn->encoding); + return NULL; + } +} + +/** BINARY - cast a binary string into a python string **/ + +/* the function typecast_BINARY_cast_unescape is used when libpq does not + provide PQunescapeBytea: it convert all the \xxx octal sequences to the + proper byte value */ + +#ifdef PSYCOPG_OWN_QUOTING +static unsigned char * +typecast_BINARY_cast_unescape(unsigned char *str, size_t *to_length) +{ + char *dstptr, *dststr; + int len, i; + + len = strlen(str); + dststr = (char*)calloc(len, sizeof(char)); + dstptr = dststr; + + if (dststr == NULL) return NULL; + + Py_BEGIN_ALLOW_THREADS; + + for (i = 0; i < len; i++) { + if (str[i] == '\\') { + if ( ++i < len) { + if (str[i] == '\\') { + *dstptr = '\\'; + } + else { + *dstptr = 0; + *dstptr |= (str[i++] & 7) << 6; + *dstptr |= (str[i++] & 7) << 3; + *dstptr |= (str[i] & 7); + } + } + } + else { + *dstptr = str[i]; + } + dstptr++; + } + + Py_END_ALLOW_THREADS; + + *to_length = (size_t)(dstptr-dststr); + + return dststr; +} + +#define PQunescapeBytea typecast_BINARY_cast_unescape +#endif + +static PyObject * +typecast_BINARY_cast(PyObject *s, PyObject *curs) +{ + PyObject *res; + unsigned char *str; + size_t len; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PQunescapeBytea(PyString_AS_STRING(s), &len); + Dprintf("typecast_BINARY_cast: unescaped %d bytes", len); + + /* TODO: using a PyBuffer would make this a zero-copy operation but we'll + need to define our own buffer-derived object to keep a reference to the + memory area: does it buy it? + + res = PyBuffer_FromMemory((void*)str, len); */ + res = PyString_FromStringAndSize(str, len); + free(str); + + return res; +} + +/** BOOLEAN - cast boolean value into right python object **/ + +static PyObject * +typecast_BOOLEAN_cast(PyObject *s, PyObject *curs) +{ + PyObject *res; + + if (s == Py_None) {Py_INCREF(s); return s;} + + if (PyString_AS_STRING(s)[0] == 't') + res = Py_True; + else + res = Py_False; + + Py_INCREF(res); + return res; +} + +/** DECIMAL - cast any kind of number into a Python Decimal object **/ + +#ifdef HAVE_DECIMAL +static PyObject * +typecast_DECIMAL_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyObject_CallFunction(decimalType, "O", s); +} +#else +#define typecast_DECIMAL_cast typecast_FLOAT_cast +#endif + +/* some needed aliases */ +#define typecast_NUMBER_cast typecast_FLOAT_cast +#define typecast_ROWID_cast typecast_INTEGER_cast diff --git a/psycopg/typecast_basic.c.old b/psycopg/typecast_basic.c.old new file mode 100644 index 0000000..c9d36e1 --- /dev/null +++ b/psycopg/typecast_basic.c.old @@ -0,0 +1,147 @@ +/* pgcasts_basic.c - basic typecasting functions to python types + * + * Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of the psycopg module. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <libpq-fe.h> + +/** INTEGER - cast normal integers (4 bytes) to python int **/ + +static PyObject * +typecast_INTEGER_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyNumber_Int(s); +} + +/** LONGINTEGER - cast long integers (8 bytes) to python long **/ + +static PyObject * +typecast_LONGINTEGER_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyNumber_Long(s); +} + +/** FLOAT - cast floating point numbers to python float **/ + +static PyObject * +typecast_FLOAT_cast(PyObject *s, PyObject *curs) +{ + if (s == Py_None) {Py_INCREF(s); return s;} + return PyNumber_Float(s); +} + +/** STRING - cast strings of any type to python string **/ + +static PyObject * +typecast_STRING_cast(PyObject *s, PyObject *curs) +{ + Py_INCREF(s); + return s; +} + +/** BINARY - cast a binary string into a python string **/ + +/* the function typecast_BINARY_cast_unescape is used when libpq does not + provide PQunescapeBytea: it convert all the \xxx octal sequences to the + proper byte value */ + +#ifdef PSYCOPG_OWN_QUOTING +static unsigned char * +typecast_BINARY_cast_unescape(unsigned char *str, size_t *to_length) +{ + char *dstptr, *dststr; + int len, i; + + len = strlen(str); + dststr = (char*)calloc(len, sizeof(char)); + dstptr = dststr; + + if (dststr == NULL) return NULL; + + Py_BEGIN_ALLOW_THREADS; + + for (i = 0; i < len; i++) { + if (str[i] == '\\') { + if ( ++i < len) { + if (str[i] == '\\') { + *dstptr = '\\'; + } + else { + *dstptr = 0; + *dstptr |= (str[i++] & 7) << 6; + *dstptr |= (str[i++] & 7) << 3; + *dstptr |= (str[i] & 7); + } + } + } + else { + *dstptr = str[i]; + } + dstptr++; + } + + Py_END_ALLOW_THREADS; + + *to_length = (size_t)(dstptr-dststr); + + return dststr; +} + +#define PQunescapeBytea typecast_BINARY_cast_unescape +#endif + +static PyObject * +typecast_BINARY_cast(PyObject *s, PyObject *curs) +{ + PyObject *res; + unsigned char *str; + size_t len; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PQunescapeBytea(PyString_AS_STRING(s), &len); + res = PyBuffer_FromMemory((void*)str, len); + free(str); + + return res; +} + +/** BOOLEAN - cast boolean value into right python object **/ + +static PyObject * +typecast_BOOLEAN_cast(PyObject *s, PyObject *curs) +{ + PyObject *res; + + if (s == Py_None) {Py_INCREF(s); return s;} + + if (PyString_AS_STRING(s)[0] == 't') + res = Py_True; + else + res = Py_False; + + Py_INCREF(res); + return res; +} + +/* some needed aliases */ +#define typecast_NUMBER_cast typecast_FLOAT_cast +#define typecast_ROWID_cast typecast_INTEGER_cast diff --git a/psycopg/typecast_builtins.c b/psycopg/typecast_builtins.c new file mode 100644 index 0000000..c554a37 --- /dev/null +++ b/psycopg/typecast_builtins.c @@ -0,0 +1,34 @@ +static long int typecast_NUMBER_types[] = {20, 23, 21, 701, 700, 1700, 0}; +static long int typecast_LONGINTEGER_types[] = {20, 0}; +static long int typecast_INTEGER_types[] = {23, 21, 0}; +static long int typecast_FLOAT_types[] = {701, 700, 0}; +static long int typecast_DECIMAL_types[] = {1700, 0}; +static long int typecast_UNICODE_types[] = {19, 18, 25, 1042, 1043, 0}; +static long int typecast_STRING_types[] = {19, 18, 25, 1042, 1043, 0}; +static long int typecast_BOOLEAN_types[] = {16, 0}; +static long int typecast_DATETIME_types[] = {1114, 1184, 704, 1186, 0}; +static long int typecast_TIME_types[] = {1083, 1266, 0}; +static long int typecast_DATE_types[] = {1082, 0}; +static long int typecast_INTERVAL_types[] = {704, 1186, 0}; +static long int typecast_BINARY_types[] = {17, 0}; +static long int typecast_ROWID_types[] = {26, 0}; + + +typecastObject_initlist typecast_builtins[] = { + {"NUMBER", typecast_NUMBER_types, typecast_NUMBER_cast}, + {"LONGINTEGER", typecast_LONGINTEGER_types, typecast_LONGINTEGER_cast}, + {"INTEGER", typecast_INTEGER_types, typecast_INTEGER_cast}, + {"FLOAT", typecast_FLOAT_types, typecast_FLOAT_cast}, + {"DECIMAL", typecast_DECIMAL_types, typecast_DECIMAL_cast}, + {"UNICODE", typecast_UNICODE_types, typecast_UNICODE_cast}, + {"STRING", typecast_STRING_types, typecast_STRING_cast}, + {"BOOLEAN", typecast_BOOLEAN_types, typecast_BOOLEAN_cast}, + {"DATETIME", typecast_DATETIME_types, typecast_DATETIME_cast}, + {"TIME", typecast_TIME_types, typecast_TIME_cast}, + {"DATE", typecast_DATE_types, typecast_DATE_cast}, + {"INTERVAL", typecast_INTERVAL_types, typecast_INTERVAL_cast}, + {"BINARY", typecast_BINARY_types, typecast_BINARY_cast}, + {"ROWID", typecast_ROWID_types, typecast_ROWID_cast}, + {NULL, NULL, NULL} +}; + diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c new file mode 100644 index 0000000..0f29bfd --- /dev/null +++ b/psycopg/typecast_datetime.c @@ -0,0 +1,287 @@ +/* typecast_datetime.c - date and time typecasting functions to python types + * + * Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of the psycopg module. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <math.h> +#include "datetime.h" + + +/* the pointer to the datetime module API is initialized by the module init + code, we just need to grab it */ +extern PyObject* pyDateTimeModuleP; +extern PyObject *pyDateTypeP; +extern PyObject *pyTimeTypeP; +extern PyObject *pyDateTimeTypeP; +extern PyObject *pyDeltaTypeP; + +/** DATE - cast a date into a date python object **/ + +static PyObject * +typecast_PYDATE_cast(PyObject *s, PyObject *curs) +{ + PyObject* obj = NULL; + int n, y=0, m=0, d=0; + char *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + + /* check for infinity */ + if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { + if (str[0] == '-') { + obj = PyObject_GetAttrString(pyDateTypeP, "min"); + } + else { + obj = PyObject_GetAttrString(pyDateTypeP, "max"); + } + } + + else { + n = sscanf(str, "%d-%d-%d", &y, &m, &d); + + if (n != 3) { + PyErr_SetString(DataError, "unable to parse date"); + } + else { + obj = PyObject_CallFunction(pyDateTypeP, "iii", y, m, d); + } + } + return obj; +} + +/** DATETIME - cast a timestamp into a datetime python object **/ + +static PyObject * +typecast_PYDATETIME_cast(PyObject *s, PyObject *curs) +{ + PyObject* obj = NULL; + int n, y=0, m=0, d=0; + int hh=0, mm=0; + int tzh=0, tzm=0; + double ss=0.0; + char tzs=0, *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + + /* check for infinity */ + if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { + if (str[0] == '-') { + obj = PyObject_GetAttrString(pyDateTimeModuleP, "min"); + } + else { + obj = PyObject_GetAttrString(pyDateTimeModuleP, "max"); + } + } + + else { + Dprintf("typecast_PYDATETIME_cast: s = %s", str); + n = sscanf(str, "%d-%d-%d %d:%d:%lf%c%d:%d", + &y, &m, &d, &hh, &mm, &ss, &tzs, &tzh, &tzm); + Dprintf("typecast_PYDATETIME_cast: date parsed, %d components", n); + + if (n != 3 && n != 6 && n <= 7) { + PyErr_SetString(DataError, "unable to parse date"); + } + else { + double micro = (ss - floor(ss)) * 1000000.0; + int sec = (int)floor(ss); + if (sec > 59) { + mm += 1; + sec -= 60; + } + if (tzs && ((cursorObject*)curs)->tzinfo_factory != Py_None) { + /* we have a time zone, calculate minutes and create + appropriate tzinfo object calling the factory */ + PyObject *tzinfo; + tzm += tzh*60; + if (tzs == '-') tzm = -tzm; + Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tzm); + tzinfo = PyObject_CallFunction( + ((cursorObject*)curs)->tzinfo_factory, "i", tzm); + obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO", + y, m, d, hh, mm, sec, (int)round(micro), tzinfo); + Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = %d", + tzinfo, tzinfo->ob_refcnt); + Py_XDECREF(tzinfo); + } + else { + obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii", + y, m, d, hh, mm, sec, (int)round(micro)); + } + } + } + return obj; +} + +/** TIME - parse time into a time object **/ + +static PyObject * +typecast_PYTIME_cast(PyObject *s, PyObject *curs) +{ + PyObject* obj = NULL; + int n, hh=0, mm=0; + double ss=0.0; + char *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + + n = sscanf(str, "%d:%d:%lf", &hh, &mm, &ss); + + if (n != 3) { + PyErr_SetString(DataError, "unable to parse time"); + } + else { + double micro = (ss - floor(ss)) * 1000000.0; + int sec = (int)floor(ss); + if (sec > 59) { + mm += 1; + sec -= 60; + } + obj = PyObject_CallFunction(pyTimeTypeP, "iiii", + hh, mm, sec, (int)round(micro)); + } + return obj; +} + +/** INTERVAL - parse an interval into a timedelta object **/ + +static PyObject * +typecast_PYINTERVAL_cast(PyObject *s, PyObject *curs) +{ + long years = 0, months = 0, days = 0, denominator = 1; + double hours = 0.0, minutes = 0.0, seconds = 0.0, hundredths = 0.0; + double v = 0.0, sign = 1.0; + int part = 0, sec; + + double micro; + char *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + + Dprintf("typecast_PYINTERVAL_cast: s = %s", str); + + while (*str) { + switch (*str) { + + case '-': + sign = -1.0; + break; + + 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'; + if (part == 6){ + denominator *= 10; + } + break; + + case 'y': + if (part == 0) { + years = (long)(v*sign); + str = skip_until_space(str); + v = 0.0; sign = 1.0; part = 1; + } + break; + + case 'm': + if (part <= 1) { + months = (long)(v*sign); + str = skip_until_space(str); + v = 0.0; sign = 1.0; part = 2; + } + break; + + case 'd': + if (part <= 2) { + days = (long)(v*sign); + str = skip_until_space(str); + v = 0.0; sign = 1.0; part = 3; + } + break; + + case ':': + if (part <= 3) { + hours = v; + v = 0.0; part = 4; + } + else if (part == 4) { + minutes = v; + v = 0.0; part = 5; + } + break; + + case '.': + if (part == 5) { + seconds = v; + v = 0.0; part = 6; + } + break; + + default: + break; + } + + str++; + } + + /* manage last value, be it minutes or seconds or hundredths of a second */ + if (part == 4) { + minutes = v; + } + else if (part == 5) { + seconds = v; + } + else if (part == 6) { + hundredths = v; + hundredths = hundredths/denominator; + } + + /* calculates seconds */ + if (sign < 0.0) { + seconds = - (hundredths + seconds + minutes*60 + hours*3600); + } + else { + seconds += hundredths + minutes*60 + hours*3600; + } + + /* calculates days */ + days += years*365 + months*30; + + micro = (seconds - floor(seconds)) * 1000000.0; + sec = (int)floor(seconds); + return PyObject_CallFunction(pyDeltaTypeP, "iii", + days, sec, (int)round(micro)); +} + +/* psycopg defaults to using python datetime types */ + +#ifdef PSYCOPG_DEFAULT_PYDATETIME +#define typecast_DATE_cast typecast_PYDATE_cast +#define typecast_TIME_cast typecast_PYTIME_cast +#define typecast_INTERVAL_cast typecast_PYINTERVAL_cast +#define typecast_DATETIME_cast typecast_PYDATETIME_cast +#endif diff --git a/psycopg/typecast_mxdatetime.c b/psycopg/typecast_mxdatetime.c new file mode 100644 index 0000000..5f853a1 --- /dev/null +++ b/psycopg/typecast_mxdatetime.c @@ -0,0 +1,222 @@ +/* typecast_mxdatetime.c - date and time typecasting functions to mx types + * + * Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org> + * + * This file is part of the psycopg module. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "mxDateTime.h" + +/* the pointer to the mxDateTime API is initialized by the module init code, + we just need to grab it */ +extern mxDateTimeModule_APIObject *mxDateTimeP; + +/** DATE - cast a date into mx.DateTime python object **/ + +static PyObject * +typecast_MXDATE_cast(PyObject *s, PyObject *curs) +{ + int n, y=0, m=0, d=0; + int hh=0, mm=0; + double ss=0.0; + char *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + + /* check for infinity */ + if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { + if (str[0] == '-') { + return mxDateTimeP->DateTime_FromDateAndTime(-999998,1,1, 0,0,0); + } + else { + return mxDateTimeP->DateTime_FromDateAndTime(999999,12,31, 0,0,0); + } + } + + Dprintf("typecast_MXDATE_cast: s = %s", str); + n = sscanf(str, "%d-%d-%d %d:%d:%lf", &y, &m, &d, &hh, &mm, &ss); + Dprintf("typecast_MXDATE_cast: date parsed, %d components", n); + + if (n != 3 && n != 6) { + PyErr_SetString(DataError, "unable to parse date"); + return NULL; + } + return mxDateTimeP->DateTime_FromDateAndTime(y, m, d, hh, mm, ss); +} + +/** TIME - parse time into an mx.DateTime object **/ + +static PyObject * +typecast_MXTIME_cast(PyObject *s, PyObject *curs) +{ + int n, hh=0, mm=0; + double ss=0.0; + char *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + + Dprintf("typecast_MXTIME_cast: s = %s", str); + + n = sscanf(str, "%d:%d:%lf", &hh, &mm, &ss); + Dprintf("typecast_MXTIME_cast: time parsed, %d components", n); + Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %f", hh, mm, ss); + + if (n != 3) { + PyErr_SetString(DataError, "unable to parse time"); + return NULL; + } + + return mxDateTimeP->DateTimeDelta_FromTime(hh, mm ,ss); +} + +/** INTERVAL - parse an interval into an mx.DateTimeDelta **/ + +static PyObject * +typecast_MXINTERVAL_cast(PyObject *s, PyObject *curs) +{ + long years = 0, months = 0, days = 0, denominator = 1; + double hours = 0.0, minutes = 0.0, seconds = 0.0, hundredths = 0.0; + double v = 0.0, sign = 1.0; + int part = 0; + char *str; + + if (s == Py_None) {Py_INCREF(s); return s;} + + str = PyString_AsString(s); + Dprintf("typecast_MXINTERVAL_cast: s = %s", str); + + while (*str) { + switch (*str) { + + case '-': + sign = -1.0; + break; + + 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'; + Dprintf("typecast_MXINTERVAL_cast: v = %f", v); + if (part == 6){ + denominator *= 10; + Dprintf("typecast_MXINTERVAL_cast: denominator = %ld", + denominator); + } + break; + + case 'y': + if (part == 0) { + years = (long)(v*sign); + str = skip_until_space(str); + Dprintf("typecast_MXINTERVAL_cast: years = %ld, rest = %s", + years, str); + v = 0.0; sign = 1.0; part = 1; + } + break; + + case 'm': + if (part <= 1) { + months = (long)(v*sign); + str = skip_until_space(str); + Dprintf("typecast_MXINTERVAL_cast: months = %ld, rest = %s", + months, str); + v = 0.0; sign = 1.0; part = 2; + } + break; + + case 'd': + if (part <= 2) { + days = (long)(v*sign); + str = skip_until_space(str); + Dprintf("typecast_MXINTERVAL_cast: days = %ld, rest = %s", + days, str); + v = 0.0; sign = 1.0; part = 3; + } + break; + + case ':': + if (part <= 3) { + hours = v; + Dprintf("typecast_MXINTERVAL_cast: hours = %f", hours); + v = 0.0; part = 4; + } + else if (part == 4) { + minutes = v; + Dprintf("typecast_MXINTERVAL_cast: minutes = %f", minutes); + v = 0.0; part = 5; + } + break; + + case '.': + if (part == 5) { + seconds = v; + Dprintf("typecast_MXINTERVAL_cast: seconds = %f", seconds); + v = 0.0; part = 6; + } + break; + + default: + break; + } + + str++; + } + + /* manage last value, be it minutes or seconds or hundredths of a second */ + if (part == 4) { + minutes = v; + Dprintf("typecast_MXINTERVAL_cast: minutes = %f", minutes); + } + else if (part == 5) { + seconds = v; + Dprintf("typecast_MXINTERVAL_cast: seconds = %f", seconds); + } + else if (part == 6) { + hundredths = v; + Dprintf("typecast_MXINTERVAL_cast: hundredths = %f", hundredths); + hundredths = hundredths/denominator; + Dprintf("typecast_MXINTERVAL_cast: fractions = %.20f", hundredths); + } + + /* calculates seconds */ + if (sign < 0.0) { + seconds = - (hundredths + seconds + minutes*60 + hours*3600); + } + else { + seconds += hundredths + minutes*60 + hours*3600; + } + + /* calculates days */ + days += years*365 + months*30; + + Dprintf("typecast_MXINTERVAL_cast: days = %ld, seconds = %f", + days, seconds); + return mxDateTimeP->DateTimeDelta_FromDaysAndSeconds(days, seconds); +} + +/* psycopg defaults to using mx types */ + +#ifdef PSYCOPG_DEFAULT_MXDATETIME +#define typecast_DATE_cast typecast_MXDATE_cast +#define typecast_TIME_cast typecast_MXTIME_cast +#define typecast_INTERVAL_cast typecast_MXINTERVAL_cast +#define typecast_DATETIME_cast typecast_MXDATE_cast +#endif + diff --git a/sandbox/pbool.py b/sandbox/pbool.py new file mode 100644 index 0000000..35ca760 --- /dev/null +++ b/sandbox/pbool.py @@ -0,0 +1,13 @@ +class B(object): + def __init__(self, x): + if x: self._o = True + else: self._o = False + def __getattribute__(self, attr): + print "ga called", attr + return object.__getattribute__(self, attr) + def _sqlquote(self): + if self._o == True: + return 'It is True' + else: + return 'It is False' + diff --git a/sandbox/stress.py b/sandbox/stress.py new file mode 100644 index 0000000..bb31800 --- /dev/null +++ b/sandbox/stress.py @@ -0,0 +1,19 @@ +import psycopg +import psycopg.extras + +conn = psycopg.connect('dbname=test') +#curs = conn.cursor() +#curs.execute("CREATE TABLE itest (n int4)") + +#for i in xrange(10000000): +# curs = conn.cursor() +# curs.execute("INSERT INTO itest VALUES (1)") +# curs.execute("SELECT '2003-12-12 10:00:00'::timestamp AS foo") +# curs.execute("SELECT 'xxx' AS foo") +# curs.fetchall() +# curs.close() + +curs = conn.cursor(factory=psycopg.extras.DictCursor) +curs.execute("select 1 as foo") +x = curs.fetchone() +print x['foo'] diff --git a/sandbox/test.py b/sandbox/test.py new file mode 100644 index 0000000..5704f73 --- /dev/null +++ b/sandbox/test.py @@ -0,0 +1,12 @@ +import datetime +import psycopg + +#d = datetime.timedelta(12, 100, 9876) +#print d.days, d.seconds, d.microseconds +#print psycopg.adapt(d).getquoted() + +o = psycopg.connect("dbname=test") +c = o.cursor() +c.execute("SELECT 1.0 AS foo") +print c.fetchmany(2) +print c.fetchall() diff --git a/scripts/buildtypes.py b/scripts/buildtypes.py new file mode 100644 index 0000000..a09a0cc --- /dev/null +++ b/scripts/buildtypes.py @@ -0,0 +1,100 @@ +# -*- python -*- +# +# Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org> +# +# This file is part of the psycopg module. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2, +# or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# this a little script that analyze a file with (TYPE, NUMBER) tuples +# and write out C code ready for inclusion in psycopg. the generated +# code defines the DBAPITypeObject fundamental types and warns for +# undefined types. + +import sys, os, string, copy +from string import split, join, strip + + +# here is the list of the foundamental types we want to import from +# postgresql header files + +basic_types = (['NUMBER', ['INT8', 'INT4', 'INT2', 'FLOAT8', 'FLOAT4', + 'NUMERIC']], + ['LONGINTEGER', ['INT8']], + ['INTEGER', ['INT4', 'INT2']], + ['FLOAT', ['FLOAT8', 'FLOAT4']], + ['DECIMAL', ['NUMERIC']], + ['UNICODE', ['NAME', 'CHAR', 'TEXT', 'BPCHAR', + 'VARCHAR']], + ['STRING', ['NAME', 'CHAR', 'TEXT', 'BPCHAR', + 'VARCHAR']], + ['BOOLEAN', ['BOOL']], + ['DATETIME', ['TIMESTAMP', 'TIMESTAMPTZ', + 'TINTERVAL', 'INTERVAL']], + ['TIME', ['TIME', 'TIMETZ']], + ['DATE', ['DATE']], + ['INTERVAL', ['TINTERVAL', 'INTERVAL']], + ['BINARY', ['BYTEA']], + ['ROWID', ['OID']]) + +# this is the header used to compile the data in the C module +HEADER = """ +typecastObject_initlist typecast_builtins[] = { +""" + +# then comes the footer +FOOTER = """ {NULL, NULL, NULL}\n};\n""" + + +# usefull error reporting function +def error(msg): + """Report an error on stderr.""" + sys.stderr.write(msg+'\n') + + +# read couples from stdin and build list +read_types = [] +for l in sys.stdin.readlines(): + oid, val = split(l) + read_types.append((strip(oid)[:-3], strip(val))) + +# look for the wanted types in the read touples +found_types = {} + +for t in basic_types: + k = t[0] + found_types[k] = [] + for v in t[1]: + found = filter(lambda x, y=v: x[0] == y, read_types) + if len(found) == 0: + error(v+': value not found') + elif len(found) > 1: + error(v+': too many values') + else: + found_types[k].append(int(found[0][1])) + +# now outputs to stdout the right C-style definitions +stypes = "" ; sstruct = "" +for t in basic_types: + k = t[0] + s = str(found_types[k]) + s = '{' + s[1:-1] + ', 0}' + stypes = stypes + ('static long int typecast_%s_types[] = %s;\n' % (k, s)) + sstruct = sstruct + (' {"%s", typecast_%s_types, typecast_%s_cast},\n' + % (k, k, k)) +sstruct = HEADER + sstruct + FOOTER + +print stypes +print sstruct diff --git a/scripts/maketypes.sh b/scripts/maketypes.sh new file mode 100644 index 0000000..e5078ae --- /dev/null +++ b/scripts/maketypes.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +SCRIPTSDIR="`dirname $0`" +SRCDIR="`dirname $SCRIPTSDIR`/psycopg" + +if [ -z "$1" ] ; then + echo Usage: $0 '<postgresql include directory>' + exit 1 +fi + +echo -n checking for pg_type.h ... +if [ -f "$1/catalog/pg_type.h" ] ; then + PGTYPE="$1/catalog/pg_type.h" +else + if [ -f "$1/server/catalog/pg_type.h" ] ; then + PGTYPE="$1/server/catalog/pg_type.h" + else + echo + echo "error: can't find pg_type.h under $1" + exit 2 + fi +fi +echo " found" + +PGVERSION="`sed -n -e 's/.*PG_VERSION \"\([0-9]\.[0-9]\).*\"/\1/p' $1/pg_config.h`" +PGMAJOR="`echo $PGVERSION | cut -d. -f1`" +PGMINOR="`echo $PGVERSION | cut -d. -f2`" + +echo checking for postgresql major: $PGMAJOR +echo checking for postgresql minor: $PGMINOR + +echo -n generating pgtypes.h ... +awk '/#define .+OID/ {print "#define " $2 " " $3}' "$PGTYPE" \ + > $SRCDIR/pgtypes.h +echo " done" +echo -n generating typecast_builtins.c ... +awk '/#define .+OID/ {print $2 " " $3}' "$PGTYPE" | \ + python $SCRIPTSDIR/buildtypes.py >$SRCDIR/typecast_builtins.c +echo " done" +echo -n generating pgversion.h ... +echo "#define PG_VERSION_MAJOR $PGMAJOR" >$SRCDIR/pgversion.h +echo "#define PG_VERSION_MINOR $PGMINOR" >>$SRCDIR/pgversion.h +echo " done" + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..25bbb52 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[build_ext] +define=PSYCOPG_DEBUG,PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_ASPRINTF,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3 +# PSYCOPG_OWN_QUOTING can be added above but it is deprecated + +# include_dirs is the preferred method for locating postgresql headers, +# but some extra checks on sys.platform will still be done in setup.py +include_dirs=.:/usr/include/postgresql:/usr/include/postgresql/server + +# if postgresql is installed somewhere weird, just addthe right path in +# library_dir any extra libraries required to link in libraries +#library_dirs= +libraries=pq diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ad07b90 --- /dev/null +++ b/setup.py @@ -0,0 +1,167 @@ +# setup.py - distutils packaging +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +"""Python-PostgreSQL Database Adapter + +psycopg is a PostgreSQL database adapter for the Python programming +language. This is version 2, a complete rewrite of the original code to +provide new-style classes for connection and cursor objects and other sweet +candies. Like the original, psycopg 2 was written with the aim of being +very small and fast, and stable as a rock. + +psycopg is different from the other database adapter because it was +designed for heavily multi-threaded applications that create and destroy +lots of cursors and make a conspicuous number of concurrent INSERTs or +UPDATEs. psycopg 2 also provide full asycronous operations for the really +brave programmer. +""" + +classifiers = """\ +Development Status :: 4 - Beta +Intended Audience :: Developers +License :: OSI Approved :: GNU General Public License (GPL) +License :: OSI Approved :: Zope Public License +Programming Language :: Python +Programming Language :: C +Programming Language :: SQL +Topic :: Database +Topic :: Database :: Front-Ends +Topic :: Software Development +Topic :: Software Development :: Libraries :: Python Modules +Operating System :: Microsoft :: Windows +Operating System :: Unix +""" + +import sys, os.path +from distutils.core import setup, Extension +from distutils.sysconfig import get_python_inc +import distutils.ccompiler + +PSYCOPG_VERSION = '1.99.10' + +have_pydatetime = False +have_mxdatetime = False +use_pydatetime = True + +# windows-only definitions (TODO: this should be moved to setup.cfg!) +POSTGRESQLDIR = "D:\\POSTGRESQL-7.4.2" +USE_PG_DLL = True + +# to work around older distutil limitations +if sys.version < '2.2.3': + from distutils.dist import DistributionMetadata + DistributionMetadata.classifiers = None + DistributionMetadata.download_url = None + +# let's start with macro definitions (the ones not already in setup.cfg) +define_macros = [] + +# python version +define_macros.append(('PY_MAJOR_VERSION', str(sys.version_info[0]))) +define_macros.append(('PY_MINOR_VERSION', str(sys.version_info[1]))) + +# some macros related to python versions and features +if sys.version_info[0] >= 2 and sys.version_info[1] >= 3: + define_macros.append(('HAVE_PYBOOL','1')) +if sys.version_info[0] >= 2 and sys.version_info[1] >= 4: + define_macros.append(('HAVE_DECIMAL','1')) + +# gather information to build the extension module +ext = [] ; data_files = [] +library_dirs = [] ; libraries = [] ; include_dirs = [] + +if sys.platform != 'win32': + define_macros.append(('PSYCOPG_VERSION', '"'+PSYCOPG_VERSION+'"')) +else: + define_macros.append(('PSYCOPG_VERSION', '\\"'+PSYCOPG_VERSION+'\\"')) + include_dirs = ['.', + POSTGRESQLDIR + "\\src\\interfaces\\libpq", + POSTGRESQLDIR + "\\src\\include" ] + library_dirs = [ POSTGRESQLDIR + "\\src\\interfaces\\libpq\\Release" ] + libraries = ["ws2_32"] + if USE_PG_DLL: + data_files.append((".\\lib\site-packages\\", + [POSTGRESQLDIR + "\\src\interfaces\\libpq\\Release\\libpq.dll"])) + libraries.append("libpqdll") + else: + libraries.append("libpq") + libraries.append("advapi32") + +# extra checks on darwin +if sys.platform == "darwin": + # fink installs lots of goodies in /sw/... - make sure we check there + include_dirs.append("/sw/include/postgresql") + library_dirs.append("/sw/lib") + +# sources + +sources = [ + 'psycopgmodule.c', 'pqpath.c', 'typecast.c', + 'microprotocols.c', 'microprotocols_proto.c', + 'connection_type.c', 'connection_int.c', 'cursor_type.c', 'cursor_int.c', + 'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c'] + +# check for mx package +mxincludedir = os.path.join(get_python_inc(plat_specific=1), "mx") +if os.path.exists(mxincludedir): + include_dirs.append(mxincludedir) + define_macros.append(('HAVE_MXDATETIME','1')) + sources.append('adapter_mxdatetime.c') + have_mxdatetime = True + +# check for python datetime package +if os.path.exists(os.path.join(get_python_inc(plat_specific=1),"datetime.h")): + define_macros.append(('HAVE_PYDATETIME','1')) + sources.append('adapter_datetime.c') + have_pydatetime = True + +# now decide which package will be the default for date/time typecasts +if have_pydatetime and use_pydatetime \ + or have_pydatetime and not have_mxdatetime: + define_macros.append(('PSYCOPG_DEFAULT_PYDATETIME','1')) +elif have_mxdatetime: + define_macros.append(('PSYCOPG_DEFAULT_MXDATETIME','1')) +else: + sys.stderr.write("error: psycopg requires a datetime module:\n") + sys.stderr.write("error: mx.DateTime module not found\n") + sys.stderr.write("error: python datetime module not found\n") + sys.exit(1) + +# build the extension + +sources = map(lambda x: os.path.join('psycopg', x), sources) + +ext.append(Extension("psycopg._psycopg", sources, + include_dirs=include_dirs, + library_dirs=library_dirs, + define_macros=define_macros, + undef_macros=[], + libraries=libraries)) + +setup(name="psycopg", + version=PSYCOPG_VERSION, + maintainer="Federico Di Gregorio", + maintainer_email="fog@initd.org", + author="Federico Di Gregorio", + author_email="fog@initd.org", + url="http://initd.org/software/initd/psycopg", + download_url = "http://initd.org/software/initd/psycopg", + license="GPL or ZPL", + platforms = ["any"], + description=__doc__.split("\n")[0], + long_description="\n".join(__doc__.split("\n")[2:]), + classifiers=filter(None, classifiers.split("\n")), + data_files=data_files, + package_dir={'psycopg':'lib'}, + packages=['psycopg'], + ext_modules=ext) |