psycopg2/psycopg/adapter_binary.c
2007-11-09 13:00:37 +00:00

415 lines
11 KiB
C

/* 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.
*/
#define PY_SSIZE_T_CLEAN
#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_binary.h"
#include "psycopg/microprotocols_proto.h"
/** the quoting code */
#ifndef PSYCOPG_OWN_QUOTING
static unsigned char *
binary_escape(unsigned char *from, size_t from_length,
size_t *to_length, PGconn *conn)
{
#if PG_MAJOR_VERSION > 8 || \
(PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION > 1) || \
(PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION == 1 && PG_PATCH_VERSION >= 4)
if (conn)
return PQescapeByteaConn(conn, from, from_length, to_length);
else
#endif
return PQescapeBytea(from, from_length, to_length);
}
#else
static unsigned char *
binary_escape(unsigned char *from, size_t from_length,
size_t *to_length, PGconn *conn)
{
unsigned char *quoted, *chptr, *newptr;
size_t 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 < from_length; 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_length = 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;
Py_ssize_t buffer_len;
size_t len = 0;
PGconn *pgconn = NULL;
const char *quotes = "'%s'";
const char *scs;
/* 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);
if (self->conn) {
pgconn = ((connectionObject*)self->conn)->pgconn;
/*
* The presence of the 'standard_conforming_strings' parameters
* means that the server _accepts_ the E'' quote.
*
* If the paramer is off, the PQescapeByteaConn returns
* backslash escaped strings (e.g. '\001' -> "\\001"),
* so the E'' quotes are required to avoid warnings
* if 'escape_string_warning' is set.
*
* If the parameter is on, the PQescapeByteaConn returns
* not escaped strings (e.g. '\001' -> "\001"), relying on the
* fact that the '\' will pass untouched the string parser.
* In this case the E'' quotes are NOT to be used.
*/
scs = PQparameterStatus(pgconn, "standard_conforming_strings");
if (scs && (0 == strcmp("off", scs)))
quotes = "E'%s'";
}
to = (char *)binary_escape((unsigned char*)buffer, (size_t) buffer_len,
&len, pgconn);
if (to == NULL) {
PyErr_NoMemory();
return NULL;
}
if (len > 0)
self->buffer = PyString_FromFormat(quotes, to);
else
self->buffer = PyString_FromString("''");
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_XINCREF(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)
{
connectionObject *conn;
if (!PyArg_ParseTuple(args, "O", &conn))
return NULL;
Py_XDECREF(self->conn);
if (conn) {
self->conn = (PyObject*)conn;
Py_INCREF(self->conn);
}
Py_INCREF(Py_None);
return Py_None;
}
PyObject *
binary_conform(binaryObject *self, PyObject *args)
{
PyObject *res, *proto;
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
if (proto == (PyObject*)&isqlquoteType)
res = (PyObject*)self;
else
res = Py_None;
Py_INCREF(res);
return res;
}
/** 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) -> prepare for binary encoding using conn"},
{"__conform__", (PyCFunction)binary_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
binary_setup(binaryObject *self, PyObject *str)
{
Dprintf("binary_setup: init binary object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, ((PyObject *)self)->ob_refcnt
);
self->buffer = NULL;
self->conn = NULL;
self->wrapped = str;
Py_INCREF(self->wrapped);
Dprintf("binary_setup: good binary object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
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);
Py_XDECREF(self->conn);
Dprintf("binary_dealloc: deleted binary object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
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("<psycopg2._psycopg.Binary object at %p>", self);
}
/* object type */
#define binaryType_doc \
"Binary(buffer) -> new binary object"
PyTypeObject binaryType = {
PyObject_HEAD_INIT(NULL)
0,
"psycopg2._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*/
0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/
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);
}