mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-25 18:33:44 +03:00
472 lines
12 KiB
C
472 lines
12 KiB
C
/* lobject_type.c - python interface to lobject objects
|
|
*
|
|
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
|
|
* Copyright (C) 2020 The Psycopg Team
|
|
*
|
|
* This file is part of psycopg.
|
|
*
|
|
* psycopg2 is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* In addition, as a special exception, the copyright holders give
|
|
* permission to link this program with the OpenSSL library (or with
|
|
* modified versions of OpenSSL that use the same license as OpenSSL),
|
|
* and distribute linked combinations including the two.
|
|
*
|
|
* You must obey the GNU Lesser General Public License in all respects for
|
|
* all of the code used other than OpenSSL.
|
|
*
|
|
* psycopg2 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 Lesser General Public
|
|
* License for more details.
|
|
*/
|
|
|
|
#define PSYCOPG_MODULE
|
|
#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 <string.h>
|
|
|
|
|
|
/** 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)
|
|
{
|
|
/* 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 (!lobject_is_closed(self)
|
|
&& !self->conn->autocommit
|
|
&& self->conn->mark == self->mark)
|
|
{
|
|
Dprintf("psyco_lobj_close: closing lobject at %p", self);
|
|
if (lobject_close(self) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
Py_RETURN_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)
|
|
{
|
|
char *buffer;
|
|
Py_ssize_t len;
|
|
Py_ssize_t res;
|
|
PyObject *obj;
|
|
PyObject *data = NULL;
|
|
PyObject *rv = NULL;
|
|
|
|
if (!PyArg_ParseTuple(args, "O", &obj)) return NULL;
|
|
|
|
EXC_IF_LOBJ_CLOSED(self);
|
|
EXC_IF_LOBJ_LEVEL0(self);
|
|
EXC_IF_LOBJ_UNMARKED(self);
|
|
|
|
if (Bytes_Check(obj)) {
|
|
Py_INCREF(obj);
|
|
data = obj;
|
|
}
|
|
else if (PyUnicode_Check(obj)) {
|
|
if (!(data = conn_encode(self->conn, obj))) { goto exit; }
|
|
}
|
|
else {
|
|
PyErr_Format(PyExc_TypeError,
|
|
"lobject.write requires a string; got %s instead",
|
|
Py_TYPE(obj)->tp_name);
|
|
goto exit;
|
|
}
|
|
|
|
if (-1 == Bytes_AsStringAndSize(data, &buffer, &len)) {
|
|
goto exit;
|
|
}
|
|
|
|
if (0 > (res = lobject_write(self, buffer, (size_t)len))) {
|
|
goto exit;
|
|
}
|
|
|
|
rv = PyInt_FromSsize_t((Py_ssize_t)res);
|
|
|
|
exit:
|
|
Py_XDECREF(data);
|
|
return rv;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
PyObject *res;
|
|
Py_ssize_t where, end;
|
|
Py_ssize_t size = -1;
|
|
char *buffer;
|
|
|
|
if (!PyArg_ParseTuple(args, "|n", &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) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
if ((size = lobject_read(self, buffer, size)) < 0) {
|
|
PyMem_Free(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
if (self->mode & LOBJECT_BINARY) {
|
|
res = Bytes_FromStringAndSize(buffer, size);
|
|
} else {
|
|
res = conn_decode(self->conn, buffer, size);
|
|
}
|
|
PyMem_Free(buffer);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
Py_ssize_t offset, pos=0;
|
|
int whence=0;
|
|
|
|
if (!PyArg_ParseTuple(args, "n|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 (" FORMAT_CODE_PY_SSIZE_T "): "
|
|
"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_FromSsize_t(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)
|
|
{
|
|
Py_ssize_t pos;
|
|
|
|
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_FromSsize_t(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 (lobject_unlink(self) < 0)
|
|
return NULL;
|
|
|
|
Py_RETURN_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)
|
|
{
|
|
const char *filename;
|
|
|
|
if (!PyArg_ParseTuple(args, "s", &filename))
|
|
return NULL;
|
|
|
|
EXC_IF_LOBJ_LEVEL0(self);
|
|
|
|
if (lobject_export(self, filename) < 0)
|
|
return NULL;
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
psyco_lobj_get_closed(lobjectObject *self, void *closure)
|
|
{
|
|
return PyBool_FromLong(lobject_is_closed(self));
|
|
}
|
|
|
|
#define psyco_lobj_truncate_doc \
|
|
"truncate(len=0) -- Truncate large object to given size."
|
|
|
|
static PyObject *
|
|
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
|
|
{
|
|
Py_ssize_t len = 0;
|
|
|
|
if (!PyArg_ParseTuple(args, "|n", &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 (" FORMAT_CODE_PY_SSIZE_T "): "
|
|
"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 (" FORMAT_CODE_PY_SSIZE_T "): "
|
|
"this psycopg version was not built with lobject 64 API support",
|
|
len);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
if (lobject_truncate(self, len) < 0)
|
|
return NULL;
|
|
|
|
Py_RETURN_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_NOARGS, psyco_lobj_tell_doc},
|
|
{"close", (PyCFunction)psyco_lobj_close,
|
|
METH_NOARGS, psyco_lobj_close_doc},
|
|
{"unlink",(PyCFunction)psyco_lobj_unlink,
|
|
METH_NOARGS, psyco_lobj_unlink_doc},
|
|
{"export",(PyCFunction)psyco_lobj_export,
|
|
METH_VARARGS, psyco_lobj_export_doc},
|
|
{"truncate",(PyCFunction)psyco_lobj_truncate,
|
|
METH_VARARGS, psyco_lobj_truncate_doc},
|
|
{NULL}
|
|
};
|
|
|
|
/* object member list */
|
|
|
|
static struct PyMemberDef lobjectObject_members[] = {
|
|
{"oid", T_OID, offsetof(lobjectObject, oid), READONLY,
|
|
"The backend OID associated to this lobject."},
|
|
{"mode", T_STRING, offsetof(lobjectObject, smode), READONLY,
|
|
"Open mode."},
|
|
{NULL}
|
|
};
|
|
|
|
/* object getset list */
|
|
|
|
static struct PyGetSetDef lobjectObject_getsets[] = {
|
|
{"closed", (getter)psyco_lobj_get_closed, NULL,
|
|
"The if the large object is closed (no file-like methods)."},
|
|
{NULL}
|
|
};
|
|
|
|
/* initialization and finalization methods */
|
|
|
|
static int
|
|
lobject_setup(lobjectObject *self, connectionObject *conn,
|
|
Oid oid, const char *smode, Oid new_oid, const char *new_file)
|
|
{
|
|
Dprintf("lobject_setup: init lobject object at %p", self);
|
|
|
|
if (conn->autocommit) {
|
|
psyco_set_error(ProgrammingError, NULL,
|
|
"can't use a lobject outside of transactions");
|
|
return -1;
|
|
}
|
|
|
|
Py_INCREF((PyObject*)conn);
|
|
self->conn = conn;
|
|
self->mark = conn->mark;
|
|
|
|
self->fd = -1;
|
|
self->oid = InvalidOid;
|
|
|
|
if (0 != lobject_open(self, conn, oid, smode, new_oid, new_file))
|
|
return -1;
|
|
|
|
Dprintf("lobject_setup: good lobject object at %p, refcnt = "
|
|
FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self));
|
|
Dprintf("lobject_setup: oid = %u, fd = %d", self->oid, self->fd);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
lobject_dealloc(PyObject* obj)
|
|
{
|
|
lobjectObject *self = (lobjectObject *)obj;
|
|
|
|
if (self->conn && self->fd != -1) {
|
|
if (lobject_close(self) < 0)
|
|
PyErr_Print();
|
|
}
|
|
Py_CLEAR(self->conn);
|
|
PyMem_Free(self->smode);
|
|
|
|
Dprintf("lobject_dealloc: deleted lobject object at %p, refcnt = "
|
|
FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj));
|
|
|
|
Py_TYPE(obj)->tp_free(obj);
|
|
}
|
|
|
|
static int
|
|
lobject_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
|
{
|
|
Oid oid = InvalidOid, new_oid = InvalidOid;
|
|
const char *smode = NULL;
|
|
const char *new_file = NULL;
|
|
PyObject *conn = NULL;
|
|
|
|
if (!PyArg_ParseTuple(args, "O!|IzIz",
|
|
&connectionType, &conn,
|
|
&oid, &smode, &new_oid, &new_file))
|
|
return -1;
|
|
|
|
if (!smode)
|
|
smode = "";
|
|
|
|
return lobject_setup((lobjectObject *)obj,
|
|
(connectionObject *)conn, oid, smode, new_oid, new_file);
|
|
}
|
|
|
|
static PyObject *
|
|
lobject_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
{
|
|
return type->tp_alloc(type, 0);
|
|
}
|
|
|
|
static PyObject *
|
|
lobject_repr(lobjectObject *self)
|
|
{
|
|
return PyString_FromFormat(
|
|
"<lobject object at %p; closed: %d>", self, lobject_is_closed(self));
|
|
}
|
|
|
|
|
|
/* object type */
|
|
|
|
#define lobjectType_doc \
|
|
"A database large object."
|
|
|
|
PyTypeObject lobjectType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
"psycopg2.extensions.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*/
|
|
lobjectObject_methods, /*tp_methods*/
|
|
lobjectObject_members, /*tp_members*/
|
|
lobjectObject_getsets, /*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*/
|
|
lobject_new, /*tp_new*/
|
|
};
|