mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-01-31 09:24:07 +03:00
Merge branch 'lo64'
This commit is contained in:
commit
b5ac992944
4
NEWS
4
NEWS
|
@ -4,6 +4,10 @@ Current release
|
|||
What's new in psycopg 2.6
|
||||
-------------------------
|
||||
|
||||
New features:
|
||||
|
||||
- Added support for large objects larger than 2GB.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Json apapter's `!str()` returns the adapted content instead of the `!repr()`
|
||||
|
|
|
@ -40,18 +40,20 @@ functionalities defined by the |DBAPI|_.
|
|||
|
||||
The class can be subclassed: see the `connection.lobject()` to know
|
||||
how to specify a `!lobject` subclass.
|
||||
|
||||
|
||||
.. versionadded:: 2.0.8
|
||||
|
||||
.. attribute:: oid
|
||||
|
||||
Database OID of the object.
|
||||
|
||||
|
||||
.. attribute:: mode
|
||||
|
||||
The mode the database was open. See `connection.lobject()` for a
|
||||
description of the available modes.
|
||||
|
||||
|
||||
.. method:: read(bytes=-1)
|
||||
|
||||
Read a chunk of data from the current file position. If -1 (default)
|
||||
|
@ -64,6 +66,7 @@ functionalities defined by the |DBAPI|_.
|
|||
.. versionchanged:: 2.4
|
||||
added Unicode support.
|
||||
|
||||
|
||||
.. method:: write(str)
|
||||
|
||||
Write a string to the large object. Return the number of bytes
|
||||
|
@ -73,42 +76,60 @@ functionalities defined by the |DBAPI|_.
|
|||
.. versionchanged:: 2.4
|
||||
added Unicode support.
|
||||
|
||||
|
||||
.. method:: export(file_name)
|
||||
|
||||
Export the large object content to the file system.
|
||||
|
||||
|
||||
The method uses the efficient |lo_export|_ libpq function.
|
||||
|
||||
|
||||
.. |lo_export| replace:: `!lo_export()`
|
||||
.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
|
||||
|
||||
|
||||
.. method:: seek(offset, whence=0)
|
||||
|
||||
Set the lobject current position.
|
||||
|
||||
.. versionchanged:: 2.6.0
|
||||
added support for *offset* > 2GB.
|
||||
|
||||
|
||||
.. method:: tell()
|
||||
|
||||
Return the lobject current position.
|
||||
|
||||
.. method:: truncate(len=0)
|
||||
|
||||
.. versionadded:: 2.2.0
|
||||
|
||||
.. versionchanged:: 2.6.0
|
||||
added support for return value > 2GB.
|
||||
|
||||
|
||||
.. method:: truncate(len=0)
|
||||
|
||||
Truncate the lobject to the given size.
|
||||
|
||||
The method will only be available if Psycopg has been built against libpq
|
||||
from PostgreSQL 8.3 or later and can only be used with PostgreSQL servers
|
||||
running these versions. It uses the |lo_truncate|_ libpq function.
|
||||
The method will only be available if Psycopg has been built against
|
||||
libpq from PostgreSQL 8.3 or later and can only be used with
|
||||
PostgreSQL servers running these versions. It uses the |lo_truncate|_
|
||||
libpq function.
|
||||
|
||||
.. |lo_truncate| replace:: `!lo_truncate()`
|
||||
.. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
|
||||
|
||||
.. warning::
|
||||
.. versionadded:: 2.2.0
|
||||
|
||||
.. versionchanged:: 2.6.0
|
||||
added support for *len* > 2GB.
|
||||
|
||||
.. warning::
|
||||
|
||||
If Psycopg is built with |lo_truncate| support or with the 64 bits API
|
||||
support (resp. from PostgreSQL versions 8.3 and 9.3) but at runtime an
|
||||
older version of the dynamic library is found, the ``psycopg2`` module
|
||||
will fail to import. See :ref:`the lo_truncate FAQ <faq-lo_truncate>`
|
||||
about the problem.
|
||||
|
||||
If Psycopg is built with |lo_truncate| support (i.e. if the
|
||||
:program:`pg_config` used during setup is version >= 8.3), but at
|
||||
runtime an older libpq is found, Psycopg will fail to import. See
|
||||
:ref:`the lo_truncate FAQ <faq-lo_truncate>` about the problem.
|
||||
|
||||
.. method:: close()
|
||||
|
||||
|
|
|
@ -248,13 +248,20 @@ I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file
|
|||
.. cssclass:: faq
|
||||
|
||||
`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported.
|
||||
This means that Psycopg has been compiled with |lo_truncate|_ support,
|
||||
which means that the libpq used at compile time was version >= 8.3, but at
|
||||
runtime an older libpq library is found. You can use::
|
||||
This means that Psycopg was compiled with |lo_truncate|_ support (*i.e.*
|
||||
the libpq used at compile time was version >= 8.3) but at runtime an older
|
||||
libpq dynamic library is found.
|
||||
|
||||
Fast-forward several years, if the message reports *undefined symbol:
|
||||
lo_truncate64* it means that Psycopg was built with large objects 64 bits
|
||||
API support (*i.e.* the libpq used at compile time was at least 9.3) but
|
||||
at runtime an older libpq dynamic library is found.
|
||||
|
||||
You can use::
|
||||
|
||||
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq
|
||||
|
||||
to find what is the version used at runtime.
|
||||
to find what is the libpq dynamic library used at runtime.
|
||||
|
||||
You can avoid the problem by using the same version of the
|
||||
:program:`pg_config` at install time and the libpq at runtime.
|
||||
|
|
|
@ -899,6 +899,18 @@ using the |lo_import|_ and |lo_export|_ libpq functions.
|
|||
.. |lo_export| replace:: `!lo_export()`
|
||||
.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
added support for large objects greated than 2GB. Note that the support is
|
||||
enabled only if both these conditions are verified:
|
||||
|
||||
- the extension was built against libpq at least 9.3 (you can check if
|
||||
`psycopg2.__version__` contains the ``lo64`` flag);
|
||||
- the server version is at least PostgreSQL 9.3
|
||||
(`~connection.server_version` must be >= ``90300``).
|
||||
|
||||
If the contitions are not met several `!lobject` methods will fail if the
|
||||
arguments exceed 2GB.
|
||||
|
||||
|
||||
|
||||
.. index::
|
||||
|
|
|
@ -60,8 +60,8 @@ RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename);
|
|||
RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf,
|
||||
size_t len);
|
||||
RAISES_NEG HIDDEN int lobject_seek(lobjectObject *self, int pos, int whence);
|
||||
RAISES_NEG HIDDEN int lobject_tell(lobjectObject *self);
|
||||
RAISES_NEG HIDDEN long lobject_seek(lobjectObject *self, long pos, int whence);
|
||||
RAISES_NEG HIDDEN long lobject_tell(lobjectObject *self);
|
||||
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
|
||||
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);
|
||||
|
||||
|
|
|
@ -376,21 +376,29 @@ lobject_read(lobjectObject *self, char *buf, size_t len)
|
|||
|
||||
/* lobject_seek - move the current position in the lo */
|
||||
|
||||
RAISES_NEG int
|
||||
lobject_seek(lobjectObject *self, int pos, int whence)
|
||||
RAISES_NEG long
|
||||
lobject_seek(lobjectObject *self, long pos, int whence)
|
||||
{
|
||||
PGresult *pgres = NULL;
|
||||
char *error = NULL;
|
||||
int where;
|
||||
long where;
|
||||
|
||||
Dprintf("lobject_seek: fd = %d, pos = %d, whence = %d",
|
||||
Dprintf("lobject_seek: fd = %d, pos = %ld, whence = %d",
|
||||
self->fd, pos, whence);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
where = lo_lseek(self->conn->pgconn, self->fd, pos, whence);
|
||||
Dprintf("lobject_seek: where = %d", where);
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
} else {
|
||||
where = lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
|
||||
}
|
||||
#else
|
||||
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
#endif
|
||||
Dprintf("lobject_seek: where = %ld", where);
|
||||
if (where < 0)
|
||||
collect_error(self->conn, &error);
|
||||
|
||||
|
@ -404,20 +412,28 @@ lobject_seek(lobjectObject *self, int pos, int whence)
|
|||
|
||||
/* lobject_tell - tell the current position in the lo */
|
||||
|
||||
RAISES_NEG int
|
||||
RAISES_NEG long
|
||||
lobject_tell(lobjectObject *self)
|
||||
{
|
||||
PGresult *pgres = NULL;
|
||||
char *error = NULL;
|
||||
int where;
|
||||
long where;
|
||||
|
||||
Dprintf("lobject_tell: fd = %d", self->fd);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
where = lo_tell(self->conn->pgconn, self->fd);
|
||||
Dprintf("lobject_tell: where = %d", where);
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
where = (long)lo_tell(self->conn->pgconn, self->fd);
|
||||
} else {
|
||||
where = lo_tell64(self->conn->pgconn, self->fd);
|
||||
}
|
||||
#else
|
||||
where = (long)lo_tell(self->conn->pgconn, self->fd);
|
||||
#endif
|
||||
Dprintf("lobject_tell: where = %ld", where);
|
||||
if (where < 0)
|
||||
collect_error(self->conn, &error);
|
||||
|
||||
|
@ -473,7 +489,15 @@ lobject_truncate(lobjectObject *self, size_t len)
|
|||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
retvalue = lo_truncate(self->conn->pgconn, self->fd, len);
|
||||
} else {
|
||||
retvalue = lo_truncate64(self->conn->pgconn, self->fd, len);
|
||||
}
|
||||
#else
|
||||
retvalue = lo_truncate(self->conn->pgconn, self->fd, len);
|
||||
#endif
|
||||
Dprintf("lobject_truncate: result = %d", retvalue);
|
||||
if (retvalue < 0)
|
||||
collect_error(self->conn, &error);
|
||||
|
|
|
@ -121,7 +121,7 @@ static PyObject *
|
|||
psyco_lobj_read(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res;
|
||||
int where, end;
|
||||
long where, end;
|
||||
Py_ssize_t size = -1;
|
||||
char *buffer;
|
||||
|
||||
|
@ -165,20 +165,39 @@ psyco_lobj_read(lobjectObject *self, PyObject *args)
|
|||
static PyObject *
|
||||
psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
int offset, whence=0;
|
||||
int pos=0;
|
||||
long offset, pos=0;
|
||||
int whence=0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i|i", &offset, &whence))
|
||||
if (!PyArg_ParseTuple(args, "l|i", &offset, &whence))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if ((offset < INT_MIN || offset > INT_MAX)
|
||||
&& self->conn->server_version < 90300) {
|
||||
PyErr_Format(NotSupportedError,
|
||||
"offset out of range (%ld): server version %d "
|
||||
"does not support the lobject 64 API",
|
||||
offset, self->conn->server_version);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (offset < INT_MIN || offset > INT_MAX) {
|
||||
PyErr_Format(InterfaceError,
|
||||
"offset out of range (%ld): this psycopg version was not built "
|
||||
"with lobject 64 API support",
|
||||
offset);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((pos = lobject_seek(self, offset, whence)) < 0)
|
||||
return NULL;
|
||||
|
||||
return PyInt_FromLong((long)pos);
|
||||
return PyLong_FromLong(pos);
|
||||
}
|
||||
|
||||
/* tell method - tell current position in the lobject */
|
||||
|
@ -189,7 +208,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
|||
static PyObject *
|
||||
psyco_lobj_tell(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
int pos;
|
||||
long pos;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
|
@ -198,7 +217,7 @@ psyco_lobj_tell(lobjectObject *self, PyObject *args)
|
|||
if ((pos = lobject_tell(self)) < 0)
|
||||
return NULL;
|
||||
|
||||
return PyInt_FromLong((long)pos);
|
||||
return PyLong_FromLong(pos);
|
||||
}
|
||||
|
||||
/* unlink method - unlink (destroy) the lobject */
|
||||
|
@ -255,15 +274,33 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
|
|||
static PyObject *
|
||||
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
int len = 0;
|
||||
long len = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|i", &len))
|
||||
if (!PyArg_ParseTuple(args, "|l", &len))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if (len > INT_MAX && self->conn->server_version < 90300) {
|
||||
PyErr_Format(NotSupportedError,
|
||||
"len out of range (%ld): server version %d "
|
||||
"does not support the lobject 64 API",
|
||||
len, self->conn->server_version);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (len > INT_MAX) {
|
||||
PyErr_Format(InterfaceError,
|
||||
"len out of range (%ld): this psycopg version was not built "
|
||||
"with lobject 64 API support",
|
||||
len);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lobject_truncate(self, len) < 0)
|
||||
return NULL;
|
||||
|
||||
|
|
20
setup.py
20
setup.py
|
@ -407,6 +407,9 @@ class psycopg_build_ext(build_ext):
|
|||
pgmajor, pgminor, pgpatch = m.group(1, 2, 3)
|
||||
if pgpatch is None or not pgpatch.isdigit():
|
||||
pgpatch = 0
|
||||
pgmajor = int(pgmajor)
|
||||
pgminor = int(pgminor)
|
||||
pgpatch = int(pgpatch)
|
||||
else:
|
||||
sys.stderr.write(
|
||||
"Error: could not determine PostgreSQL version from '%s'"
|
||||
|
@ -414,7 +417,22 @@ class psycopg_build_ext(build_ext):
|
|||
sys.exit(1)
|
||||
|
||||
define_macros.append(("PG_VERSION_HEX", "0x%02X%02X%02X" %
|
||||
(int(pgmajor), int(pgminor), int(pgpatch))))
|
||||
(pgmajor, pgminor, pgpatch)))
|
||||
|
||||
# enable lo64 if libpq >= 9.3 and Python 64 bits
|
||||
if (pgmajor, pgminor) >= (9, 3) and sys.maxint > (1 << 32):
|
||||
define_macros.append(("HAVE_LO64", "1"))
|
||||
|
||||
# Inject the flag in the version string already packed up
|
||||
# because we didn't know the version before.
|
||||
# With distutils everything is complicated.
|
||||
for i, t in enumerate(define_macros):
|
||||
if t[0] == 'PSYCOPG_VERSION':
|
||||
n = t[1].find(')')
|
||||
if n > 0:
|
||||
define_macros[i] = (
|
||||
t[0], t[1][:n] + ' lo64' + t[1][n:])
|
||||
|
||||
except Warning:
|
||||
w = sys.exc_info()[1] # work around py 2/3 different syntax
|
||||
sys.stderr.write("Error: %s\n" % w)
|
||||
|
|
|
@ -440,6 +440,68 @@ decorate_all_tests(LargeObjectTruncateTests,
|
|||
skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate)
|
||||
|
||||
|
||||
def _has_lo64(conn):
|
||||
"""Return (bool, msg) about the lo64 support"""
|
||||
if conn.server_version < 90300:
|
||||
return (False, "server version %s doesn't support the lo64 API"
|
||||
% conn.server_version)
|
||||
|
||||
if 'lo64' not in psycopg2.__version__:
|
||||
return (False, "this psycopg build doesn't support the lo64 API")
|
||||
|
||||
return (True, "this server and build support the lo64 API")
|
||||
|
||||
def skip_if_no_lo64(f):
|
||||
@wraps(f)
|
||||
def skip_if_no_lo64_(self):
|
||||
lo64, msg = _has_lo64(self.conn)
|
||||
if not lo64: return self.skipTest(msg)
|
||||
else: return f(self)
|
||||
|
||||
return skip_if_no_lo64_
|
||||
|
||||
class LargeObject64Tests(LargeObjectTestCase):
|
||||
def test_seek_tell_truncate_greater_than_2gb(self):
|
||||
lo = self.conn.lobject()
|
||||
|
||||
length = (1 << 31) + (1 << 30) # 2gb + 1gb = 3gb
|
||||
lo.truncate(length)
|
||||
|
||||
self.assertEqual(lo.seek(length, 0), length)
|
||||
self.assertEqual(lo.tell(), length)
|
||||
|
||||
decorate_all_tests(LargeObject64Tests,
|
||||
skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate, skip_if_no_lo64)
|
||||
|
||||
|
||||
def skip_if_lo64(f):
|
||||
@wraps(f)
|
||||
def skip_if_lo64_(self):
|
||||
lo64, msg = _has_lo64(self.conn)
|
||||
if lo64: return self.skipTest(msg)
|
||||
else: return f(self)
|
||||
|
||||
return skip_if_lo64_
|
||||
|
||||
class LargeObjectNot64Tests(LargeObjectTestCase):
|
||||
def test_seek_larger_than_2gb(self):
|
||||
lo = self.conn.lobject()
|
||||
offset = 1 << 32 # 4gb
|
||||
self.assertRaises(
|
||||
(psycopg2.InterfaceError, psycopg2.NotSupportedError),
|
||||
lo.seek, offset, 0)
|
||||
|
||||
def test_truncate_larger_than_2gb(self):
|
||||
lo = self.conn.lobject()
|
||||
length = 1 << 32 # 4gb
|
||||
self.assertRaises(
|
||||
(psycopg2.InterfaceError, psycopg2.NotSupportedError),
|
||||
lo.truncate, length)
|
||||
|
||||
decorate_all_tests(LargeObjectNot64Tests,
|
||||
skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate, skip_if_lo64)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user