summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog13
-rw-r--r--examples/lobject.py79
-rw-r--r--lib/extensions.py3
-rw-r--r--psycopg/connection_int.c3
-rw-r--r--psycopg/connection_type.c84
-rw-r--r--psycopg/lobject.h89
-rw-r--r--psycopg/lobject_int.c244
-rw-r--r--psycopg/lobject_type.c389
-rw-r--r--psycopg/pqpath.c2
-rw-r--r--psycopg/psycopgmodule.c13
-rw-r--r--setup.py1
11 files changed, 913 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index af04ec0..f88d549 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2006-09-01 Federico Di Gregorio <fog@initd.org>
+
+ * psycopg/connection_type.c: merged in double mutex destroy patch
+ from Joerg Sonnenberger.
+
+ * Implemented large objects support.
+
+ * psycopg/connection_int.c: removed increment of self->mark,
+ now it is done directly in pqpath.c to make sure even the
+ large object support gets it.
+
+ * Starting 2.1 development.
+
2008-04-21 James Henstridge <james@jamesh.id.au>
* tests/test_quote.py (QuotingTestCase.test_unicode): If the
diff --git a/examples/lobject.py b/examples/lobject.py
new file mode 100644
index 0000000..182c951
--- /dev/null
+++ b/examples/lobject.py
@@ -0,0 +1,79 @@
+# lobject.py - lobject example
+#
+# Copyright (C) 2001-2006 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 psycopg2
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg2.connect(DSN)
+print "Encoding for this connection is", conn.encoding
+
+# this will create a large object with a new random oid, we'll
+# use it to make some basic tests about read/write and seek.
+lobj = conn.lobject()
+loid = lobj.oid
+print "Created a new large object with oid", loid
+
+print "Manually importing some binary data into the object:"
+data = open("somehackers.jpg").read()
+len = lobj.write(data)
+print " imported", len, "bytes of data"
+
+conn.commit()
+
+print "Trying to (re)open large object with oid", loid
+lobj = conn.lobject(loid)
+print "Manually exporting the data from the lobject:"
+data1 = lobj.read()
+len = lobj.tell()
+lobj.seek(0, 0)
+data2 = lobj.read()
+if data1 != data2:
+ print "ERROR: read after seek returned different data"
+open("somehackers_lobject1.jpg", 'wb').write(data1)
+print " written", len, "bytes of data to somehackers_lobject1.jpg"
+
+lobj.unlink()
+print "Large object with oid", loid, "removed"
+
+conn.commit()
+
+# now we try to use the import and export functions to do the same
+lobj = conn.lobject(0, 'n', 0, "somehackers.jpg")
+loid = lobj.oid
+print "Imported a new large object with oid", loid
+
+conn.commit()
+
+print "Trying to (re)open large object with oid", loid
+lobj = conn.lobject(loid, 'n')
+print "Using export() to export the data from the large object:"
+lobj.export("somehackers_lobject2.jpg")
+print " exported large object to somehackers_lobject2.jpg"
+
+lobj.unlink()
+print "Large object with oid", loid, "removed"
+
+conn.commit()
+
+print "\nNow try to load the new images, to check it worked!"
diff --git a/lib/extensions.py b/lib/extensions.py
index a2bc869..99c7201 100644
--- a/lib/extensions.py
+++ b/lib/extensions.py
@@ -4,6 +4,7 @@ This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
- `connection` -- the new-type inheritable connection class
- `cursor` -- the new-type inheritable cursor class
+- `lobject` -- the new-type inheritable large object class
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
by psycopg to adapt Python types to PostgreSQL ones
@@ -38,7 +39,7 @@ try:
except:
pass
-from _psycopg import adapt, adapters, encodings, connection, cursor
+from _psycopg import adapt, adapters, encodings, connection, cursor, lobject
from _psycopg import string_types, binary_types, new_type, register_type
from _psycopg import ISQLQuote
diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c
index b9d4484..65c88c5 100644
--- a/psycopg/connection_int.c
+++ b/psycopg/connection_int.c
@@ -246,7 +246,6 @@ conn_commit(connectionObject *self)
int res;
res = pq_commit(self);
- self->mark++;
return res;
}
@@ -258,7 +257,6 @@ conn_rollback(connectionObject *self)
int res;
res = pq_abort(self);
- self->mark++;
return res;
}
@@ -283,7 +281,6 @@ conn_switch_isolation_level(connectionObject *self, int level)
res = pq_abort_locked(self, &pgres, &error);
}
self->isolation_level = level;
- self->mark++;
Dprintf("conn_switch_isolation_level: switched to level %d", level);
diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c
index d9b4541..f5cc3b5 100644
--- a/psycopg/connection_type.c
+++ b/psycopg/connection_type.c
@@ -33,6 +33,7 @@
#include "psycopg/psycopg.h"
#include "psycopg/connection.h"
#include "psycopg/cursor.h"
+#include "psycopg/lobject.h"
/** DBAPI methods **/
@@ -178,7 +179,7 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
return Py_None;
}
-/* set_isolation_level method - switch connection isolation level */
+/* set_client_encoding method - set client encoding */
#define psyco_conn_set_client_encoding_doc \
"set_client_encoding(encoding) -- Set client encoding to ``encoding``."
@@ -215,7 +216,7 @@ psyco_conn_set_client_encoding(connectionObject *self, PyObject *args)
}
}
-/* set_isolation_level method - switch connection isolation level */
+/* get_transaction_status method - Get backend transaction status */
#define psyco_conn_get_transaction_status_doc \
"get_transaction_status() -- Get backend transaction status."
@@ -230,6 +231,82 @@ psyco_conn_get_transaction_status(connectionObject *self, PyObject *args)
return PyInt_FromLong((long)PQtransactionStatus(self->pgconn));
}
+/* lobject method - allocate a new lobject */
+
+#define psyco_conn_lobject_doc \
+"cursor(oid=0, mode=0, new_oid=0, new_file=None,\n" \
+" lobject_factory=extensions.lobject) -- new lobject\n\n" \
+"Return a new lobject.\n\nThe ``lobject_factory`` argument can be used\n" \
+"to create non-standard lobjects by passing a class different from the\n" \
+"default. Note that the new class *should* be a sub-class of\n" \
+"`extensions.lobject`.\n\n" \
+":rtype: `extensions.lobject`"
+
+static PyObject *
+psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds)
+{
+ Oid oid=InvalidOid, new_oid=InvalidOid;
+ char *smode = NULL, *new_file = NULL;
+ int mode=0;
+ PyObject *obj, *factory = NULL;
+
+ static char *kwlist[] = {"oid", "mode", "new_oid", "new_file",
+ "cursor_factory", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izizO", kwlist,
+ &oid, &smode, &new_oid, &new_file,
+ &factory)) {
+ return NULL;
+ }
+
+ EXC_IF_CONN_CLOSED(self);
+
+ Dprintf("psyco_conn_lobject: new lobject for connection at %p", self);
+ Dprintf("psyco_conn_lobject: parameters: oid = %d, mode = %s",
+ oid, smode);
+ Dprintf("psyco_conn_lobject: parameters: new_oid = %d, new_file = %s",
+ new_oid, new_file);
+
+ /* build a mode number out of the mode string: right now we only accept
+ 'r', 'w' and 'rw' (but note that 'w' implies 'rw' because PostgreSQL
+ backend does that. */
+ if (smode) {
+ if (strncmp("rw", smode, 2) == 0)
+ mode = INV_READ+INV_WRITE;
+ else if (smode[0] == 'r')
+ mode = INV_READ;
+ else if (smode[0] == 'w')
+ mode = INV_WRITE;
+ else if (smode[0] == 'n')
+ mode = -1;
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "mode should be one of 'r', 'w' or 'rw'");
+ return NULL;
+ }
+ }
+
+ if (factory == NULL) factory = (PyObject *)&lobjectType;
+ if (new_file)
+ obj = PyObject_CallFunction(factory, "Oiiis",
+ self, oid, mode, new_oid, new_file);
+ else
+ obj = PyObject_CallFunction(factory, "Oiii",
+ self, oid, mode, new_oid);
+
+ if (obj == NULL) return NULL;
+ if (PyObject_IsInstance(obj, (PyObject *)&lobjectType) == 0) {
+ PyErr_SetString(PyExc_TypeError,
+ "lobject factory must be subclass of psycopg2._psycopg.lobject");
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ Dprintf("psyco_conn_lobject: new lobject at %p: refcnt = %d",
+ obj, obj->ob_refcnt);
+ return obj;
+}
+
#endif
static PyObject *
@@ -262,7 +339,8 @@ static struct PyMethodDef connectionObject_methods[] = {
METH_VARARGS, psyco_conn_set_client_encoding_doc},
{"get_transaction_status", (PyCFunction)psyco_conn_get_transaction_status,
METH_VARARGS, psyco_conn_get_transaction_status_doc},
-#endif
+ {"lobject", (PyCFunction)psyco_conn_lobject,
+ METH_VARARGS|METH_KEYWORDS, psyco_conn_lobject_doc},
{NULL}
};
diff --git a/psycopg/lobject.h b/psycopg/lobject.h
new file mode 100644
index 0000000..4b84021
--- /dev/null
+++ b/psycopg/lobject.h
@@ -0,0 +1,89 @@
+/* lobject.h - definition for the psycopg lobject type
+ *
+ * Copyright (C) 2006 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_LOBJECT_H
+#define PSYCOPG_LOBJECT_H 1
+
+#include <Python.h>
+#include <libpq-fe.h>
+#include <libpq/libpq-fs.h>
+
+#include "psycopg/connection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern PyTypeObject lobjectType;
+
+typedef struct {
+ PyObject HEAD;
+
+ connectionObject *conn; /* connection owning the lobject */
+
+ int closed; /* 1 if the lobject is closed */
+ int mode; /* numeric mode, tells if lobject was opened */
+ char *smode; /* string mode if lobject was opened */
+
+ long int mark; /* copied from conn->mark */
+
+ Oid oid; /* the oid for this lobject */
+ int fd; /* the file descriptor for file-like ops */
+} lobjectObject;
+
+/* functions exported from lobject_int.c */
+
+extern int lobject_open(lobjectObject *self, connectionObject *conn,
+ Oid oid, int mode, Oid new_oid, char *new_file);
+extern int lobject_unlink(lobjectObject *self);
+extern int lobject_export(lobjectObject *self, char *filename);
+
+extern size_t lobject_read(lobjectObject *self, char *buf, size_t len);
+extern size_t lobject_write(lobjectObject *self, char *buf, size_t len);
+extern int lobject_seek(lobjectObject *self, int pos, int whence);
+extern int lobject_tell(lobjectObject *self);
+extern void lobject_close(lobjectObject *self);
+
+/* exception-raising macros */
+
+#define EXC_IF_LOBJ_CLOSED(self) \
+if ((self)->closed || ((self)->conn && (self)->conn->closed)) { \
+ PyErr_SetString(InterfaceError, "lobject already closed"); \
+ return NULL; }
+
+#define EXC_IF_LOBJ_LEVEL0(self) \
+if (self->conn->isolation_level == 0) { \
+ psyco_set_error(ProgrammingError, (PyObject*)self, \
+ "can't use a lobject outside of transactions", NULL, NULL); \
+ return NULL; \
+}
+#define EXC_IF_LOBJ_UNMARKED(self) \
+if (self->conn->mark != self->mark) { \
+ psyco_set_error(ProgrammingError, (PyObject*)self, \
+ "lobject isn't valid anymore", NULL, NULL); \
+ return NULL; \
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !defined(PSYCOPG_LOBJECT_H) */
diff --git a/psycopg/lobject_int.c b/psycopg/lobject_int.c
new file mode 100644
index 0000000..d4cc38f
--- /dev/null
+++ b/psycopg/lobject_int.c
@@ -0,0 +1,244 @@
+/* lobject_int.c - code used by the lobject object
+ *
+ * Copyright (C) 2006 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/lobject.h"
+#include "psycopg/pqpath.h"
+
+#ifdef PSYCOPG_EXTENSIONS
+
+/* lobject_open - create a new/open an existing lo */
+
+int
+lobject_open(lobjectObject *self, connectionObject *conn,
+ Oid oid, int mode, Oid new_oid, char *new_file)
+{
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ pq_begin(self->conn);
+
+ /* if the oid is InvalidOid we create a new lob before opening it
+ or we import a file from the FS, depending on the value of
+ new_name */
+ if (oid == InvalidOid) {
+ if (new_file)
+ self->oid = lo_import(self->conn->pgconn, new_file);
+ else
+ self->oid = lo_create(self->conn->pgconn, new_oid);
+
+ Dprintf("lobject_open: large object created with oid = %d",
+ self->oid);
+
+ if (self->oid == InvalidOid) goto end;
+
+ mode = INV_WRITE;
+ }
+ else {
+ self->oid = oid;
+ if (mode == 0) mode = INV_READ;
+ }
+
+ /* if the oid is a real one we try to open with the given mode,
+ unless the mode is -1, meaning "don't open!" */
+ if (mode != -1) {
+ self->fd = lo_open(self->conn->pgconn, self->oid, mode);
+ Dprintf("lobject_open: large object opened with fd = %d",
+ self->fd);
+ }
+ else {
+ /* this is necessary to make sure no function that needs and
+ fd is called on unopened lobjects */
+ self->closed = 1;
+ }
+
+ end:
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ /* here we check for errors before returning 0 */
+ if ((self->fd == -1 && mode != -1) || self->oid == InvalidOid) {
+ pq_raise(conn, NULL, NULL, NULL);
+ return -1;
+ }
+ else {
+ /* set the mode for future reference and return */
+ self->mode = mode;
+ switch (mode) {
+ case -1:
+ self->smode = "n"; break;
+ case INV_READ:
+ self->smode = "r"; break;
+ case INV_WRITE:
+ self->smode = "w"; break;
+ case INV_READ+INV_WRITE:
+ self->smode = "rw"; break;
+ }
+ return 0;
+ }
+}
+
+/* lobject_unlink - remove an lo from database */
+
+int
+lobject_unlink(lobjectObject *self)
+{
+ int res;
+
+ /* first we make sure the lobject is closed and then we unlink */
+ lobject_close(self);
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ pq_begin(self->conn);
+
+ res = lo_unlink(self->conn->pgconn, self->oid);
+
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ if (res == -1)
+ pq_raise(self->conn, NULL, NULL, NULL);
+ return res;
+}
+
+/* lobject_close - close an existing lo */
+
+void
+lobject_close(lobjectObject *self)
+{
+ if (self->conn->isolation_level > 0
+ && self->conn->mark == self->mark) {
+ if (self->fd != -1)
+ lo_close(self->conn->pgconn, self->fd);
+ }
+}
+
+/* lobject_write - write bytes to a lo */
+
+size_t
+lobject_write(lobjectObject *self, char *buf, size_t len)
+{
+ size_t written;
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ written = lo_write(self->conn->pgconn, self->fd, buf, len);
+
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ if (written < 0)
+ pq_raise(self->conn, NULL, NULL, NULL);
+ return written;
+}
+
+/* lobject_read - read bytes from a lo */
+
+size_t
+lobject_read(lobjectObject *self, char *buf, size_t len)
+{
+ size_t readed;
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ readed = lo_read(self->conn->pgconn, self->fd, buf, len);
+
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ if (readed < 0)
+ pq_raise(self->conn, NULL, NULL, NULL);
+ return readed;
+}
+
+/* lobject_seek - move the current position in the lo */
+
+int
+lobject_seek(lobjectObject *self, int pos, int whence)
+{
+ int where;
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ where = lo_lseek(self->conn->pgconn, self->fd, pos, whence);
+
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ if (where < 0)
+ pq_raise(self->conn, NULL, NULL, NULL);
+ return where;
+}
+
+/* lobject_tell - tell the current position in the lo */
+
+int
+lobject_tell(lobjectObject *self)
+{
+ int where;
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ where = lo_tell(self->conn->pgconn, self->fd);
+
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ if (where < 0)
+ pq_raise(self->conn, NULL, NULL, NULL);
+ return where;
+}
+
+/* lobject_export - export to a local file */
+
+int
+lobject_export(lobjectObject *self, char *filename)
+{
+ int res;
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&(self->conn->lock));
+
+ res = lo_export(self->conn->pgconn, self->oid, filename);
+
+ pthread_mutex_unlock(&(self->conn->lock));
+ Py_END_ALLOW_THREADS;
+
+ if (res < 0)
+ pq_raise(self->conn, NULL, NULL, NULL);
+ return res;
+}
+
+
+#endif
+
diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c
new file mode 100644
index 0000000..8d299e9
--- /dev/null
+++ b/psycopg/lobject_type.c
@@ -0,0 +1,389 @@
+/* lobject_type.c - python interface to lobject objects
+ *
+ * Copyright (C) 2003-2006 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 Likcense
+ * 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/lobject.h"
+#include "psycopg/connection.h"
+#include "psycopg/microprotocols.h"
+#include "psycopg/microprotocols_proto.h"
+#include "psycopg/pqpath.h"
+#include "pgversion.h"
+
+
+#ifdef PSYCOPG_EXTENSIONS
+
+/** public methods **/
+
+/* close method - close the lobject */
+
+#define psyco_lobj_close_doc \
+"close() -- Close the lobject."
+
+static PyObject *
+psyco_lobj_close(lobjectObject *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, "")) return NULL;
+
+ /* file-like objects can be closed multiple times and remember that
+ closing the current transaction is equivalent to close all the
+ opened large objects */
+ if (!self->closed
+ && self->conn->isolation_level > 0
+ && self->conn->mark == self->mark)
+ {
+ self->closed = 1;
+ lobject_close(self);
+
+ Dprintf("psyco_lobj_close: lobject at %p closed", self);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* write method - write data to the lobject */
+
+#define psyco_lobj_write_doc \
+"write(str) -- Write a string to the large object."
+
+static PyObject *
+psyco_lobj_write(lobjectObject *self, PyObject *args)
+{
+ int len, res=0;
+ char *buffer;
+
+ if (!PyArg_ParseTuple(args, "s#", &buffer, &len)) return NULL;
+
+ EXC_IF_LOBJ_CLOSED(self);
+ EXC_IF_LOBJ_LEVEL0(self);
+ EXC_IF_LOBJ_UNMARKED(self);
+
+ if ((res = lobject_write(self, buffer, len)) < 0) return NULL;
+
+ return PyInt_FromLong((long)res);
+}
+
+/* read method - read data from the lobject */
+
+#define psyco_lobj_read_doc \
+"read(size=-1) -- Read at most size bytes or to the end of the large object."
+
+static PyObject *
+psyco_lobj_read(lobjectObject *self, PyObject *args)
+{
+ int where, end, size = -1;
+ char *buffer;
+
+ if (!PyArg_ParseTuple(args, "|i", &size)) return NULL;
+
+ EXC_IF_LOBJ_CLOSED(self);
+ EXC_IF_LOBJ_LEVEL0(self);
+ EXC_IF_LOBJ_UNMARKED(self);
+
+ if (size < 0) {
+ if ((where = lobject_tell(self)) < 0) return NULL;
+ if ((end = lobject_seek(self, 0, SEEK_END)) < 0) return NULL;
+ if (lobject_seek(self, where, SEEK_SET) < 0) return NULL;
+ size = end - where;
+ }
+
+ if ((buffer = PyMem_Malloc(size)) == NULL) return NULL;
+ if ((size = lobject_read(self, buffer, size)) < 0) {
+ PyMem_Free(buffer);
+ return NULL;
+ }
+
+ return PyString_FromStringAndSize(buffer, size);
+}
+
+/* seek method - seek in the lobject */
+
+#define psyco_lobj_seek_doc \
+"seek(offset, whence=0) -- Set the lobject's current position."
+
+static PyObject *
+psyco_lobj_seek(lobjectObject *self, PyObject *args)
+{
+ int offset, whence=0;
+ int pos=0;
+
+ if (!PyArg_ParseTuple(args, "i|i", &offset, &whence))
+ return NULL;
+
+ EXC_IF_LOBJ_CLOSED(self);
+ EXC_IF_LOBJ_LEVEL0(self);
+ EXC_IF_LOBJ_UNMARKED(self);
+
+ if ((pos = lobject_seek(self, pos, whence)) < 0)
+ return NULL;
+
+ return PyInt_FromLong((long)pos);
+}
+
+/* tell method - tell current position in the lobject */
+
+#define psyco_lobj_tell_doc \
+"tell() -- Return the lobject's current position."
+
+static PyObject *
+psyco_lobj_tell(lobjectObject *self, PyObject *args)
+{
+ int pos;
+
+ if (!PyArg_ParseTuple(args, "")) return NULL;
+
+ EXC_IF_LOBJ_CLOSED(self);
+ EXC_IF_LOBJ_LEVEL0(self);
+ EXC_IF_LOBJ_UNMARKED(self);
+
+ if ((pos = lobject_tell(self)) < 0)
+ return NULL;
+
+ return PyInt_FromLong((long)pos);
+}
+
+/* unlink method - unlink (destroy) the lobject */
+
+#define psyco_lobj_unlink_doc \
+"unlink() -- Close and then remove the lobject."
+
+static PyObject *
+psyco_lobj_unlink(lobjectObject *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, "")) return NULL;
+
+ if (lobject_unlink(self) < 0)
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* export method - export lobject's content to given file */
+
+#define psyco_lobj_export_doc \
+"export(filename) -- Export large object to given file."
+
+static PyObject *
+psyco_lobj_export(lobjectObject *self, PyObject *args)
+{
+ char *filename;
+
+ if (!PyArg_ParseTuple(args, "s", &filename))
+ return NULL;
+
+ if (lobject_export(self, filename) < 0)
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+/** the lobject object **/
+
+/* object method list */
+
+static struct PyMethodDef lobjectObject_methods[] = {
+ {"read", (PyCFunction)psyco_lobj_read,
+ METH_VARARGS, psyco_lobj_read_doc},
+ {"write", (PyCFunction)psyco_lobj_write,
+ METH_VARARGS, psyco_lobj_write_doc},
+ {"seek", (PyCFunction)psyco_lobj_seek,
+ METH_VARARGS, psyco_lobj_seek_doc},
+ {"tell", (PyCFunction)psyco_lobj_tell,
+ METH_VARARGS, psyco_lobj_tell_doc},
+ {"close", (PyCFunction)psyco_lobj_close,
+ METH_VARARGS, psyco_lobj_close_doc},
+ {"unlink",(PyCFunction)psyco_lobj_unlink,
+ METH_VARARGS, psyco_lobj_unlink_doc},
+ {"export",(PyCFunction)psyco_lobj_export,
+ METH_VARARGS, psyco_lobj_export_doc},
+ {NULL}
+};
+
+/* object member list */
+
+static struct PyMemberDef lobjectObject_members[] = {
+ {"oid", T_LONG, offsetof(lobjectObject, oid), RO,
+ "The backend OID associated to this lobject."},
+ {"closed", T_LONG, offsetof(lobjectObject, closed), RO,
+ "The if the large object is closed (no file-like methods)."},
+ {"mode", T_STRING, offsetof(lobjectObject, smode), RO,
+ "Open mode ('r', 'w', 'rw' or 'n')."},
+ {NULL}
+};
+
+/* initialization and finalization methods */
+
+static int
+lobject_setup(lobjectObject *self, connectionObject *conn,
+ Oid oid, int mode, Oid new_oid, char *new_file)
+{
+ Dprintf("lobject_setup: init lobject object at %p", self);
+
+ if (conn->isolation_level == 0) {
+ psyco_set_error(ProgrammingError, (PyObject*)self,
+ "can't use a lobject outside of transactions", NULL, NULL);
+ return -1;
+ }
+
+ self->conn = conn;
+ self->mark = conn->mark;
+
+ Py_INCREF((PyObject*)self->conn);
+
+ self->closed = 0;
+ self->oid = InvalidOid;
+ self->fd = -1;
+
+ if (lobject_open(self, conn, oid, mode, new_oid, new_file) == -1)
+ return -1;
+
+ Dprintf("lobject_setup: good lobject object at %p, refcnt = %d",
+ self, ((PyObject *)self)->ob_refcnt);
+ Dprintf("lobject_setup: oid = %d, fd = %d", self->oid, self->fd);
+ return 0;
+}
+
+static void
+lobject_dealloc(PyObject* obj)
+{
+ lobjectObject *self = (lobjectObject *)obj;
+
+ lobject_close(self);
+ Py_XDECREF((PyObject*)self->conn);
+
+ Dprintf("lobject_dealloc: deleted lobject object at %p, refcnt = %d",
+ obj, obj->ob_refcnt);
+
+ obj->ob_type->tp_free(obj);
+}
+
+static int
+lobject_init(PyObject *obj, PyObject *args, PyObject *kwds)
+{
+ Oid oid=InvalidOid, new_oid=InvalidOid;
+ int mode=0;
+ char *new_file = NULL;
+ PyObject *conn;
+
+ if (!PyArg_ParseTuple(args, "O|iiis",
+ &conn, &oid, &mode, &new_oid, &new_file))
+ return -1;
+
+ return lobject_setup((lobjectObject *)obj,
+ (connectionObject *)conn, oid, mode, new_oid, new_file);
+}
+
+static PyObject *
+lobject_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ return type->tp_alloc(type, 0);
+}
+
+static void
+lobject_del(PyObject* self)
+{
+ PyObject_Del(self);
+}
+
+static PyObject *
+lobject_repr(lobjectObject *self)
+{
+ return PyString_FromFormat(
+ "<lobject object at %p; closed: %d>", self, self->closed);
+}
+
+
+/* object type */
+
+#define lobjectType_doc \
+"A database large object."
+
+PyTypeObject lobjectType = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "psycopg2._psycopg.lobject",
+ sizeof(lobjectObject),
+ 0,
+ lobject_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ (reprfunc)lobject_repr, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+
+ 0, /*tp_call*/
+ (reprfunc)lobject_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*/
+ lobjectType_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 */
+
+ lobjectObject_methods, /*tp_methods*/
+ lobjectObject_members, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+
+ lobject_init, /*tp_init*/
+ 0, /*tp_alloc Will be set to PyType_GenericAlloc in module init*/
+ lobject_new, /*tp_new*/
+ (freefunc)lobject_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*/
+};
+
+#endif
+
diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c
index 14eecd2..16703b3 100644
--- a/psycopg/pqpath.c
+++ b/psycopg/pqpath.c
@@ -416,6 +416,7 @@ pq_commit(connectionObject *conn)
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock);
+ conn->mark += 1;
pq_clear_async(conn);
retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error);
@@ -446,6 +447,7 @@ pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error)
return 0;
}
+ conn->mark += 1;
pq_clear_async(conn);
retvalue = pq_execute_command_locked(conn, "ROLLBACK", pgres, error);
if (retvalue == 0)
diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c
index a26aac1..6c658fc 100644
--- a/psycopg/psycopgmodule.c
+++ b/psycopg/psycopgmodule.c
@@ -28,6 +28,7 @@
#include "psycopg/psycopg.h"
#include "psycopg/connection.h"
#include "psycopg/cursor.h"
+#include "psycopg/lobject.h"
#include "psycopg/typecast.h"
#include "psycopg/microprotocols.h"
#include "psycopg/microprotocols_proto.h"
@@ -719,6 +720,11 @@ init_psycopg(void)
if (PyType_Ready(&listType) == -1) return;
if (PyType_Ready(&chunkType) == -1) return;
+#ifdef PSYCOPG_EXTENSIONS
+ lobjectType.ob_type = &PyType_Type;
+ if (PyType_Ready(&lobjectType) == -1) return;
+#endif
+
#ifdef HAVE_PYBOOL
pbooleanType.ob_type = &PyType_Type;
if (PyType_Ready(&pbooleanType) == -1) return;
@@ -794,6 +800,9 @@ init_psycopg(void)
PyModule_AddObject(module, "connection", (PyObject*)&connectionType);
PyModule_AddObject(module, "cursor", (PyObject*)&cursorType);
PyModule_AddObject(module, "ISQLQuote", (PyObject*)&isqlquoteType);
+#ifdef PSYCOPG_EXTENSIONS
+ PyModule_AddObject(module, "lobject", (PyObject*)&lobjectType);
+#endif
/* encodings dictionary in module dictionary */
PyModule_AddObject(module, "encodings", psycoEncodings);
@@ -820,6 +829,10 @@ init_psycopg(void)
listType.tp_alloc = PyType_GenericAlloc;
chunkType.tp_alloc = PyType_GenericAlloc;
+#ifdef PSYCOPG_EXTENSIONS
+ lobjectType.tp_alloc = PyType_GenericAlloc;
+#endif
+
#ifdef HAVE_PYDATETIME
pydatetimeType.tp_alloc = PyType_GenericAlloc;
#endif
diff --git a/setup.py b/setup.py
index 09181d4..bf74cb0 100644
--- a/setup.py
+++ b/setup.py
@@ -326,6 +326,7 @@ sources = [
'psycopgmodule.c', 'pqpath.c', 'typecast.c',
'microprotocols.c', 'microprotocols_proto.c',
'connection_type.c', 'connection_int.c', 'cursor_type.c', 'cursor_int.c',
+ 'lobject_type.c', 'lobject_int.c',
'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c',
'adapter_asis.c', 'adapter_list.c']