summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Di Gregorio <fog@initd.org>2004-10-19 03:17:12 +0000
committerFederico Di Gregorio <fog@initd.org>2004-10-19 03:17:12 +0000
commitc904d97f696a665958c2cc43333d09c0e6357577 (patch)
treede88cb1cb6a48230f79bc0b532835d26a33660e9
downloadpsycopg2-c904d97f696a665958c2cc43333d09c0e6357577.tar.gz
Initial psycopg 2 import after SVN crash.
-rw-r--r--AUTHORS8
-rw-r--r--ChangeLog362
-rw-r--r--INSTALL18
-rw-r--r--MANIFEST.in8
-rw-r--r--NEWS155
-rw-r--r--README45
-rw-r--r--ZPsycopgDA/DA.py202
-rw-r--r--ZPsycopgDA/DABase.py67
-rw-r--r--ZPsycopgDA/__init__.py32
-rw-r--r--ZPsycopgDA/db.py209
-rw-r--r--ZPsycopgDA/dtml/add.dtml96
-rw-r--r--ZPsycopgDA/dtml/edit.dtml67
-rw-r--r--ZPsycopgDA/icons/bin.gifbin0 -> 924 bytes
-rw-r--r--ZPsycopgDA/icons/date.gifbin0 -> 930 bytes
-rw-r--r--ZPsycopgDA/icons/datetime.gifbin0 -> 925 bytes
-rw-r--r--ZPsycopgDA/icons/field.gifbin0 -> 915 bytes
-rw-r--r--ZPsycopgDA/icons/float.gifbin0 -> 929 bytes
-rw-r--r--ZPsycopgDA/icons/int.gifbin0 -> 918 bytes
-rw-r--r--ZPsycopgDA/icons/stable.gifbin0 -> 884 bytes
-rw-r--r--ZPsycopgDA/icons/table.gifbin0 -> 878 bytes
-rw-r--r--ZPsycopgDA/icons/text.gifbin0 -> 918 bytes
-rw-r--r--ZPsycopgDA/icons/time.gifbin0 -> 926 bytes
-rw-r--r--ZPsycopgDA/icons/view.gifbin0 -> 893 bytes
-rw-r--r--ZPsycopgDA/icons/what.gifbin0 -> 894 bytes
-rw-r--r--ZPsycopgDA/pool.py51
-rw-r--r--doc/ChangeLog-1.x1744
-rw-r--r--doc/HACKING43
-rw-r--r--doc/SUCCESS114
-rw-r--r--doc/TODO33
-rw-r--r--examples/binary.py88
-rw-r--r--examples/cursor.py64
-rw-r--r--examples/dialtone.py145
-rw-r--r--examples/dt.py91
-rw-r--r--examples/encoding.py102
-rw-r--r--examples/lastrowid.py59
-rw-r--r--examples/mogrify.py47
-rw-r--r--examples/myfirstrecipe.py117
-rw-r--r--examples/simple.py52
-rw-r--r--examples/somehackers.jpgbin0 -> 22565 bytes
-rw-r--r--examples/threads.py160
-rw-r--r--examples/tz.py62
-rw-r--r--examples/whereareyou.jpgbin0 -> 34980 bytes
-rw-r--r--lib/__init__.py28
-rw-r--r--lib/extensions.py31
-rw-r--r--lib/extras.py59
-rw-r--r--lib/pool.py184
-rw-r--r--lib/tz.py94
-rw-r--r--psycopg/adapter_binary.c336
-rw-r--r--psycopg/adapter_binary.h52
-rw-r--r--psycopg/adapter_datetime.c439
-rw-r--r--psycopg/adapter_datetime.h95
-rw-r--r--psycopg/adapter_mxdatetime.c392
-rw-r--r--psycopg/adapter_mxdatetime.h100
-rw-r--r--psycopg/adapter_pboolean.c223
-rw-r--r--psycopg/adapter_pboolean.h51
-rw-r--r--psycopg/adapter_qstring.c354
-rw-r--r--psycopg/adapter_qstring.h51
-rw-r--r--psycopg/config.h96
-rw-r--r--psycopg/connection.h98
-rw-r--r--psycopg/connection_int.c277
-rw-r--r--psycopg/connection_type.c401
-rw-r--r--psycopg/cursor.h87
-rw-r--r--psycopg/cursor_int.c47
-rw-r--r--psycopg/cursor_type.c1204
-rw-r--r--psycopg/microprotocols.c121
-rw-r--r--psycopg/microprotocols.h55
-rw-r--r--psycopg/microprotocols_proto.c211
-rw-r--r--psycopg/microprotocols_proto.h45
-rw-r--r--psycopg/pgtypes.h60
-rw-r--r--psycopg/pgversion.h2
-rw-r--r--psycopg/pqpath.c777
-rw-r--r--psycopg/pqpath.h41
-rw-r--r--psycopg/psycopg.h107
-rw-r--r--psycopg/psycopgmodule.c477
-rw-r--r--psycopg/python.h43
-rw-r--r--psycopg/typecast.c439
-rw-r--r--psycopg/typecast.h77
-rw-r--r--psycopg/typecast_basic.c192
-rw-r--r--psycopg/typecast_basic.c.old147
-rw-r--r--psycopg/typecast_builtins.c34
-rw-r--r--psycopg/typecast_datetime.c287
-rw-r--r--psycopg/typecast_mxdatetime.c222
-rw-r--r--sandbox/pbool.py13
-rw-r--r--sandbox/stress.py19
-rw-r--r--sandbox/test.py12
-rw-r--r--scripts/buildtypes.py100
-rw-r--r--scripts/maketypes.sh44
-rw-r--r--setup.cfg12
-rw-r--r--setup.py167
89 files changed, 12644 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..44c77fc
--- /dev/null
+++ b/AUTHORS
@@ -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.
+
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..50d47f5
--- /dev/null
+++ b/INSTALL
@@ -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
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e2415fc
--- /dev/null
+++ b/NEWS
@@ -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.)
diff --git a/README b/README
new file mode 100644
index 0000000..a1d5ce0
--- /dev/null
+++ b/README
@@ -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
new file mode 100644
index 0000000..fa4fdd0
--- /dev/null
+++ b/ZPsycopgDA/icons/bin.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/date.gif b/ZPsycopgDA/icons/date.gif
new file mode 100644
index 0000000..0d88a57
--- /dev/null
+++ b/ZPsycopgDA/icons/date.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/datetime.gif b/ZPsycopgDA/icons/datetime.gif
new file mode 100644
index 0000000..faa540b
--- /dev/null
+++ b/ZPsycopgDA/icons/datetime.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/field.gif b/ZPsycopgDA/icons/field.gif
new file mode 100644
index 0000000..9bf8692
--- /dev/null
+++ b/ZPsycopgDA/icons/field.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/float.gif b/ZPsycopgDA/icons/float.gif
new file mode 100644
index 0000000..efc5c78
--- /dev/null
+++ b/ZPsycopgDA/icons/float.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/int.gif b/ZPsycopgDA/icons/int.gif
new file mode 100644
index 0000000..5ee3ced
--- /dev/null
+++ b/ZPsycopgDA/icons/int.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/stable.gif b/ZPsycopgDA/icons/stable.gif
new file mode 100644
index 0000000..acdd37d
--- /dev/null
+++ b/ZPsycopgDA/icons/stable.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/table.gif b/ZPsycopgDA/icons/table.gif
new file mode 100644
index 0000000..4fb32d9
--- /dev/null
+++ b/ZPsycopgDA/icons/table.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/text.gif b/ZPsycopgDA/icons/text.gif
new file mode 100644
index 0000000..c9d5365
--- /dev/null
+++ b/ZPsycopgDA/icons/text.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/time.gif b/ZPsycopgDA/icons/time.gif
new file mode 100644
index 0000000..6d08915
--- /dev/null
+++ b/ZPsycopgDA/icons/time.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/view.gif b/ZPsycopgDA/icons/view.gif
new file mode 100644
index 0000000..71b30de
--- /dev/null
+++ b/ZPsycopgDA/icons/view.gif
Binary files differ
diff --git a/ZPsycopgDA/icons/what.gif b/ZPsycopgDA/icons/what.gif
new file mode 100644
index 0000000..0214a4d
--- /dev/null
+++ b/ZPsycopgDA/icons/what.gif
Binary files differ
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
new file mode 100644
index 0000000..8bb6e01
--- /dev/null
+++ b/examples/somehackers.jpg
Binary files differ
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
new file mode 100644
index 0000000..f508c0b
--- /dev/null
+++ b/examples/whereareyou.jpg
Binary files differ
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)