first commit based on psycopg2 2.9 version

This commit is contained in:
lishifu_db
2021-07-05 21:34:17 +08:00
parent d126b6ec53
commit 3553ed0e30
178 changed files with 51253 additions and 0 deletions

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="amd64" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>

195
psycopg/adapter_asis.c Normal file
View File

@ -0,0 +1,195 @@
/* adapter_asis.c - adapt types as they are
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_asis.h"
#include "psycopg/microprotocols_proto.h"
#include <string.h>
/** the AsIs object **/
static PyObject *
asis_getquoted(asisObject *self, PyObject *args)
{
PyObject *rv;
if (self->wrapped == Py_None) {
Py_INCREF(psyco_null);
rv = psyco_null;
}
else {
rv = PyObject_Str(self->wrapped);
/* unicode to bytes */
if (rv) {
PyObject *tmp = PyUnicode_AsUTF8String(rv);
Py_DECREF(rv);
rv = tmp;
}
}
return rv;
}
static PyObject *
asis_str(asisObject *self)
{
return psyco_ensure_text(asis_getquoted(self, NULL));
}
static PyObject *
asis_conform(asisObject *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 AsIs object */
/* object member list */
static struct PyMemberDef asisObject_members[] = {
{"adapted", T_OBJECT, offsetof(asisObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef asisObject_methods[] = {
{"getquoted", (PyCFunction)asis_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"__conform__", (PyCFunction)asis_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
asis_setup(asisObject *self, PyObject *obj)
{
Dprintf("asis_setup: init asis object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("asis_setup: good asis object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
asis_dealloc(PyObject* obj)
{
asisObject *self = (asisObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("asis_dealloc: deleted asis object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
asis_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *o;
if (!PyArg_ParseTuple(args, "O", &o))
return -1;
return asis_setup((asisObject *)obj, o);
}
static PyObject *
asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
/* object type */
#define asisType_doc \
"AsIs(str) -> new AsIs adapter object"
PyTypeObject asisType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.AsIs",
sizeof(asisObject), 0,
asis_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*/
(reprfunc)asis_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
asisType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
asisObject_methods, /*tp_methods*/
asisObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
asis_init, /*tp_init*/
0, /*tp_alloc*/
asis_new, /*tp_new*/
};

48
psycopg/adapter_asis.h Normal file
View File

@ -0,0 +1,48 @@
/* adapter_asis.h - definition for the psycopg AsIs type wrapper
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_ASIS_H
#define PSYCOPG_ASIS_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject asisType;
typedef struct {
PyObject_HEAD
/* this is the real object we wrap */
PyObject *wrapped;
} asisObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_ASIS_H) */

281
psycopg/adapter_binary.c Normal file
View File

@ -0,0 +1,281 @@
/* adapter_binary.c - Binary objects
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_binary.h"
#include "psycopg/microprotocols_proto.h"
#include "psycopg/connection.h"
#include <string.h>
/** the quoting code */
static unsigned char *
binary_escape(unsigned char *from, size_t from_length,
size_t *to_length, PGconn *conn)
{
if (conn)
return PQescapeByteaConn(conn, from, from_length, to_length);
else
return PQescapeBytea(from, from_length, to_length);
}
/* binary_quote - do the quote process on plain and unicode strings */
static PyObject *
binary_quote(binaryObject *self)
{
char *to = NULL;
const char *buffer = NULL;
Py_ssize_t buffer_len;
size_t len = 0;
PyObject *rv = NULL;
Py_buffer view;
int got_view = 0;
/* Allow Binary(None) to work */
if (self->wrapped == Py_None) {
Py_INCREF(psyco_null);
rv = psyco_null;
goto exit;
}
/* if we got a plain string or a buffer we escape it and save the buffer */
if (PyObject_CheckBuffer(self->wrapped)) {
if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) {
goto exit;
}
got_view = 1;
buffer = (const char *)(view.buf);
buffer_len = view.len;
}
if (!buffer) {
goto exit;
}
/* escape and build quoted buffer */
to = (char *)binary_escape((unsigned char*)buffer, (size_t)buffer_len,
&len, self->conn ? ((connectionObject*)self->conn)->pgconn : NULL);
if (to == NULL) {
PyErr_NoMemory();
goto exit;
}
if (len > 0)
rv = Bytes_FromFormat(
(self->conn && ((connectionObject*)self->conn)->equote)
? "E'%s'::bytea" : "'%s'::bytea" , to);
else
rv = Bytes_FromString("''::bytea");
exit:
if (to) { PQfreemem(to); }
if (got_view) { PyBuffer_Release(&view); }
/* if the wrapped object is not bytes or a buffer, this is an error */
if (!rv && !PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError, "can't escape %s to binary",
Py_TYPE(self->wrapped)->tp_name);
}
return rv;
}
/* binary_str, binary_getquoted - return result of quoting */
static PyObject *
binary_getquoted(binaryObject *self, PyObject *args)
{
if (self->buffer == NULL) {
self->buffer = binary_quote(self);
}
Py_XINCREF(self->buffer);
return self->buffer;
}
static PyObject *
binary_str(binaryObject *self)
{
return psyco_ensure_text(binary_getquoted(self, NULL));
}
static PyObject *
binary_prepare(binaryObject *self, PyObject *args)
{
PyObject *conn;
if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn))
return NULL;
Py_XDECREF(self->conn);
self->conn = conn;
Py_INCREF(self->conn);
Py_RETURN_NONE;
}
static 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), READONLY},
{"buffer", T_OBJECT, offsetof(binaryObject, buffer), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef binaryObject_methods[] = {
{"getquoted", (PyCFunction)binary_getquoted, METH_NOARGS,
"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, Py_REFCNT(self)
);
self->buffer = NULL;
self->conn = NULL;
Py_INCREF(str);
self->wrapped = str;
Dprintf("binary_setup: good binary object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self));
return 0;
}
static void
binary_dealloc(PyObject* obj)
{
binaryObject *self = (binaryObject *)obj;
Py_CLEAR(self->wrapped);
Py_CLEAR(self->buffer);
Py_CLEAR(self->conn);
Dprintf("binary_dealloc: deleted binary object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->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);
}
/* object type */
#define binaryType_doc \
"Binary(buffer) -> new binary object"
PyTypeObject binaryType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Binary",
sizeof(binaryObject), 0,
binary_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*/
(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*/
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*/
binary_new, /*tp_new*/
};

48
psycopg/adapter_binary.h Normal file
View File

@ -0,0 +1,48 @@
/* adapter_binary.h - definition for the Binary type
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_BINARY_H
#define PSYCOPG_BINARY_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject binaryType;
typedef struct {
PyObject_HEAD
PyObject *wrapped;
PyObject *buffer;
PyObject *conn;
} binaryObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_BINARY_H) */

515
psycopg/adapter_datetime.c Normal file
View File

@ -0,0 +1,515 @@
/* adapter_datetime.c - python date/time objects
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_datetime.h"
#include "psycopg/microprotocols_proto.h"
#include <datetime.h>
#include <time.h>
#include <string.h>
RAISES_NEG int
adapter_datetime_init(void)
{
PyDateTime_IMPORT;
if (!PyDateTimeAPI) {
PyErr_SetString(PyExc_ImportError, "datetime initialization failed");
return -1;
}
return 0;
}
/* datetime_str, datetime_getquoted - return result of quoting */
static PyObject *
_pydatetime_string_date_time(pydatetimeObject *self)
{
PyObject *rv = NULL;
PyObject *iso = NULL;
PyObject *tz;
/* Select the right PG type to cast into. */
char *fmt = NULL;
switch (self->type) {
case PSYCO_DATETIME_TIME:
tz = PyObject_GetAttrString(self->wrapped, "tzinfo");
if (!tz) { goto error; }
fmt = (tz == Py_None) ? "'%s'::time" : "'%s'::timetz";
Py_DECREF(tz);
break;
case PSYCO_DATETIME_DATE:
fmt = "'%s'::date";
break;
case PSYCO_DATETIME_TIMESTAMP:
tz = PyObject_GetAttrString(self->wrapped, "tzinfo");
if (!tz) { goto error; }
fmt = (tz == Py_None) ? "'%s'::timestamp" : "'%s'::timestamptz";
Py_DECREF(tz);
break;
}
if (!(iso = psyco_ensure_bytes(
PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) {
goto error;
}
rv = Bytes_FromFormat(fmt, Bytes_AsString(iso));
Py_DECREF(iso);
return rv;
error:
Py_XDECREF(iso);
return rv;
}
static PyObject *
_pydatetime_string_delta(pydatetimeObject *self)
{
PyDateTime_Delta *obj = (PyDateTime_Delta*)self->wrapped;
char buffer[8];
int i;
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
for (i=0; i < 6 ; i++) {
buffer[5-i] = '0' + (a % 10);
a /= 10;
}
buffer[6] = '\0';
return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
PyDateTime_DELTA_GET_DAYS(obj),
PyDateTime_DELTA_GET_SECONDS(obj),
buffer);
}
static PyObject *
pydatetime_getquoted(pydatetimeObject *self, PyObject *args)
{
if (self->type <= PSYCO_DATETIME_TIMESTAMP) {
return _pydatetime_string_date_time(self);
}
else {
return _pydatetime_string_delta(self);
}
}
static PyObject *
pydatetime_str(pydatetimeObject *self)
{
return psyco_ensure_text(pydatetime_getquoted(self, NULL));
}
static PyObject *
pydatetime_conform(pydatetimeObject *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 DateTime wrapper object **/
/* object member list */
static struct PyMemberDef pydatetimeObject_members[] = {
{"adapted", T_OBJECT, offsetof(pydatetimeObject, wrapped), READONLY},
{"type", T_INT, offsetof(pydatetimeObject, type), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef pydatetimeObject_methods[] = {
{"getquoted", (PyCFunction)pydatetime_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL date/time"},
{"__conform__", (PyCFunction)pydatetime_conform, METH_VARARGS, NULL},
{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 = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self));
self->type = type;
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("pydatetime_setup: good pydatetime object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self));
return 0;
}
static void
pydatetime_dealloc(PyObject* obj)
{
pydatetimeObject *self = (pydatetimeObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("mpydatetime_dealloc: deleted pydatetime object at %p, "
"refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj));
Py_TYPE(obj)->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);
}
/* object type */
#define pydatetimeType_doc \
"datetime(datetime, type) -> new datetime wrapper object"
PyTypeObject pydatetimeType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.datetime",
sizeof(pydatetimeObject), 0,
pydatetime_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*/
(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*/
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*/
0, /*tp_alloc*/
pydatetime_new, /*tp_new*/
};
/** module-level functions **/
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((PyObject*)PyDateTimeAPI->DateType,
"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;
PyObject *tzinfo = NULL;
int hours, minutes=0;
double micro, second=0.0;
PyObject* obj = NULL;
if (!PyArg_ParseTuple(args, "iid|O", &hours, &minutes, &second,
&tzinfo))
return NULL;
micro = (second - floor(second)) * 1000000.0;
second = floor(second);
if (tzinfo == NULL)
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiii",
hours, minutes, (int)second, (int)round(micro));
else
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
hours, minutes, (int)second, (int)round(micro), tzinfo);
if (obj) {
res = PyObject_CallFunction((PyObject *)&pydatetimeType,
"Oi", obj, PSYCO_DATETIME_TIME);
Py_DECREF(obj);
}
return res;
}
static PyObject *
_psyco_Timestamp(int year, int month, int day,
int hour, int minute, double second, PyObject *tzinfo)
{
double micro;
PyObject *obj;
PyObject *res = NULL;
micro = (second - floor(second)) * 1000000.0;
second = floor(second);
if (tzinfo == NULL)
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateTimeType,
"iiiiiii",
year, month, day, hour, minute, (int)second,
(int)round(micro));
else
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateTimeType,
"iiiiiiiO",
year, month, day, hour, minute, (int)second,
(int)round(micro), tzinfo);
if (obj) {
res = PyObject_CallFunction((PyObject *)&pydatetimeType,
"Oi", obj, PSYCO_DATETIME_TIMESTAMP);
Py_DECREF(obj);
}
return res;
}
PyObject *
psyco_Timestamp(PyObject *self, PyObject *args)
{
PyObject *tzinfo = NULL;
int year, month, day;
int hour=0, minute=0; /* default to midnight */
double second=0.0;
if (!PyArg_ParseTuple(args, "iii|iidO", &year, &month, &day,
&hour, &minute, &second, &tzinfo))
return NULL;
return _psyco_Timestamp(year, month, day, hour, minute, second, tzinfo);
}
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)floor(ticks);
if (localtime_r(&t, &tm)) {
args = Py_BuildValue("iii", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday);
if (args) {
res = psyco_Date(self, args);
Py_DECREF(args);
}
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
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)floor(ticks);
ticks -= (double)t;
if (localtime_r(&t, &tm)) {
args = Py_BuildValue("iid", tm.tm_hour, tm.tm_min,
(double)tm.tm_sec + ticks);
if (args) {
res = psyco_Time(self, args);
Py_DECREF(args);
}
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
return res;
}
PyObject *
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
{
pydatetimeObject *wrapper = NULL;
PyObject *dt_aware = NULL;
PyObject *res = NULL;
struct tm tm;
time_t t;
double ticks;
if (!PyArg_ParseTuple(args, "d", &ticks))
return NULL;
t = (time_t)floor(ticks);
ticks -= (double)t;
if (!localtime_r(&t, &tm)) {
PyErr_SetString(InterfaceError, "failed localtime call");
goto exit;
}
/* Convert the tm to a wrapper containing a naive datetime.datetime */
if (!(wrapper = (pydatetimeObject *)_psyco_Timestamp(
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, NULL))) {
goto exit;
}
/* Localize the datetime and assign it back to the wrapper */
if (!(dt_aware = PyObject_CallMethod(
wrapper->wrapped, "astimezone", NULL))) {
goto exit;
}
Py_CLEAR(wrapper->wrapped);
wrapper->wrapped = dt_aware;
dt_aware = NULL;
/* the wrapper is ready to be returned */
res = (PyObject *)wrapper;
wrapper = NULL;
exit:
Py_XDECREF(dt_aware);
Py_XDECREF(wrapper);
return res;
}
PyObject *
psyco_DateFromPy(PyObject *self, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DateType, &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!", PyDateTimeAPI->TimeType, &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!", PyDateTimeAPI->DateTimeType, &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!", PyDateTimeAPI->DeltaType, &obj))
return NULL;
return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj,
PSYCO_DATETIME_INTERVAL);
}

107
psycopg/adapter_datetime.h Normal file
View File

@ -0,0 +1,107 @@
/* adapter_datetime.h - definition for the python date/time types
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_DATETIME_H
#define PSYCOPG_DATETIME_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN 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;
RAISES_NEG HIDDEN int adapter_datetime_init(void);
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
#define psyco_Date_doc \
"Date(year, month, day) -> new date\n\n" \
"Build an object holding a date value."
HIDDEN PyObject *psyco_Time(PyObject *module, PyObject *args);
#define psyco_Time_doc \
"Time(hour, minutes, seconds, tzinfo=None) -> new time\n\n" \
"Build an object holding a time value."
HIDDEN PyObject *psyco_Timestamp(PyObject *module, PyObject *args);
#define psyco_Timestamp_doc \
"Timestamp(year, month, day, hour, minutes, seconds, tzinfo=None) -> new timestamp\n\n" \
"Build an object holding a timestamp value."
HIDDEN PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args);
#define psyco_DateFromTicks_doc \
"DateFromTicks(ticks) -> new date\n\n" \
"Build an object holding a date value from the given ticks value.\n\n" \
"Ticks are the number of seconds since the epoch; see the documentation " \
"of the standard Python time module for details)."
HIDDEN PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args);
#define psyco_TimeFromTicks_doc \
"TimeFromTicks(ticks) -> new time\n\n" \
"Build an object holding a time value from the given ticks value.\n\n" \
"Ticks are the number of seconds since the epoch; see the documentation " \
"of the standard Python time module for details)."
HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args);
#define psyco_TimestampFromTicks_doc \
"TimestampFromTicks(ticks) -> new timestamp\n\n" \
"Build an object holding a timestamp value from the given ticks value.\n\n" \
"Ticks are the number of seconds since the epoch; see the documentation " \
"of the standard Python time module for details)."
HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args);
#define psyco_DateFromPy_doc \
"DateFromPy(datetime.date) -> new wrapper"
HIDDEN PyObject *psyco_TimeFromPy(PyObject *module, PyObject *args);
#define psyco_TimeFromPy_doc \
"TimeFromPy(datetime.time) -> new wrapper"
HIDDEN PyObject *psyco_TimestampFromPy(PyObject *module, PyObject *args);
#define psyco_TimestampFromPy_doc \
"TimestampFromPy(datetime.datetime) -> new wrapper"
HIDDEN PyObject *psyco_IntervalFromPy(PyObject *module, PyObject *args);
#define psyco_IntervalFromPy_doc \
"IntervalFromPy(datetime.timedelta) -> new wrapper"
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_DATETIME_H) */

342
psycopg/adapter_list.c Normal file
View File

@ -0,0 +1,342 @@
/* adapter_list.c - python list objects
*
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_list.h"
#include "psycopg/microprotocols.h"
#include "psycopg/microprotocols_proto.h"
/* list_str, list_getquoted - return result of quoting */
static PyObject *
list_quote(listObject *self)
{
/* adapt the list by calling adapt() recursively and then wrapping
everything into "ARRAY[]" */
PyObject *res = NULL;
PyObject **qs = NULL;
Py_ssize_t bufsize = 0;
char *buf = NULL, *ptr;
/* list consisting of only NULL don't work with the ARRAY[] construct
* so we use the {NULL,...} syntax. The same syntax is also necessary
* to convert array of arrays containing only nulls. */
int all_nulls = 1;
Py_ssize_t i, len;
len = PyList_GET_SIZE(self->wrapped);
/* empty arrays are converted to NULLs (still searching for a way to
insert an empty array in postgresql */
if (len == 0) {
/* it cannot be ARRAY[] because it would make empty lists unusable
* in any() without a cast. But we may convert it into ARRAY[] below */
res = Bytes_FromString("'{}'");
goto exit;
}
if (!(qs = PyMem_New(PyObject *, len))) {
PyErr_NoMemory();
goto exit;
}
memset(qs, 0, len * sizeof(PyObject *));
for (i = 0; i < len; i++) {
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
if (wrapped == Py_None) {
Py_INCREF(psyco_null);
qs[i] = psyco_null;
}
else {
if (!(qs[i] = microprotocol_getquoted(
wrapped, (connectionObject*)self->connection))) {
goto exit;
}
/* Lists of arrays containing only nulls are also not supported
* by the ARRAY construct so we should do some special casing */
if (PyList_Check(wrapped)) {
if (Bytes_AS_STRING(qs[i])[0] == 'A') {
all_nulls = 0;
}
else if (0 == strcmp(Bytes_AS_STRING(qs[i]), "'{}'")) {
/* case of issue #788: '{{}}' is not supported but
* array[array[]] is */
all_nulls = 0;
Py_CLEAR(qs[i]);
if (!(qs[i] = Bytes_FromString("ARRAY[]"))) {
goto exit;
}
}
}
else {
all_nulls = 0;
}
}
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
}
/* Create an array literal, usually ARRAY[...] but if the contents are
* all NULL or array of NULL we must use the '{...}' syntax
*/
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
PyErr_NoMemory();
goto exit;
}
if (!all_nulls) {
strcpy(ptr, "ARRAY[");
ptr += 6;
for (i = 0; i < len; i++) {
Py_ssize_t sl;
sl = Bytes_GET_SIZE(qs[i]);
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
ptr += sl;
*ptr++ = ',';
}
*(ptr - 1) = ']';
}
else {
*ptr++ = '\'';
*ptr++ = '{';
for (i = 0; i < len; i++) {
/* in case all the adapted things are nulls (or array of nulls),
* the quoted string is either NULL or an array of the form
* '{NULL,...}', in which case we have to strip the extra quotes */
char *s;
Py_ssize_t sl;
s = Bytes_AS_STRING(qs[i]);
sl = Bytes_GET_SIZE(qs[i]);
if (s[0] != '\'') {
memcpy(ptr, s, sl);
ptr += sl;
}
else {
memcpy(ptr, s + 1, sl - 2);
ptr += sl - 2;
}
*ptr++ = ',';
}
*(ptr - 1) = '}';
*ptr++ = '\'';
}
res = Bytes_FromStringAndSize(buf, ptr - buf);
exit:
if (qs) {
for (i = 0; i < len; i++) {
PyObject *q = qs[i];
Py_XDECREF(q);
}
PyMem_Free(qs);
}
PyMem_Free(buf);
return res;
}
static PyObject *
list_str(listObject *self)
{
return psyco_ensure_text(list_quote(self));
}
static PyObject *
list_getquoted(listObject *self, PyObject *args)
{
return list_quote(self);
}
static PyObject *
list_prepare(listObject *self, PyObject *args)
{
PyObject *conn;
if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn))
return NULL;
Py_CLEAR(self->connection);
Py_INCREF(conn);
self->connection = conn;
Py_RETURN_NONE;
}
static PyObject *
list_conform(listObject *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 DateTime wrapper object **/
/* object member list */
static struct PyMemberDef listObject_members[] = {
{"adapted", T_OBJECT, offsetof(listObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef listObject_methods[] = {
{"getquoted", (PyCFunction)list_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL date/time"},
{"prepare", (PyCFunction)list_prepare, METH_VARARGS,
"prepare(conn) -> set encoding to conn->encoding"},
{"__conform__", (PyCFunction)list_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
list_setup(listObject *self, PyObject *obj)
{
Dprintf("list_setup: init list object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
if (!PyList_Check(obj))
return -1;
self->connection = NULL;
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("list_setup: good list object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static int
list_traverse(listObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->wrapped);
Py_VISIT(self->connection);
return 0;
}
static int
list_clear(listObject *self)
{
Py_CLEAR(self->wrapped);
Py_CLEAR(self->connection);
return 0;
}
static void
list_dealloc(listObject* self)
{
PyObject_GC_UnTrack((PyObject *)self);
list_clear(self);
Dprintf("list_dealloc: deleted list object at %p, "
"refcnt = " FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self));
Py_TYPE(self)->tp_free((PyObject *)self);
}
static int
list_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *l;
if (!PyArg_ParseTuple(args, "O", &l))
return -1;
return list_setup((listObject *)obj, l);
}
static PyObject *
list_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
/* object type */
#define listType_doc \
"List(list) -> new list wrapper object"
PyTypeObject listType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.List",
sizeof(listObject), 0,
(destructor)list_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*/
(reprfunc)list_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
listType_doc, /*tp_doc*/
(traverseproc)list_traverse, /*tp_traverse*/
(inquiry)list_clear, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
listObject_methods, /*tp_methods*/
listObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
list_init, /*tp_init*/
0, /*tp_alloc*/
list_new, /*tp_new*/
};

47
psycopg/adapter_list.h Normal file
View File

@ -0,0 +1,47 @@
/* adapter_list.h - definition for the python list types
*
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_LIST_H
#define PSYCOPG_LIST_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject listType;
typedef struct {
PyObject_HEAD
PyObject *wrapped;
PyObject *connection;
} listObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_LIST_H) */

185
psycopg/adapter_pboolean.c Normal file
View File

@ -0,0 +1,185 @@
/* adapter_pboolean.c - psycopg boolean type wrapper implementation
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_pboolean.h"
#include "psycopg/microprotocols_proto.h"
#include <string.h>
/** the Boolean object **/
static PyObject *
pboolean_getquoted(pbooleanObject *self, PyObject *args)
{
if (PyObject_IsTrue(self->wrapped)) {
return Bytes_FromString("true");
}
else {
return Bytes_FromString("false");
}
}
static PyObject *
pboolean_str(pbooleanObject *self)
{
return psyco_ensure_text(pboolean_getquoted(self, NULL));
}
static PyObject *
pboolean_conform(pbooleanObject *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 Boolean object */
/* object member list */
static struct PyMemberDef pbooleanObject_members[] = {
{"adapted", T_OBJECT, offsetof(pbooleanObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef pbooleanObject_methods[] = {
{"getquoted", (PyCFunction)pboolean_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"__conform__", (PyCFunction)pboolean_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
pboolean_setup(pbooleanObject *self, PyObject *obj)
{
Dprintf("pboolean_setup: init pboolean object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("pboolean_setup: good pboolean object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
pboolean_dealloc(PyObject* obj)
{
pbooleanObject *self = (pbooleanObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("pboolean_dealloc: deleted pboolean object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->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);
}
/* object type */
#define pbooleanType_doc \
"Boolean(str) -> new Boolean adapter object"
PyTypeObject pbooleanType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Boolean",
sizeof(pbooleanObject), 0,
pboolean_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*/
(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*/
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*/
0, /*tp_alloc*/
pboolean_new, /*tp_new*/
};

View File

@ -0,0 +1,48 @@
/* adapter_pboolean.h - definition for the psycopg boolean type wrapper
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_PBOOLEAN_H
#define PSYCOPG_PBOOLEAN_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject pbooleanType;
typedef struct {
PyObject_HEAD
/* this is the real object we wrap */
PyObject *wrapped;
} pbooleanObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_PBOOLEAN_H) */

248
psycopg/adapter_pdecimal.c Normal file
View File

@ -0,0 +1,248 @@
/* adapter_pdecimal.c - psycopg Decimal type wrapper implementation
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_pdecimal.h"
#include "psycopg/microprotocols_proto.h"
#include <floatobject.h>
#include <math.h>
/** the Decimal object **/
static PyObject *
pdecimal_getquoted(pdecimalObject *self, PyObject *args)
{
PyObject *check, *res = NULL;
check = PyObject_CallMethod(self->wrapped, "is_finite", NULL);
if (check == Py_True) {
if (!(res = PyObject_Str(self->wrapped))) {
goto end;
}
goto output;
}
else if (check) {
res = Bytes_FromString("'NaN'::numeric");
goto end;
}
/* is_finite() was introduced 2.5.1 < somewhere <= 2.5.4.
* We assume we are here because we didn't find the method. */
PyErr_Clear();
if (!(check = PyObject_CallMethod(self->wrapped, "_isnan", NULL))) {
goto end;
}
if (PyObject_IsTrue(check)) {
res = Bytes_FromString("'NaN'::numeric");
goto end;
}
Py_DECREF(check);
if (!(check = PyObject_CallMethod(self->wrapped, "_isinfinity", NULL))) {
goto end;
}
if (PyObject_IsTrue(check)) {
res = Bytes_FromString("'NaN'::numeric");
goto end;
}
/* wrapped is finite */
if (!(res = PyObject_Str(self->wrapped))) {
goto end;
}
/* res may be unicode and may suffer for issue #57 */
output:
/* unicode to bytes */
{
PyObject *tmp = PyUnicode_AsUTF8String(res);
Py_DECREF(res);
if (!(res = tmp)) {
goto end;
}
}
if ('-' == Bytes_AS_STRING(res)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */
PyObject *tmp;
if (!(tmp = Bytes_FromString(" "))) {
Py_DECREF(res);
res = NULL;
goto end;
}
Bytes_ConcatAndDel(&tmp, res);
if (!(res = tmp)) {
goto end;
}
}
end:
Py_XDECREF(check);
return res;
}
static PyObject *
pdecimal_str(pdecimalObject *self)
{
return psyco_ensure_text(pdecimal_getquoted(self, NULL));
}
static PyObject *
pdecimal_conform(pdecimalObject *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 Decimal object */
/* object member list */
static struct PyMemberDef pdecimalObject_members[] = {
{"adapted", T_OBJECT, offsetof(pdecimalObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef pdecimalObject_methods[] = {
{"getquoted", (PyCFunction)pdecimal_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"__conform__", (PyCFunction)pdecimal_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
pdecimal_setup(pdecimalObject *self, PyObject *obj)
{
Dprintf("pdecimal_setup: init pdecimal object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("pdecimal_setup: good pdecimal object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
pdecimal_dealloc(PyObject* obj)
{
pdecimalObject *self = (pdecimalObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("pdecimal_dealloc: deleted pdecimal object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
pdecimal_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *o;
if (!PyArg_ParseTuple(args, "O", &o))
return -1;
return pdecimal_setup((pdecimalObject *)obj, o);
}
static PyObject *
pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
/* object type */
#define pdecimalType_doc \
"Decimal(str) -> new Decimal adapter object"
PyTypeObject pdecimalType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Decimal",
sizeof(pdecimalObject), 0,
pdecimal_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*/
(reprfunc)pdecimal_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
pdecimalType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
pdecimalObject_methods, /*tp_methods*/
pdecimalObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
pdecimal_init, /*tp_init*/
0, /*tp_alloc*/
pdecimal_new, /*tp_new*/
};

View File

@ -0,0 +1,48 @@
/* adapter_pdecimal.h - definition for the psycopg Decimal type wrapper
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_PDECIMAL_H
#define PSYCOPG_PDECIMAL_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject pdecimalType;
typedef struct {
PyObject_HEAD
/* this is the real object we wrap */
PyObject *wrapped;
} pdecimalObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_PDECIMAL_H) */

221
psycopg/adapter_pfloat.c Normal file
View File

@ -0,0 +1,221 @@
/* adapter_float.c - psycopg pfloat type wrapper implementation
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/adapter_pfloat.h"
#include "psycopg/microprotocols_proto.h"
#include <floatobject.h>
#include <math.h>
/** the Float object **/
static PyObject *
pfloat_getquoted(pfloatObject *self, PyObject *args)
{
PyObject *rv;
double n = PyFloat_AsDouble(self->wrapped);
if (isnan(n))
rv = Bytes_FromString("'NaN'::float");
else if (isinf(n)) {
if (n > 0)
rv = Bytes_FromString("'Infinity'::float");
else
rv = Bytes_FromString("'-Infinity'::float");
}
else {
if (!(rv = PyObject_Repr(self->wrapped))) {
goto exit;
}
/* unicode to bytes */
{
PyObject *tmp = PyUnicode_AsUTF8String(rv);
Py_DECREF(rv);
if (!(rv = tmp)) {
goto exit;
}
}
if ('-' == Bytes_AS_STRING(rv)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */
PyObject *tmp;
if (!(tmp = Bytes_FromString(" "))) {
Py_DECREF(rv);
rv = NULL;
goto exit;
}
Bytes_ConcatAndDel(&tmp, rv);
if (!(rv = tmp)) {
goto exit;
}
}
}
exit:
return rv;
}
static PyObject *
pfloat_str(pfloatObject *self)
{
return psyco_ensure_text(pfloat_getquoted(self, NULL));
}
static PyObject *
pfloat_conform(pfloatObject *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 Float object */
/* object member list */
static struct PyMemberDef pfloatObject_members[] = {
{"adapted", T_OBJECT, offsetof(pfloatObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef pfloatObject_methods[] = {
{"getquoted", (PyCFunction)pfloat_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"__conform__", (PyCFunction)pfloat_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
pfloat_setup(pfloatObject *self, PyObject *obj)
{
Dprintf("pfloat_setup: init pfloat object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("pfloat_setup: good pfloat object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
pfloat_dealloc(PyObject* obj)
{
pfloatObject *self = (pfloatObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("pfloat_dealloc: deleted pfloat object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
pfloat_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *o;
if (!PyArg_ParseTuple(args, "O", &o))
return -1;
return pfloat_setup((pfloatObject *)obj, o);
}
static PyObject *
pfloat_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
/* object type */
#define pfloatType_doc \
"Float(str) -> new Float adapter object"
PyTypeObject pfloatType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Float",
sizeof(pfloatObject), 0,
pfloat_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*/
(reprfunc)pfloat_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
pfloatType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
pfloatObject_methods, /*tp_methods*/
pfloatObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
pfloat_init, /*tp_init*/
0, /*tp_alloc*/
pfloat_new, /*tp_new*/
};

48
psycopg/adapter_pfloat.h Normal file
View File

@ -0,0 +1,48 @@
/* adapter_pfloat.h - definition for the psycopg float type wrapper
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_PFLOAT_H
#define PSYCOPG_PFLOAT_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject pfloatType;
typedef struct {
PyObject_HEAD
/* this is the real object we wrap */
PyObject *wrapped;
} pfloatObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_PFLOAT_H) */

222
psycopg/adapter_pint.c Normal file
View File

@ -0,0 +1,222 @@
/* adapter_int.c - psycopg pint type wrapper implementation
*
* Copyright (C) 2011-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/adapter_pint.h"
#include "psycopg/microprotocols_proto.h"
/** the Int object **/
static PyObject *
pint_getquoted(pintObject *self, PyObject *args)
{
PyObject *res = NULL;
/* Convert subclass to int to handle IntEnum and other subclasses
* whose str() is not the number. */
if (PyLong_CheckExact(self->wrapped)) {
res = PyObject_Str(self->wrapped);
} else {
PyObject *tmp;
if (!(tmp = PyObject_CallFunctionObjArgs(
(PyObject *)&PyLong_Type, self->wrapped, NULL))) {
goto exit;
}
res = PyObject_Str(tmp);
Py_DECREF(tmp);
}
if (!res) {
goto exit;
}
/* unicode to bytes */
{
PyObject *tmp = PyUnicode_AsUTF8String(res);
Py_DECREF(res);
if (!(res = tmp)) {
goto exit;
}
}
if ('-' == Bytes_AS_STRING(res)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */
PyObject *tmp;
if (!(tmp = Bytes_FromString(" "))) {
Py_DECREF(res);
res = NULL;
goto exit;
}
Bytes_ConcatAndDel(&tmp, res);
if (!(res = tmp)) {
goto exit;
}
}
exit:
return res;
}
static PyObject *
pint_str(pintObject *self)
{
return psyco_ensure_text(pint_getquoted(self, NULL));
}
static PyObject *
pint_conform(pintObject *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 int object */
/* object member list */
static struct PyMemberDef pintObject_members[] = {
{"adapted", T_OBJECT, offsetof(pintObject, wrapped), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef pintObject_methods[] = {
{"getquoted", (PyCFunction)pint_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"__conform__", (PyCFunction)pint_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
pint_setup(pintObject *self, PyObject *obj)
{
Dprintf("pint_setup: init pint object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("pint_setup: good pint object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
pint_dealloc(PyObject* obj)
{
pintObject *self = (pintObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("pint_dealloc: deleted pint object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
pint_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *o;
if (!PyArg_ParseTuple(args, "O", &o))
return -1;
return pint_setup((pintObject *)obj, o);
}
static PyObject *
pint_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
/* object type */
#define pintType_doc \
"Int(str) -> new Int adapter object"
PyTypeObject pintType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Int",
sizeof(pintObject), 0,
pint_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*/
(reprfunc)pint_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
pintType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
pintObject_methods, /*tp_methods*/
pintObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
pint_init, /*tp_init*/
0, /*tp_alloc*/
pint_new, /*tp_new*/
};

48
psycopg/adapter_pint.h Normal file
View File

@ -0,0 +1,48 @@
/* adapter_pint.h - definition for the psycopg int type wrapper
*
* Copyright (C) 2011-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_PINT_H
#define PSYCOPG_PINT_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject pintType;
typedef struct {
PyObject_HEAD
/* this is the real object we wrap */
PyObject *wrapped;
} pintObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_PINT_H) */

307
psycopg/adapter_qstring.c Normal file
View File

@ -0,0 +1,307 @@
/* adapter_qstring.c - QuotedString objects
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/connection.h"
#include "psycopg/adapter_qstring.h"
#include "psycopg/microprotocols_proto.h"
#include <string.h>
static const char *default_encoding = "latin1";
/* qstring_quote - do the quote process on plain and unicode strings */
static PyObject *
qstring_quote(qstringObject *self)
{
PyObject *str = NULL;
char *s, *buffer = NULL;
Py_ssize_t len, qlen;
const char *encoding;
PyObject *rv = NULL;
if (PyUnicode_Check(self->wrapped)) {
if (self->conn) {
if (!(str = conn_encode(self->conn, self->wrapped))) { goto exit; }
}
else {
encoding = self->encoding ? self->encoding : default_encoding;
if(!(str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL))) {
goto exit;
}
}
}
/* if the wrapped object is a binary string, we don't know how to
(re)encode it, so we pass it as-is */
else if (Bytes_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");
goto exit;
}
/* encode the string into buffer */
Bytes_AsStringAndSize(str, &s, &len);
if (!(buffer = psyco_escape_string(self->conn, s, len, NULL, &qlen))) {
goto exit;
}
if (qlen > PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_IndexError,
"PG buffer too large to fit in Python buffer.");
goto exit;
}
rv = Bytes_FromStringAndSize(buffer, qlen);
exit:
PyMem_Free(buffer);
Py_XDECREF(str);
return rv;
}
/* qstring_str, qstring_getquoted - return result of quoting */
static PyObject *
qstring_getquoted(qstringObject *self, PyObject *args)
{
if (self->buffer == NULL) {
self->buffer = qstring_quote(self);
}
Py_XINCREF(self->buffer);
return self->buffer;
}
static PyObject *
qstring_str(qstringObject *self)
{
return psyco_ensure_text(qstring_getquoted(self, NULL));
}
static PyObject *
qstring_prepare(qstringObject *self, PyObject *args)
{
PyObject *conn;
if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn))
return NULL;
Py_CLEAR(self->conn);
Py_INCREF(conn);
self->conn = (connectionObject *)conn;
Py_RETURN_NONE;
}
static PyObject *
qstring_conform(qstringObject *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;
}
static PyObject *
qstring_get_encoding(qstringObject *self)
{
if (self->conn) {
return conn_pgenc_to_pyenc(self->conn->encoding, NULL);
}
else {
return Text_FromUTF8(self->encoding ? self->encoding : default_encoding);
}
}
static int
qstring_set_encoding(qstringObject *self, PyObject *pyenc)
{
int rv = -1;
const char *tmp;
char *cenc;
/* get a C copy of the encoding (which may come from unicode) */
Py_INCREF(pyenc);
if (!(pyenc = psyco_ensure_bytes(pyenc))) { goto exit; }
if (!(tmp = Bytes_AsString(pyenc))) { goto exit; }
if (0 > psyco_strdup(&cenc, tmp, -1)) { goto exit; }
Dprintf("qstring_set_encoding: encoding set to %s", cenc);
PyMem_Free((void *)self->encoding);
self->encoding = cenc;
rv = 0;
exit:
Py_XDECREF(pyenc);
return rv;
}
/** the QuotedString object **/
/* object member list */
static struct PyMemberDef qstringObject_members[] = {
{"adapted", T_OBJECT, offsetof(qstringObject, wrapped), READONLY},
{"buffer", T_OBJECT, offsetof(qstringObject, buffer), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef qstringObject_methods[] = {
{"getquoted", (PyCFunction)qstring_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL-quoted string"},
{"prepare", (PyCFunction)qstring_prepare, METH_VARARGS,
"prepare(conn) -> set encoding to conn->encoding and store conn"},
{"__conform__", (PyCFunction)qstring_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
static PyGetSetDef qstringObject_getsets[] = {
{ "encoding",
(getter)qstring_get_encoding,
(setter)qstring_set_encoding,
"current encoding of the adapter" },
{NULL}
};
/* initialization and finalization methods */
static int
qstring_setup(qstringObject *self, PyObject *str)
{
Dprintf("qstring_setup: init qstring object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
Py_INCREF(str);
self->wrapped = str;
Dprintf("qstring_setup: good qstring object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
qstring_dealloc(PyObject* obj)
{
qstringObject *self = (qstringObject *)obj;
Py_CLEAR(self->wrapped);
Py_CLEAR(self->buffer);
Py_CLEAR(self->conn);
PyMem_Free((void *)self->encoding);
Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
qstring_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *str;
if (!PyArg_ParseTuple(args, "O", &str))
return -1;
return qstring_setup((qstringObject *)obj, str);
}
static PyObject *
qstring_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
/* object type */
#define qstringType_doc \
"QuotedString(str) -> new quoted object"
PyTypeObject qstringType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.QuotedString",
sizeof(qstringObject), 0,
qstring_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*/
(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*/
qstringObject_methods, /*tp_methods*/
qstringObject_members, /*tp_members*/
qstringObject_getsets, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
qstring_init, /*tp_init*/
0, /*tp_alloc*/
qstring_new, /*tp_new*/
};

52
psycopg/adapter_qstring.h Normal file
View File

@ -0,0 +1,52 @@
/* adapter_qstring.h - definition for the QuotedString type
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_QSTRING_H
#define PSYCOPG_QSTRING_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject qstringType;
typedef struct {
PyObject_HEAD
PyObject *wrapped;
PyObject *buffer;
connectionObject *conn;
const char *encoding;
} qstringObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_QSTRING_H) */

58
psycopg/aix_support.c Normal file
View File

@ -0,0 +1,58 @@
/* aix_support.c - emulate functions missing on AIX
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018, Joyent, Inc.
* Copyright (C) 2020-2021 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/aix_support.h"
#if defined(_AIX)
/* timeradd is missing on AIX */
#ifndef timeradd
void
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec + b->tv_sec;
c->tv_usec = a->tv_usec + b->tv_usec;
if (c->tv_usec >= 1000000) {
c->tv_usec -= 1000000;
c->tv_sec += 1;
}
}
/* timersub is missing on AIX */
void
timersub(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec - b->tv_sec;
c->tv_usec = a->tv_usec - b->tv_usec;
if (c->tv_usec < 0) {
c->tv_usec += 1000000;
c->tv_sec -= 1;
}
}
#endif /* timeradd */
#endif /* defined(_AIX)*/

48
psycopg/aix_support.h Normal file
View File

@ -0,0 +1,48 @@
/* aix_support.h - definitions for aix_support.c
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018-2019, Joyent, Inc.
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_AIX_SUPPORT_H
#define PSYCOPG_AIX_SUPPORT_H
#include "psycopg/config.h"
#ifdef _AIX
#include <sys/time.h>
#ifndef timeradd
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
#endif
#ifndef timercmp
#define timercmp(a, b, cmp) \
(((a)->tv_sec == (b)->tv_sec) ? \
((a)->tv_usec cmp (b)->tv_usec) : \
((a)->tv_sec cmp (b)->tv_sec))
#endif
#endif
#endif /* !defined(PSYCOPG_AIX_SUPPORT_H) */

309
psycopg/bytes_format.c Normal file
View File

@ -0,0 +1,309 @@
/* bytes_format.c - bytes-oriented version of PyString_Format
*
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
/* This implementation is based on the PyString_Format function available in
* Python 2.7.1. The function is altered to be used with both Python 2 strings
* and Python 3 bytes and is stripped of the support of formats different than
* 's'. Original license follows.
*
* PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
* --------------------------------------------
*
* 1. This LICENSE AGREEMENT is between the Python Software Foundation
* ("PSF"), and the Individual or Organization ("Licensee") accessing and
* otherwise using this software ("Python") in source or binary form and
* its associated documentation.
*
* 2. Subject to the terms and conditions of this License Agreement, PSF hereby
* grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
* analyze, test, perform and/or display publicly, prepare derivative works,
* distribute, and otherwise use Python alone or in any derivative version,
* provided, however, that PSF's License Agreement and PSF's notice of copyright,
* i.e., "Copyright (c) 2001-2019, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
* Python Software Foundation; All Rights Reserved" are retained in Python alone or
* in any derivative version prepared by Licensee.
*
* 3. In the event Licensee prepares a derivative work that is based on
* or incorporates Python or any part thereof, and wants to make
* the derivative work available to others as provided herein, then
* Licensee hereby agrees to include in any such work a brief summary of
* the changes made to Python.
*
* 4. PSF is making Python available to Licensee on an "AS IS"
* basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
* IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
* DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
* FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
* INFRINGE ANY THIRD PARTY RIGHTS.
*
* 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
* FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
* A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
* OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
*
* 6. This License Agreement will automatically terminate upon a material
* breach of its terms and conditions.
*
* 7. Nothing in this License Agreement shall be deemed to create any
* relationship of agency, partnership, or joint venture between PSF and
* Licensee. This License Agreement does not grant permission to use PSF
* trademarks or trade name in a trademark sense to endorse or promote
* products or services of Licensee, or any third party.
*
* 8. By copying, installing or otherwise using Python, Licensee
* agrees to be bound by the terms and conditions of this License
* Agreement.
*/
#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"
#include "pyport.h"
/* Helpers for formatstring */
BORROWED Py_LOCAL_INLINE(PyObject *)
getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx)
{
Py_ssize_t argidx = *p_argidx;
if (argidx < arglen) {
(*p_argidx)++;
if (arglen < 0)
return args;
else
return PyTuple_GetItem(args, argidx);
}
PyErr_SetString(PyExc_TypeError,
"not enough arguments for format string");
return NULL;
}
/* wrapper around _Bytes_Resize offering normal Python call semantics */
STEALS(1)
Py_LOCAL_INLINE(PyObject *)
resize_bytes(PyObject *b, Py_ssize_t newsize) {
if (0 == _Bytes_Resize(&b, newsize)) {
return b;
}
else {
return NULL;
}
}
/* fmt%(v1,v2,...) is roughly equivalent to sprintf(fmt, v1, v2, ...) */
PyObject *
Bytes_Format(PyObject *format, PyObject *args)
{
char *fmt, *res;
Py_ssize_t arglen, argidx;
Py_ssize_t reslen, rescnt, fmtcnt;
int args_owned = 0;
PyObject *result;
PyObject *dict = NULL;
if (format == NULL || !Bytes_Check(format) || args == NULL) {
PyErr_SetString(PyExc_SystemError, "bad argument to internal function");
return NULL;
}
fmt = Bytes_AS_STRING(format);
fmtcnt = Bytes_GET_SIZE(format);
reslen = rescnt = fmtcnt + 100;
result = Bytes_FromStringAndSize((char *)NULL, reslen);
if (result == NULL)
return NULL;
res = Bytes_AS_STRING(result);
if (PyTuple_Check(args)) {
arglen = PyTuple_GET_SIZE(args);
argidx = 0;
}
else {
arglen = -1;
argidx = -2;
}
if (Py_TYPE(args)->tp_as_mapping && !PyTuple_Check(args) &&
!PyObject_TypeCheck(args, &Bytes_Type))
dict = args;
while (--fmtcnt >= 0) {
if (*fmt != '%') {
if (--rescnt < 0) {
rescnt = fmtcnt + 100;
reslen += rescnt;
if (!(result = resize_bytes(result, reslen))) {
return NULL;
}
res = Bytes_AS_STRING(result) + reslen - rescnt;
--rescnt;
}
*res++ = *fmt++;
}
else {
/* Got a format specifier */
Py_ssize_t width = -1;
int c = '\0';
PyObject *v = NULL;
PyObject *temp = NULL;
char *pbuf;
Py_ssize_t len;
fmt++;
if (*fmt == '(') {
char *keystart;
Py_ssize_t keylen;
PyObject *key;
int pcount = 1;
if (dict == NULL) {
PyErr_SetString(PyExc_TypeError,
"format requires a mapping");
goto error;
}
++fmt;
--fmtcnt;
keystart = fmt;
/* Skip over balanced parentheses */
while (pcount > 0 && --fmtcnt >= 0) {
if (*fmt == ')')
--pcount;
else if (*fmt == '(')
++pcount;
fmt++;
}
keylen = fmt - keystart - 1;
if (fmtcnt < 0 || pcount > 0) {
PyErr_SetString(PyExc_ValueError,
"incomplete format key");
goto error;
}
key = Text_FromUTF8AndSize(keystart, keylen);
if (key == NULL)
goto error;
if (args_owned) {
Py_DECREF(args);
args_owned = 0;
}
args = PyObject_GetItem(dict, key);
Py_DECREF(key);
if (args == NULL) {
goto error;
}
args_owned = 1;
arglen = -1;
argidx = -2;
}
while (--fmtcnt >= 0) {
c = *fmt++;
break;
}
if (fmtcnt < 0) {
PyErr_SetString(PyExc_ValueError,
"incomplete format");
goto error;
}
switch (c) {
case '%':
pbuf = "%";
len = 1;
break;
case 's':
/* only bytes! */
if (!(v = getnextarg(args, arglen, &argidx)))
goto error;
if (!Bytes_CheckExact(v)) {
PyErr_Format(PyExc_ValueError,
"only bytes values expected, got %s",
Py_TYPE(v)->tp_name);
goto error;
}
temp = v;
Py_INCREF(v);
pbuf = Bytes_AS_STRING(temp);
len = Bytes_GET_SIZE(temp);
break;
default:
PyErr_Format(PyExc_ValueError,
"unsupported format character '%c' (0x%x) "
"at index " FORMAT_CODE_PY_SSIZE_T,
c, c,
(Py_ssize_t)(fmt - 1 - Bytes_AS_STRING(format)));
goto error;
}
if (width < len)
width = len;
if (rescnt < width) {
reslen -= rescnt;
rescnt = width + fmtcnt + 100;
reslen += rescnt;
if (reslen < 0) {
Py_DECREF(result);
Py_XDECREF(temp);
if (args_owned)
Py_DECREF(args);
return PyErr_NoMemory();
}
if (!(result = resize_bytes(result, reslen))) {
Py_XDECREF(temp);
if (args_owned)
Py_DECREF(args);
return NULL;
}
res = Bytes_AS_STRING(result)
+ reslen - rescnt;
}
Py_MEMCPY(res, pbuf, len);
res += len;
rescnt -= len;
while (--width >= len) {
--rescnt;
*res++ = ' ';
}
if (dict && (argidx < arglen) && c != '%') {
PyErr_SetString(PyExc_TypeError,
"not all arguments converted during string formatting");
Py_XDECREF(temp);
goto error;
}
Py_XDECREF(temp);
} /* '%' */
} /* until end */
if (argidx < arglen && !dict) {
PyErr_SetString(PyExc_TypeError,
"not all arguments converted during string formatting");
goto error;
}
if (args_owned) {
Py_DECREF(args);
}
if (!(result = resize_bytes(result, reslen - rescnt))) {
return NULL;
}
return result;
error:
Py_DECREF(result);
if (args_owned) {
Py_DECREF(args);
}
return NULL;
}

49
psycopg/column.h Normal file
View File

@ -0,0 +1,49 @@
/* column.h - definition for a column in cursor.description type
*
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_COLUMN_H
#define PSYCOPG_COLUMN_H 1
extern HIDDEN PyTypeObject columnType;
typedef struct {
PyObject_HEAD
PyObject *name;
PyObject *type_code;
PyObject *display_size;
PyObject *internal_size;
PyObject *precision;
PyObject *scale;
PyObject *null_ok;
/* Extensions to the DBAPI */
PyObject *table_oid;
PyObject *table_column;
} columnObject;
#endif /* PSYCOPG_COLUMN_H */

420
psycopg/column_type.c Normal file
View File

@ -0,0 +1,420 @@
/* column_type.c - python interface to cursor.description objects
*
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/column.h"
static const char column_doc[] =
"Description of a column returned by a query.\n\n"
"The DBAPI demands this object to be a 7-items sequence. This object\n"
"respects this interface, but adds names for the exposed attributes\n"
"and adds attribute not requested by the DBAPI.";
static const char name_doc[] =
"The name of the column returned.";
static const char type_code_doc[] =
"The PostgreSQL OID of the column.\n\n"
"You can use the pg_type system table to get more informations about the\n"
"type. This is the value used by Psycopg to decide what Python type use\n"
"to represent the value";
static const char display_size_doc[] =
"The actual length of the column in bytes.\n\n"
"Obtaining this value is computationally intensive, so it is always None";
static const char internal_size_doc[] =
"The size in bytes of the column associated to this column on the server.\n\n"
"Set to a negative value for variable-size types.";
static const char precision_doc[] =
"Total number of significant digits in columns of type NUMERIC.\n\n"
"None for other types.";
static const char scale_doc[] =
"Count of decimal digits in the fractional part in columns of type NUMERIC.\n\n"
"None for other types.";
static const char null_ok_doc[] =
"Always none.";
static const char table_oid_doc[] =
"The OID of the table from which the column was fetched.\n\n"
"None if not available";
static const char table_column_doc[] =
"The number (within its table) of the column making up the result\n\n"
"None if not available. Note that PostgreSQL column numbers start at 1";
static PyMemberDef column_members[] = {
{ "name", T_OBJECT, offsetof(columnObject, name), READONLY, (char *)name_doc },
{ "type_code", T_OBJECT, offsetof(columnObject, type_code), READONLY, (char *)type_code_doc },
{ "display_size", T_OBJECT, offsetof(columnObject, display_size), READONLY, (char *)display_size_doc },
{ "internal_size", T_OBJECT, offsetof(columnObject, internal_size), READONLY, (char *)internal_size_doc },
{ "precision", T_OBJECT, offsetof(columnObject, precision), READONLY, (char *)precision_doc },
{ "scale", T_OBJECT, offsetof(columnObject, scale), READONLY, (char *)scale_doc },
{ "null_ok", T_OBJECT, offsetof(columnObject, null_ok), READONLY, (char *)null_ok_doc },
{ "table_oid", T_OBJECT, offsetof(columnObject, table_oid), READONLY, (char *)table_oid_doc },
{ "table_column", T_OBJECT, offsetof(columnObject, table_column), READONLY, (char *)table_column_doc },
{ NULL }
};
static PyObject *
column_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
return type->tp_alloc(type, 0);
}
static int
column_init(columnObject *self, PyObject *args, PyObject *kwargs)
{
PyObject *name = NULL;
PyObject *type_code = NULL;
PyObject *display_size = NULL;
PyObject *internal_size = NULL;
PyObject *precision = NULL;
PyObject *scale = NULL;
PyObject *null_ok = NULL;
PyObject *table_oid = NULL;
PyObject *table_column = NULL;
static char *kwlist[] = {
"name", "type_code", "display_size", "internal_size",
"precision", "scale", "null_ok", "table_oid", "table_column", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOOOO", kwlist,
&name, &type_code, &display_size, &internal_size, &precision,
&scale, &null_ok, &table_oid, &table_column)) {
return -1;
}
Py_XINCREF(name); self->name = name;
Py_XINCREF(type_code); self->type_code = type_code;
Py_XINCREF(display_size); self->display_size = display_size;
Py_XINCREF(internal_size); self->internal_size = internal_size;
Py_XINCREF(precision); self->precision = precision;
Py_XINCREF(scale); self->scale = scale;
Py_XINCREF(null_ok); self->null_ok = null_ok;
Py_XINCREF(table_oid); self->table_oid = table_oid;
Py_XINCREF(table_column); self->table_column = table_column;
return 0;
}
static void
column_dealloc(columnObject *self)
{
Py_CLEAR(self->name);
Py_CLEAR(self->type_code);
Py_CLEAR(self->display_size);
Py_CLEAR(self->internal_size);
Py_CLEAR(self->precision);
Py_CLEAR(self->scale);
Py_CLEAR(self->null_ok);
Py_CLEAR(self->table_oid);
Py_CLEAR(self->table_column);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject*
column_repr(columnObject *self)
{
PyObject *rv = NULL;
PyObject *format = NULL;
PyObject *args = NULL;
PyObject *tmp;
if (!(format = Text_FromUTF8("Column(name=%r, type_code=%r)"))) {
goto exit;
}
if (!(args = PyTuple_New(2))) { goto exit; }
tmp = self->name ? self->name : Py_None;
Py_INCREF(tmp);
PyTuple_SET_ITEM(args, 0, tmp);
tmp = self->type_code ? self->type_code : Py_None;
Py_INCREF(tmp);
PyTuple_SET_ITEM(args, 1, tmp);
rv = Text_Format(format, args);
exit:
Py_XDECREF(args);
Py_XDECREF(format);
return rv;
}
static PyObject *
column_richcompare(columnObject *self, PyObject *other, int op)
{
PyObject *rv = NULL;
PyObject *tself = NULL;
if (!(tself = PyObject_CallFunctionObjArgs(
(PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) {
goto exit;
}
rv = PyObject_RichCompare(tself, other, op);
exit:
Py_XDECREF(tself);
return rv;
}
/* column description can be accessed as a 7 items tuple for DBAPI compatibility */
static Py_ssize_t
column_len(columnObject *self)
{
return 7;
}
static PyObject *
column_getitem(columnObject *self, Py_ssize_t item)
{
PyObject *rv = NULL;
if (item < 0)
item += 7;
switch (item) {
case 0:
rv = self->name;
break;
case 1:
rv = self->type_code;
break;
case 2:
rv = self->display_size;
break;
case 3:
rv = self->internal_size;
break;
case 4:
rv = self->precision;
break;
case 5:
rv = self->scale;
break;
case 6:
rv = self->null_ok;
break;
default:
PyErr_SetString(PyExc_IndexError, "index out of range");
return NULL;
}
if (!rv) {
rv = Py_None;
}
Py_INCREF(rv);
return rv;
}
static PyObject*
column_subscript(columnObject* self, PyObject* item)
{
PyObject *t = NULL;
PyObject *rv = NULL;
/* t = tuple(self) */
if (!(t = PyObject_CallFunctionObjArgs(
(PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) {
goto exit;
}
/* rv = t[item] */
rv = PyObject_GetItem(t, item);
exit:
Py_XDECREF(t);
return rv;
}
static PyMappingMethods column_mapping = {
(lenfunc)column_len, /* mp_length */
(binaryfunc)column_subscript, /* mp_subscript */
0 /* mp_ass_subscript */
};
static PySequenceMethods column_sequence = {
(lenfunc)column_len, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
(ssizeargfunc)column_getitem, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
0, /* sq_contains */
0, /* sq_inplace_concat */
0, /* sq_inplace_repeat */
};
static PyObject *
column_getstate(columnObject *self, PyObject *dummy)
{
return PyObject_CallFunctionObjArgs(
(PyObject *)&PyTuple_Type, (PyObject *)self, NULL);
}
PyObject *
column_setstate(columnObject *self, PyObject *state)
{
Py_ssize_t size;
PyObject *rv = NULL;
if (state == Py_None) {
goto exit;
}
if (!PyTuple_Check(state)) {
PyErr_SetString(PyExc_TypeError, "state is not a tuple");
goto error;
}
size = PyTuple_GET_SIZE(state);
if (size > 0) {
Py_CLEAR(self->name);
self->name = PyTuple_GET_ITEM(state, 0);
Py_INCREF(self->name);
}
if (size > 1) {
Py_CLEAR(self->type_code);
self->type_code = PyTuple_GET_ITEM(state, 1);
Py_INCREF(self->type_code);
}
if (size > 2) {
Py_CLEAR(self->display_size);
self->display_size = PyTuple_GET_ITEM(state, 2);
Py_INCREF(self->display_size);
}
if (size > 3) {
Py_CLEAR(self->internal_size);
self->internal_size = PyTuple_GET_ITEM(state, 3);
Py_INCREF(self->internal_size);
}
if (size > 4) {
Py_CLEAR(self->precision);
self->precision = PyTuple_GET_ITEM(state, 4);
Py_INCREF(self->precision);
}
if (size > 5) {
Py_CLEAR(self->scale);
self->scale = PyTuple_GET_ITEM(state, 5);
Py_INCREF(self->scale);
}
if (size > 6) {
Py_CLEAR(self->null_ok);
self->null_ok = PyTuple_GET_ITEM(state, 6);
Py_INCREF(self->null_ok);
}
if (size > 7) {
Py_CLEAR(self->table_oid);
self->table_oid = PyTuple_GET_ITEM(state, 7);
Py_INCREF(self->table_oid);
}
if (size > 8) {
Py_CLEAR(self->table_column);
self->table_column = PyTuple_GET_ITEM(state, 8);
Py_INCREF(self->table_column);
}
exit:
rv = Py_None;
Py_INCREF(rv);
error:
return rv;
}
static PyMethodDef column_methods[] = {
/* Make Column picklable. */
{"__getstate__", (PyCFunction)column_getstate, METH_NOARGS },
{"__setstate__", (PyCFunction)column_setstate, METH_O },
{NULL}
};
PyTypeObject columnType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Column",
sizeof(columnObject), 0,
(destructor)column_dealloc, /* tp_dealloc */
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)column_repr, /*tp_repr*/
0, /*tp_as_number*/
&column_sequence, /*tp_as_sequence*/
&column_mapping, /*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*/
column_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
(richcmpfunc)column_richcompare, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
column_methods, /*tp_methods*/
column_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)column_init, /*tp_init*/
0, /*tp_alloc*/
column_new, /*tp_new*/
};

216
psycopg/config.h Normal file
View File

@ -0,0 +1,216 @@
/* config.h - general config and Dprintf macro
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_CONFIG_H
#define PSYCOPG_CONFIG_H 1
/* GCC 4.0 and later have support for specifying symbol visibility */
#if __GNUC__ >= 4 && !defined(__MINGW32__)
# define HIDDEN __attribute__((visibility("hidden")))
#else
# define HIDDEN
#endif
/* support for getpid() */
#if defined( __GNUC__)
#define CONN_CHECK_PID
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef _WIN32
/* Windows doesn't seem affected by bug #829: just make it compile. */
#define pid_t int
#endif
/* debug printf-like function */
#ifdef PSYCOPG_DEBUG
extern HIDDEN int psycopg_debug_enabled;
#endif
#if defined( __GNUC__) && !defined(__APPLE__)
#ifdef PSYCOPG_DEBUG
#define Dprintf(fmt, args...) \
if (!psycopg_debug_enabled) ; else \
fprintf(stderr, "[%d] " fmt "\n", (int) getpid() , ## args)
#else
#define Dprintf(fmt, args...)
#endif
#else /* !__GNUC__ or __APPLE__ */
#ifdef PSYCOPG_DEBUG
#include <stdarg.h>
#ifdef _WIN32
#include <process.h>
#define getpid _getpid
#endif
static void Dprintf(const char *fmt, ...)
{
va_list ap;
if (!psycopg_debug_enabled)
return;
printf("[%d] ", (int) getpid());
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf("\n");
}
#else
static void Dprintf(const char *fmt, ...) {}
#endif
#endif
/* pthreads work-arounds for mutilated operating systems */
#if defined(_WIN32) || defined(__BEOS__)
#ifdef _WIN32
/* A Python extension should be linked to only one C runtime: the same one as
* the Python interpreter itself. Straightforwardly using the strdup function
* causes MinGW to implicitly link to the msvcrt.dll, which is not appropriate
* for any Python version later than 2.3.
* Microsoft C runtimes for Windows 98 and later make a _strdup function
* available, which replaces the "normal" strdup. If we replace all psycopg
* calls to strdup with calls to _strdup, MinGW no longer implicitly links to
* the obsolete C runtime. */
#define strdup _strdup
#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;
}
#endif /* _WIN32 */
#ifdef __BEOS__
#include <OS.h>
#define pthread_mutex_t sem_id
#define pthread_mutex_lock(object) acquire_sem(object)
#define pthread_mutex_unlock(object) release_sem(object)
#define pthread_mutex_destroy(ref) delete_sem(*ref)
static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake)
{
*mutex = create_sem(1, "psycopg_mutex");
if (*mutex < B_OK)
return *mutex;
return 0;
}
#endif /* __BEOS__ */
#else /* pthread is available */
#include <pthread.h>
#endif
/* to work around the fact that Windows does not have a gmtime_r function, or
a proper gmtime function */
#ifdef _WIN32
#define gmtime_r(t, tm) (gmtime(t)?memcpy((tm), gmtime(t), sizeof(*(tm))):NULL)
#define localtime_r(t, tm) (localtime(t)?memcpy((tm), localtime(t), sizeof(*(tm))):NULL)
/* remove the inline keyword, since it doesn't work unless C++ file */
#define inline
/* Hmmm, MSVC <2015 doesn't have a isnan/isinf function, but has _isnan function */
#if defined (_MSC_VER)
#if !defined(isnan)
#define isnan(x) (_isnan(x))
/* The following line was hacked together from simliar code by Bjorn Reese
* in libxml2 code */
#define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \
: ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0))
#endif
#define strcasecmp(x, y) lstrcmpi(x, y)
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#endif
#include "win32_support.h"
#endif
/* what's this, we have no round function either? */
#if (defined(_WIN32) && !defined(__GNUC__)) \
|| (defined(sun) || defined(__sun__)) \
&& (defined(__SunOS_5_8) || defined(__SunOS_5_9))
/* round has been added in the standard library with MSVC 2015 */
#if _MSC_VER < 1900
static double round(double num)
{
return (num >= 0) ? floor(num + 0.5) : ceil(num - 0.5);
}
#endif
#endif
/* resolve missing isinf() function for Solaris */
#if defined (__SVR4) && defined (__sun)
#include <ieeefp.h>
#define isinf(x) (!finite((x)) && (x)==(x))
#endif
/* decorators for the gcc cpychecker plugin */
#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
#define BORROWED \
__attribute__((cpychecker_returns_borrowed_ref))
#else
#define BORROWED
#endif
#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
#define STEALS(n) \
__attribute__((cpychecker_steals_reference_to_arg(n)))
#else
#define STEALS(n)
#endif
#if defined(WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE)
#define RAISES_NEG \
__attribute__((cpychecker_negative_result_sets_exception))
#else
#define RAISES_NEG
#endif
#if defined(WITH_CPYCHECKER_SETS_EXCEPTION_ATTRIBUTE)
#define RAISES \
__attribute__((cpychecker_sets_exception))
#else
#define RAISES
#endif
#endif /* !defined(PSYCOPG_CONFIG_H) */

229
psycopg/connection.h Normal file
View File

@ -0,0 +1,229 @@
/* connection.h - definition for the psycopg connection type
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_CONNECTION_H
#define PSYCOPG_CONNECTION_H 1
#include "psycopg/xid.h"
#ifdef __cplusplus
extern "C" {
#endif
/* isolation levels */
#define ISOLATION_LEVEL_AUTOCOMMIT 0
#define ISOLATION_LEVEL_READ_UNCOMMITTED 4
#define ISOLATION_LEVEL_READ_COMMITTED 1
#define ISOLATION_LEVEL_REPEATABLE_READ 2
#define ISOLATION_LEVEL_SERIALIZABLE 3
#define ISOLATION_LEVEL_DEFAULT 5
/* 3-state values on/off/default */
#define STATE_OFF 0
#define STATE_ON 1
#define STATE_DEFAULT 2
/* connection status */
#define CONN_STATUS_SETUP 0
#define CONN_STATUS_READY 1
#define CONN_STATUS_BEGIN 2
#define CONN_STATUS_PREPARED 5
/* async connection building statuses */
#define CONN_STATUS_CONNECTING 20
#define CONN_STATUS_DATESTYLE 21
/* async query execution status */
#define ASYNC_DONE 0
#define ASYNC_READ 1
#define ASYNC_WRITE 2
/* polling result */
#define PSYCO_POLL_OK 0
#define PSYCO_POLL_READ 1
#define PSYCO_POLL_WRITE 2
#define PSYCO_POLL_ERROR 3
/* Hard limit on the notices stored by the Python connection */
#define CONN_NOTICES_LIMIT 50
/* we need the initial date style to be ISO, for typecasters; if the user
later change it, she must know what she's doing... these are the queries we
need to issue */
#define psyco_datestyle "SET DATESTYLE TO 'ISO'"
extern HIDDEN PyTypeObject connectionType;
struct connectionObject_notice {
struct connectionObject_notice *next;
char *message;
};
/* the typedef is forward-declared in psycopg.h */
struct connectionObject {
PyObject_HEAD
pthread_mutex_t lock; /* the global connection lock */
char *dsn; /* data source name */
char *error; /* temporarily stored error before raising */
char *encoding; /* current backend encoding */
long int closed; /* 1 means connection has been closed;
2 that something horrible happened */
long int mark; /* number of commits/rollbacks done so far */
int status; /* status of the connection */
xidObject *tpc_xid; /* Transaction ID in two-phase commit */
long int async; /* 1 means the connection is async */
int protocol; /* protocol version */
int server_version; /* server version */
PGconn *pgconn; /* the postgresql connection */
PGcancel *cancel; /* the cancellation structure */
/* Weakref to the object executing an asynchronous query. The object
* is a cursor for async connections, but it may be something else
* for a green connection. If NULL, the connection is idle. */
PyObject *async_cursor;
int async_status; /* asynchronous execution status */
PGresult *pgres; /* temporary result across async calls */
/* notice processing */
PyObject *notice_list;
struct connectionObject_notice *notice_pending;
struct connectionObject_notice *last_notice;
/* notifies */
PyObject *notifies;
/* per-connection typecasters */
PyObject *string_types; /* a set of typecasters for string types */
PyObject *binary_types; /* a set of typecasters for binary types */
int equote; /* use E''-style quotes for escaped strings */
PyObject *weakreflist; /* list of weak references */
int autocommit;
PyObject *cursor_factory; /* default cursor factory from cursor() */
/* Optional pointer to a decoding C function, e.g. PyUnicode_DecodeUTF8 */
PyObject *(*cdecoder)(const char *, Py_ssize_t, const char *);
/* Pointers to python encoding/decoding functions, e.g.
* codecs.getdecoder('utf8') */
PyObject *pyencoder; /* python codec encoding function */
PyObject *pydecoder; /* python codec decoding function */
/* Values for the transactions characteristics */
int isolevel;
int readonly;
int deferrable;
/* the pid this connection was created into */
pid_t procpid;
/* inside a with block */
int entered;
};
/* map isolation level values into a numeric const */
typedef struct {
char *name;
int value;
} IsolationLevel;
/* C-callable functions in connection_int.c and connection_ext.c */
HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str);
HIDDEN PyObject *conn_encode(connectionObject *self, PyObject *b);
HIDDEN PyObject *conn_decode(connectionObject *self, const char *str, Py_ssize_t len);
HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn);
HIDDEN PyObject *conn_pgenc_to_pyenc(const char *encoding, char **clean_encoding);
HIDDEN int conn_get_protocol_version(PGconn *pgconn);
HIDDEN int conn_get_server_version(PGconn *pgconn);
HIDDEN void conn_notice_process(connectionObject *self);
HIDDEN void conn_notice_clean(connectionObject *self);
HIDDEN void conn_notifies_process(connectionObject *self);
RAISES_NEG HIDDEN int conn_setup(connectionObject *self);
HIDDEN int conn_connect(connectionObject *self, const char *dsn, long int async);
HIDDEN char *conn_obscure_password(const char *dsn);
HIDDEN void conn_close(connectionObject *self);
HIDDEN void conn_close_locked(connectionObject *self);
RAISES_NEG HIDDEN int conn_commit(connectionObject *self);
RAISES_NEG HIDDEN int conn_rollback(connectionObject *self);
RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, int autocommit,
int isolevel, int readonly, int deferrable);
RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc);
HIDDEN int conn_poll(connectionObject *self);
RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid);
RAISES_NEG HIDDEN int conn_tpc_command(connectionObject *self,
const char *cmd, xidObject *xid);
HIDDEN PyObject *conn_tpc_recover(connectionObject *self);
HIDDEN void conn_set_result(connectionObject *self, PGresult *pgres);
HIDDEN void conn_set_error(connectionObject *self, const char *msg);
/* exception-raising macros */
#define EXC_IF_CONN_CLOSED(self) if ((self)->closed > 0) { \
PyErr_SetString(InterfaceError, "connection already closed"); \
return NULL; }
#define EXC_IF_CONN_ASYNC(self, cmd) if ((self)->async == 1) { \
PyErr_SetString(ProgrammingError, #cmd " cannot be used " \
"in asynchronous mode"); \
return NULL; }
#define EXC_IF_IN_TRANSACTION(self, cmd) \
if (self->status != CONN_STATUS_READY) { \
PyErr_Format(ProgrammingError, \
"%s cannot be used inside a transaction", #cmd); \
return NULL; \
}
#define EXC_IF_TPC_NOT_SUPPORTED(self) \
if ((self)->server_version < 80100) { \
PyErr_Format(NotSupportedError, \
"server version %d: " \
"two-phase transactions not supported", \
(self)->server_version); \
return NULL; \
}
#define EXC_IF_TPC_BEGIN(self, cmd) if ((self)->tpc_xid) { \
PyErr_Format(ProgrammingError, "%s cannot be used " \
"during a two-phase transaction", #cmd); \
return NULL; }
#define EXC_IF_TPC_PREPARED(self, cmd) \
if ((self)->status == CONN_STATUS_PREPARED) { \
PyErr_Format(ProgrammingError, "%s cannot be used " \
"with a prepared two-phase transaction", #cmd); \
return NULL; }
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_CONNECTION_H) */

1553
psycopg/connection_int.c Normal file

File diff suppressed because it is too large Load Diff

1517
psycopg/connection_type.c Normal file

File diff suppressed because it is too large Load Diff

41
psycopg/conninfo.h Normal file
View File

@ -0,0 +1,41 @@
/* connection.h - definition for the psycopg ConnectionInfo type
*
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_CONNINFO_H
#define PSYCOPG_CONNINFO_H 1
#include "psycopg/connection.h"
extern HIDDEN PyTypeObject connInfoType;
typedef struct {
PyObject_HEAD
connectionObject *conn;
} connInfoObject;
#endif /* PSYCOPG_CONNINFO_H */

648
psycopg/conninfo_type.c Normal file
View File

@ -0,0 +1,648 @@
/* conninfo_type.c - present information about the libpq connection
*
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/conninfo.h"
static const char connInfoType_doc[] =
"Details about the native PostgreSQL database connection.\n"
"\n"
"This class exposes several `informative functions`__ about the status\n"
"of the libpq connection.\n"
"\n"
"Objects of this class are exposed as the `connection.info` attribute.\n"
"\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html";
static const char dbname_doc[] =
"The database name of the connection.\n"
"\n"
".. seealso:: libpq docs for `PQdb()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQDB";
static PyObject *
dbname_get(connInfoObject *self)
{
const char *val;
val = PQdb(self->conn->pgconn);
if (!val) {
Py_RETURN_NONE;
}
return conn_text_from_chars(self->conn, val);
}
static const char user_doc[] =
"The user name of the connection.\n"
"\n"
".. seealso:: libpq docs for `PQuser()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQUSER";
static PyObject *
user_get(connInfoObject *self)
{
const char *val;
val = PQuser(self->conn->pgconn);
if (!val) {
Py_RETURN_NONE;
}
return conn_text_from_chars(self->conn, val);
}
static const char password_doc[] =
"The password of the connection.\n"
"\n"
".. seealso:: libpq docs for `PQpass()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQPASS";
static PyObject *
password_get(connInfoObject *self)
{
const char *val;
val = PQpass(self->conn->pgconn);
if (!val) {
Py_RETURN_NONE;
}
return conn_text_from_chars(self->conn, val);
}
static const char host_doc[] =
"The server host name of the connection.\n"
"\n"
"This can be a host name, an IP address, or a directory path if the\n"
"connection is via Unix socket. (The path case can be distinguished\n"
"because it will always be an absolute path, beginning with ``/``.)\n"
"\n"
".. seealso:: libpq docs for `PQhost()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQHOST";
static PyObject *
host_get(connInfoObject *self)
{
const char *val;
val = PQhost(self->conn->pgconn);
if (!val) {
Py_RETURN_NONE;
}
return conn_text_from_chars(self->conn, val);
}
static const char port_doc[] =
"The port of the connection.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQport()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQPORT";
static PyObject *
port_get(connInfoObject *self)
{
const char *val;
val = PQport(self->conn->pgconn);
if (!val || !val[0]) {
Py_RETURN_NONE;
}
return PyInt_FromString((char *)val, NULL, 10);
}
static const char options_doc[] =
"The command-line options passed in the connection request.\n"
"\n"
".. seealso:: libpq docs for `PQoptions()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQOPTIONS";
static PyObject *
options_get(connInfoObject *self)
{
const char *val;
val = PQoptions(self->conn->pgconn);
if (!val) {
Py_RETURN_NONE;
}
return conn_text_from_chars(self->conn, val);
}
static const char dsn_parameters_doc[] =
"The effective connection parameters.\n"
"\n"
":type: `!dict`\n"
"\n"
"The results include values which weren't explicitly set by the connection\n"
"string, such as defaults, environment variables, etc.\n"
"The *password* parameter is removed from the results.\n"
"\n"
".. seealso:: libpq docs for `PQconninfo()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/libpq-connect.html"
"#LIBPQ-PQCONNINFO";
static PyObject *
dsn_parameters_get(connInfoObject *self)
{
#if PG_VERSION_NUM >= 90300
PyObject *res = NULL;
PQconninfoOption *options = NULL;
EXC_IF_CONN_CLOSED(self->conn);
if (!(options = PQconninfo(self->conn->pgconn))) {
PyErr_NoMemory();
goto exit;
}
res = psyco_dict_from_conninfo_options(options, /* include_password = */ 0);
exit:
PQconninfoFree(options);
return res;
#else
PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3");
return NULL;
#endif
}
static const char status_doc[] =
"The status of the connection.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQstatus()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQSTATUS";
static PyObject *
status_get(connInfoObject *self)
{
ConnStatusType val;
val = PQstatus(self->conn->pgconn);
return PyInt_FromLong((long)val);
}
static const char transaction_status_doc[] =
"The current in-transaction status of the connection.\n"
"\n"
"Symbolic constants for the values are defined in the module\n"
"`psycopg2.extensions`: see :ref:`transaction-status-constants` for the\n"
"available values.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQtransactionStatus()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQTRANSACTIONSTATUS";
static PyObject *
transaction_status_get(connInfoObject *self)
{
PGTransactionStatusType val;
val = PQtransactionStatus(self->conn->pgconn);
return PyInt_FromLong((long)val);
}
static const char parameter_status_doc[] =
"Looks up a current parameter setting of the server.\n"
"\n"
":param name: The name of the parameter to return.\n"
":type name: `!str`\n"
":return: The parameter value, `!None` if the parameter is unknown.\n"
":rtype: `!str`\n"
"\n"
".. seealso:: libpq docs for `PQparameterStatus()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQPARAMETERSTATUS";
static PyObject *
parameter_status(connInfoObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {"name", NULL};
const char *name;
const char *val;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) {
return NULL;
}
val = PQparameterStatus(self->conn->pgconn, name);
if (!val) {
Py_RETURN_NONE;
}
else {
return conn_text_from_chars(self->conn, val);
}
}
static const char protocol_version_doc[] =
"The frontend/backend protocol being used.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQprotocolVersion()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQPROTOCOLVERSION";
static PyObject *
protocol_version_get(connInfoObject *self)
{
int val;
val = PQprotocolVersion(self->conn->pgconn);
return PyInt_FromLong((long)val);
}
static const char server_version_doc[] =
"Returns an integer representing the server version.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQserverVersion()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQSERVERVERSION";
static PyObject *
server_version_get(connInfoObject *self)
{
int val;
val = PQserverVersion(self->conn->pgconn);
return PyInt_FromLong((long)val);
}
static const char error_message_doc[] =
"The error message most recently generated by an operation on the connection.\n"
"\n"
"`!None` if there is no current message.\n"
"\n"
".. seealso:: libpq docs for `PQerrorMessage()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQERRORMESSAGE";
static PyObject *
error_message_get(connInfoObject *self)
{
const char *val;
val = PQerrorMessage(self->conn->pgconn);
if (!val || !val[0]) {
Py_RETURN_NONE;
}
return conn_text_from_chars(self->conn, val);
}
static const char socket_doc[] =
"The file descriptor number of the connection socket to the server.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQsocket()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQSOCKET";
static PyObject *
socket_get(connInfoObject *self)
{
int val;
val = PQsocket(self->conn->pgconn);
return PyInt_FromLong((long)val);
}
static const char backend_pid_doc[] =
"The process ID (PID) of the backend process you connected to.\n"
"\n"
":type: `!int`\n"
"\n"
".. seealso:: libpq docs for `PQbackendPID()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQBACKENDPID";
static PyObject *
backend_pid_get(connInfoObject *self)
{
int val;
val = PQbackendPID(self->conn->pgconn);
return PyInt_FromLong((long)val);
}
static const char needs_password_doc[] =
"The connection authentication method required a password, but none was available.\n"
"\n"
":type: `!bool`\n"
"\n"
".. seealso:: libpq docs for `PQconnectionNeedsPassword()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQCONNECTIONNEEDSPASSWORD";
static PyObject *
needs_password_get(connInfoObject *self)
{
return PyBool_FromLong(PQconnectionNeedsPassword(self->conn->pgconn));
}
static const char used_password_doc[] =
"The connection authentication method used a password.\n"
"\n"
":type: `!bool`\n"
"\n"
".. seealso:: libpq docs for `PQconnectionUsedPassword()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQCONNECTIONUSEDPASSWORD";
static PyObject *
used_password_get(connInfoObject *self)
{
return PyBool_FromLong(PQconnectionUsedPassword(self->conn->pgconn));
}
static const char ssl_in_use_doc[] =
"`!True` if the connection uses SSL, `!False` if not.\n"
"\n"
"Only available if psycopg was built with libpq >= 9.5; raise\n"
"`~psycopg2.NotSupportedError` otherwise.\n"
"\n"
":type: `!bool`\n"
"\n"
".. seealso:: libpq docs for `PQsslInUse()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQSSLINUSE";
static PyObject *
ssl_in_use_get(connInfoObject *self)
{
PyObject *rv = NULL;
#if PG_VERSION_NUM >= 90500
rv = PyBool_FromLong(PQsslInUse(self->conn->pgconn));
#else
PyErr_SetString(NotSupportedError,
"'ssl_in_use' not available in libpq < 9.5");
#endif
return rv;
}
static const char ssl_attribute_doc[] =
"Returns SSL-related information about the connection.\n"
"\n"
":param name: The name of the attribute to return.\n"
":type name: `!str`\n"
":return: The attribute value, `!None` if unknown.\n"
":rtype: `!str`\n"
"\n"
"Only available if psycopg was built with libpq >= 9.5; raise\n"
"`~psycopg2.NotSupportedError` otherwise.\n"
"\n"
"Valid names are available in `ssl_attribute_names`.\n"
"\n"
".. seealso:: libpq docs for `PQsslAttribute()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQSSLATTRIBUTE";
static PyObject *
ssl_attribute(connInfoObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {"name", NULL};
const char *name;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) {
return NULL;
}
#if PG_VERSION_NUM >= 90500
{
const char *val;
val = PQsslAttribute(self->conn->pgconn, name);
if (!val) {
Py_RETURN_NONE;
}
else {
return conn_text_from_chars(self->conn, val);
}
}
#else
PyErr_SetString(NotSupportedError,
"'ssl_attribute()' not available in libpq < 9.5");
return NULL;
#endif
}
static const char ssl_attribute_names_doc[] =
"The list of the SSL attribute names available.\n"
"\n"
":type: `!list` of `!str`\n"
"\n"
"Only available if psycopg was built with libpq >= 9.5; raise\n"
"`~psycopg2.NotSupportedError` otherwise.\n"
"\n"
".. seealso:: libpq docs for `PQsslAttributeNames()`__ for details.\n"
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
"#LIBPQ-PQSSLATTRIBUTENAMES";
static PyObject *
ssl_attribute_names_get(connInfoObject *self)
{
#if PG_VERSION_NUM >= 90500
const char* const* names;
int i;
PyObject *l = NULL, *s = NULL, *rv = NULL;
names = PQsslAttributeNames(self->conn->pgconn);
if (!(l = PyList_New(0))) { goto exit; }
for (i = 0; names[i]; i++) {
if (!(s = conn_text_from_chars(self->conn, names[i]))) { goto exit; }
if (0 != PyList_Append(l, s)) { goto exit; }
Py_CLEAR(s);
}
rv = l;
l = NULL;
exit:
Py_XDECREF(l);
Py_XDECREF(s);
return rv;
#else
PyErr_SetString(NotSupportedError,
"'ssl_attribute_names not available in libpq < 9.5");
return NULL;
#endif
}
static struct PyGetSetDef connInfoObject_getsets[] = {
{ "dbname", (getter)dbname_get, NULL, (char *)dbname_doc },
{ "user", (getter)user_get, NULL, (char *)user_doc },
{ "password", (getter)password_get, NULL, (char *)password_doc },
{ "host", (getter)host_get, NULL, (char *)host_doc },
{ "port", (getter)port_get, NULL, (char *)port_doc },
{ "options", (getter)options_get, NULL, (char *)options_doc },
{ "dsn_parameters", (getter)dsn_parameters_get, NULL,
(char *)dsn_parameters_doc },
{ "status", (getter)status_get, NULL, (char *)status_doc },
{ "transaction_status", (getter)transaction_status_get, NULL,
(char *)transaction_status_doc },
{ "protocol_version", (getter)protocol_version_get, NULL,
(char *)protocol_version_doc },
{ "server_version", (getter)server_version_get, NULL,
(char *)server_version_doc },
{ "error_message", (getter)error_message_get, NULL,
(char *)error_message_doc },
{ "socket", (getter)socket_get, NULL, (char *)socket_doc },
{ "backend_pid", (getter)backend_pid_get, NULL, (char *)backend_pid_doc },
{ "used_password", (getter)used_password_get, NULL,
(char *)used_password_doc },
{ "needs_password", (getter)needs_password_get, NULL,
(char *)needs_password_doc },
{ "ssl_in_use", (getter)ssl_in_use_get, NULL,
(char *)ssl_in_use_doc },
{ "ssl_attribute_names", (getter)ssl_attribute_names_get, NULL,
(char *)ssl_attribute_names_doc },
{NULL}
};
static struct PyMethodDef connInfoObject_methods[] = {
{"ssl_attribute", (PyCFunction)ssl_attribute,
METH_VARARGS|METH_KEYWORDS, ssl_attribute_doc},
{"parameter_status", (PyCFunction)parameter_status,
METH_VARARGS|METH_KEYWORDS, parameter_status_doc},
{NULL}
};
/* initialization and finalization methods */
static PyObject *
conninfo_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
static int
conninfo_init(connInfoObject *self, PyObject *args, PyObject *kwds)
{
PyObject *conn = NULL;
if (!PyArg_ParseTuple(args, "O", &conn))
return -1;
if (!PyObject_TypeCheck(conn, &connectionType)) {
PyErr_SetString(PyExc_TypeError,
"The argument must be a psycopg2 connection");
return -1;
}
Py_INCREF(conn);
self->conn = (connectionObject *)conn;
return 0;
}
static void
conninfo_dealloc(connInfoObject* self)
{
Py_CLEAR(self->conn);
Py_TYPE(self)->tp_free((PyObject *)self);
}
/* object type */
PyTypeObject connInfoType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.ConnectionInfo",
sizeof(connInfoObject), 0,
(destructor)conninfo_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*/
connInfoType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
connInfoObject_methods, /*tp_methods*/
0, /*tp_members*/
connInfoObject_getsets, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)conninfo_init, /*tp_init*/
0, /*tp_alloc*/
conninfo_new, /*tp_new*/
};

147
psycopg/cursor.h Normal file
View File

@ -0,0 +1,147 @@
/* cursor.h - definition for the psycopg cursor type
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_CURSOR_H
#define PSYCOPG_CURSOR_H 1
#include "psycopg/connection.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject cursorType;
/* the typedef is forward-declared in psycopg.h */
struct cursorObject {
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 */
int withhold:1; /* 1 if the cursor is named and uses WITH HOLD */
int scrollable; /* 1 if the cursor is named and SCROLLABLE,
0 if not scrollable
-1 if undefined (PG may decide scrollable or not)
*/
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 itersize; /* how many rows should iter(cur) fetch in named cursors */
long int row; /* the row counter for fetch*() operations */
long int mark; /* transaction marker, copied from conn */
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 *caster; /* the current typecaster object */
PyObject *copyfile; /* file-like used during COPY TO/FROM ops */
Py_ssize_t copysize; /* size of the copy buffer during COPY TO/FROM ops */
#define DEFAULT_COPYSIZE 16384
#define DEFAULT_COPYBUFF 8192
PyObject *tuple_factory; /* factory for result tuples */
PyObject *tzinfo_factory; /* factory for tzinfo objects */
PyObject *query; /* last query executed */
char *qattr; /* quoting attr, used when quoting strings */
char *notice; /* a notice from the backend */
char *name; /* this cursor name */
char *qname; /* this cursor name, quoted */
PyObject *string_types; /* a set of typecasters for string types */
PyObject *binary_types; /* a set of typecasters for binary types */
PyObject *weakreflist; /* list of weak references */
};
/* C-callable functions in cursor_int.c and cursor_type.c */
BORROWED HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid);
HIDDEN void curs_reset(cursorObject *self);
RAISES_NEG HIDDEN int curs_withhold_set(cursorObject *self, PyObject *pyvalue);
RAISES_NEG HIDDEN int curs_scrollable_set(cursorObject *self, PyObject *pyvalue);
HIDDEN PyObject *curs_validate_sql_basic(cursorObject *self, PyObject *sql);
HIDDEN void curs_set_result(cursorObject *self, PGresult *pgres);
/* exception-raising macros */
#define EXC_IF_CURS_CLOSED(self) \
do { \
if (!(self)->conn) { \
PyErr_SetString(InterfaceError, "the cursor has no connection"); \
return NULL; } \
if ((self)->closed || (self)->conn->closed) { \
PyErr_SetString(InterfaceError, "cursor already closed"); \
return NULL; } \
} while (0)
#define EXC_IF_NO_TUPLES(self) \
do \
if ((self)->notuples && (self)->name == NULL) { \
PyErr_SetString(ProgrammingError, "no results to fetch"); \
return NULL; } \
while (0)
#define EXC_IF_NO_MARK(self) \
do \
if ((self)->mark != (self)->conn->mark && (self)->withhold == 0) { \
PyErr_SetString(ProgrammingError, "named cursor isn't valid anymore"); \
return NULL; } \
while (0)
#define EXC_IF_CURS_ASYNC(self, cmd) \
do \
if ((self)->conn->async == 1) { \
PyErr_SetString(ProgrammingError, \
#cmd " cannot be used in asynchronous mode"); \
return NULL; } \
while (0)
#define EXC_IF_ASYNC_IN_PROGRESS(self, cmd) \
do \
if ((self)->conn->async_cursor != NULL) { \
PyErr_SetString(ProgrammingError, \
#cmd " cannot be used while an asynchronous query is underway"); \
return NULL; } \
while (0)
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_CURSOR_H) */

171
psycopg/cursor_int.c Normal file
View File

@ -0,0 +1,171 @@
/* cursor_int.c - code used by the cursor object
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/cursor.h"
#include "psycopg/pqpath.h"
#include "psycopg/typecast.h"
/* curs_get_cast - return the type caster for an oid.
*
* Return the most specific type caster, from cursor to connection to global.
* If no type caster is found, return the default one.
*
* Return a borrowed reference.
*/
BORROWED PyObject *
curs_get_cast(cursorObject *self, PyObject *oid)
{
PyObject *cast;
/* cursor lookup */
if (self->string_types != NULL && self->string_types != Py_None) {
cast = PyDict_GetItem(self->string_types, oid);
Dprintf("curs_get_cast: per-cursor dict: %p", cast);
if (cast) { return cast; }
}
/* connection lookup */
cast = PyDict_GetItem(self->conn->string_types, oid);
Dprintf("curs_get_cast: per-connection dict: %p", cast);
if (cast) { return cast; }
/* global lookup */
cast = PyDict_GetItem(psyco_types, oid);
Dprintf("curs_get_cast: global dict: %p", cast);
if (cast) { return cast; }
/* fallback */
return psyco_default_cast;
}
#include <string.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_CLEAR(self->description);
Py_CLEAR(self->casts);
}
/* Return 1 if `obj` is a `psycopg2.sql.Composable` instance, else 0
* Set an exception and return -1 in case of error.
*/
RAISES_NEG static int
_curs_is_composible(PyObject *obj)
{
int rv = -1;
PyObject *m = NULL;
PyObject *comp = NULL;
if (!(m = PyImport_ImportModule("psycopg2.sql"))) { goto exit; }
if (!(comp = PyObject_GetAttrString(m, "Composable"))) { goto exit; }
rv = PyObject_IsInstance(obj, comp);
exit:
Py_XDECREF(comp);
Py_XDECREF(m);
return rv;
}
/* Performs very basic validation on an incoming SQL string.
* Returns a new reference to a str instance on success; NULL on failure,
* after having set an exception.
*/
PyObject *
curs_validate_sql_basic(cursorObject *self, PyObject *sql)
{
PyObject *rv = NULL;
PyObject *comp = NULL;
int iscomp;
if (!sql || !PyObject_IsTrue(sql)) {
psyco_set_error(ProgrammingError, self,
"can't execute an empty query");
goto exit;
}
if (Bytes_Check(sql)) {
/* Necessary for ref-count symmetry with the unicode case: */
Py_INCREF(sql);
rv = sql;
}
else if (PyUnicode_Check(sql)) {
if (!(rv = conn_encode(self->conn, sql))) { goto exit; }
}
else if (0 != (iscomp = _curs_is_composible(sql))) {
if (iscomp < 0) { goto exit; }
if (!(comp = PyObject_CallMethod(sql, "as_string", "O", self->conn))) {
goto exit;
}
if (Bytes_Check(comp)) {
rv = comp;
comp = NULL;
}
else if (PyUnicode_Check(comp)) {
if (!(rv = conn_encode(self->conn, comp))) { goto exit; }
}
else {
PyErr_Format(PyExc_TypeError,
"as_string() should return a string: got %s instead",
Py_TYPE(comp)->tp_name);
goto exit;
}
}
else {
/* the is not unicode or string, raise an error */
PyErr_Format(PyExc_TypeError,
"argument 1 must be a string or unicode object: got %s instead",
Py_TYPE(sql)->tp_name);
goto exit;
}
exit:
Py_XDECREF(comp);
return rv;
}
void
curs_set_result(cursorObject *self, PGresult *pgres)
{
PQclear(self->pgres);
self->pgres = pgres;
}

2126
psycopg/cursor_type.c Normal file

File diff suppressed because it is too large Load Diff

41
psycopg/diagnostics.h Normal file
View File

@ -0,0 +1,41 @@
/* diagnostics.c - definition for the psycopg Diagnostics type
*
* Copyright (C) 2013-2019 Matthew Woodcraft <matthew@woodcraft.me.uk>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_DIAGNOSTICS_H
#define PSYCOPG_DIAGNOSTICS_H 1
#include "psycopg/error.h"
extern HIDDEN PyTypeObject diagnosticsType;
typedef struct {
PyObject_HEAD
errorObject *err; /* exception to retrieve the diagnostics from */
} diagnosticsObject;
#endif /* PSYCOPG_DIAGNOSTICS_H */

208
psycopg/diagnostics_type.c Normal file
View File

@ -0,0 +1,208 @@
/* diagnostics.c - present information from libpq error responses
*
* Copyright (C) 2013-2019 Matthew Woodcraft <matthew@woodcraft.me.uk>
* Copyright (C) 2020-2021 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/diagnostics.h"
#include "psycopg/error.h"
/* These constants are defined in src/include/postgres_ext.h but some may not
* be available with the libpq we currently support at compile time. */
/* Available from PG 9.3 */
#ifndef PG_DIAG_SCHEMA_NAME
#define PG_DIAG_SCHEMA_NAME 's'
#endif
#ifndef PG_DIAG_TABLE_NAME
#define PG_DIAG_TABLE_NAME 't'
#endif
#ifndef PG_DIAG_COLUMN_NAME
#define PG_DIAG_COLUMN_NAME 'c'
#endif
#ifndef PG_DIAG_DATATYPE_NAME
#define PG_DIAG_DATATYPE_NAME 'd'
#endif
#ifndef PG_DIAG_CONSTRAINT_NAME
#define PG_DIAG_CONSTRAINT_NAME 'n'
#endif
/* Available from PG 9.6 */
#ifndef PG_DIAG_SEVERITY_NONLOCALIZED
#define PG_DIAG_SEVERITY_NONLOCALIZED 'V'
#endif
/* Retrieve an error string from the exception's cursor.
*
* If the cursor or its result isn't available, return None.
*/
static PyObject *
diagnostics_get_field(diagnosticsObject *self, void *closure)
{
const char *errortext;
if (!self->err->pgres) {
Py_RETURN_NONE;
}
errortext = PQresultErrorField(self->err->pgres, (int)(Py_intptr_t)closure);
return error_text_from_chars(self->err, errortext);
}
/* object calculated member list */
static struct PyGetSetDef diagnosticsObject_getsets[] = {
{ "severity", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SEVERITY },
{ "severity_nonlocalized", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SEVERITY_NONLOCALIZED },
{ "sqlstate", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SQLSTATE },
{ "message_primary", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_MESSAGE_PRIMARY },
{ "message_detail", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_MESSAGE_DETAIL },
{ "message_hint", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_MESSAGE_HINT },
{ "statement_position", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_STATEMENT_POSITION },
{ "internal_position", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_INTERNAL_POSITION },
{ "internal_query", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_INTERNAL_QUERY },
{ "context", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_CONTEXT },
{ "schema_name", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SCHEMA_NAME },
{ "table_name", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_TABLE_NAME },
{ "column_name", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_COLUMN_NAME },
{ "datatype_name", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_DATATYPE_NAME },
{ "constraint_name", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_CONSTRAINT_NAME },
{ "source_file", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SOURCE_FILE },
{ "source_line", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SOURCE_LINE },
{ "source_function", (getter)diagnostics_get_field, NULL,
NULL, (void*) PG_DIAG_SOURCE_FUNCTION },
{NULL}
};
/* initialization and finalization methods */
static PyObject *
diagnostics_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
}
static int
diagnostics_init(diagnosticsObject *self, PyObject *args, PyObject *kwds)
{
PyObject *err = NULL;
if (!PyArg_ParseTuple(args, "O", &err))
return -1;
if (!PyObject_TypeCheck(err, &errorType)) {
PyErr_SetString(PyExc_TypeError,
"The argument must be a psycopg2.Error");
return -1;
}
Py_INCREF(err);
self->err = (errorObject *)err;
return 0;
}
static void
diagnostics_dealloc(diagnosticsObject* self)
{
Py_CLEAR(self->err);
Py_TYPE(self)->tp_free((PyObject *)self);
}
/* object type */
static const char diagnosticsType_doc[] =
"Details from a database error report.\n\n"
"The object is returned by the `~psycopg2.Error.diag` attribute of the\n"
"`!Error` object.\n"
"All the information available from the |PQresultErrorField|_ function\n"
"are exposed as attributes by the object, e.g. the `!severity` attribute\n"
"returns the `!PG_DIAG_SEVERITY` code. "
"Please refer to the `PostgreSQL documentation`__ for the meaning of all"
" the attributes.\n\n"
".. |PQresultErrorField| replace:: `!PQresultErrorField()`\n"
".. _PQresultErrorField: https://www.postgresql.org/docs/current/static/"
"libpq-exec.html#LIBPQ-PQRESULTERRORFIELD\n"
".. __: PQresultErrorField_\n";
PyTypeObject diagnosticsType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Diagnostics",
sizeof(diagnosticsObject), 0,
(destructor)diagnostics_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*/
diagnosticsType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
0, /*tp_members*/
diagnosticsObject_getsets, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)diagnostics_init, /*tp_init*/
0, /*tp_alloc*/
diagnostics_new, /*tp_new*/
};

46
psycopg/error.h Normal file
View File

@ -0,0 +1,46 @@
/* error.h - definition for the psycopg base Error type
*
* Copyright (C) 2013-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_ERROR_H
#define PSYCOPG_ERROR_H 1
extern HIDDEN PyTypeObject errorType;
typedef struct {
PyBaseExceptionObject exc;
PyObject *pgerror;
PyObject *pgcode;
cursorObject *cursor;
PyObject *pydecoder;
PGresult *pgres;
} errorObject;
HIDDEN PyObject *error_text_from_chars(errorObject *self, const char *str);
HIDDEN BORROWED PyObject *exception_from_sqlstate(const char *sqlstate);
HIDDEN BORROWED PyObject *base_exception_from_sqlstate(const char *sqlstate);
#endif /* PSYCOPG_ERROR_H */

376
psycopg/error_type.c Normal file
View File

@ -0,0 +1,376 @@
/* error_type.c - python interface to the Error objects
*
* Copyright (C) 2013-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/error.h"
#include "psycopg/diagnostics.h"
#include "psycopg/pqpath.h"
PyObject *
error_text_from_chars(errorObject *self, const char *str)
{
return psyco_text_from_chars_safe(str, -1, self->pydecoder);
}
/* Return the Python exception corresponding to an SQLSTATE error
* code. A list of error codes can be found at:
* https://www.postgresql.org/docs/current/static/errcodes-appendix.html
*/
BORROWED PyObject *
exception_from_sqlstate(const char *sqlstate)
{
PyObject *exc;
/* First look up an exception of the proper class */
exc = PyDict_GetItemString(sqlstate_errors, sqlstate);
if (exc) {
return exc;
}
else {
PyErr_Clear();
return base_exception_from_sqlstate(sqlstate);
}
}
BORROWED PyObject *
base_exception_from_sqlstate(const char *sqlstate)
{
switch (sqlstate[0]) {
case '0':
switch (sqlstate[1]) {
case '8': /* Class 08 - Connection Exception */
return OperationalError;
case 'A': /* Class 0A - Feature Not Supported */
return NotSupportedError;
}
break;
case '2':
switch (sqlstate[1]) {
case '0': /* Class 20 - Case Not Found */
case '1': /* Class 21 - Cardinality Violation */
return ProgrammingError;
case '2': /* Class 22 - Data Exception */
return DataError;
case '3': /* Class 23 - Integrity Constraint Violation */
return IntegrityError;
case '4': /* Class 24 - Invalid Cursor State */
case '5': /* Class 25 - Invalid Transaction State */
return InternalError;
case '6': /* Class 26 - Invalid SQL Statement Name */
case '7': /* Class 27 - Triggered Data Change Violation */
case '8': /* Class 28 - Invalid Authorization Specification */
return OperationalError;
case 'B': /* Class 2B - Dependent Privilege Descriptors Still Exist */
case 'D': /* Class 2D - Invalid Transaction Termination */
case 'F': /* Class 2F - SQL Routine Exception */
return InternalError;
}
break;
case '3':
switch (sqlstate[1]) {
case '4': /* Class 34 - Invalid Cursor Name */
return OperationalError;
case '8': /* Class 38 - External Routine Exception */
case '9': /* Class 39 - External Routine Invocation Exception */
case 'B': /* Class 3B - Savepoint Exception */
return InternalError;
case 'D': /* Class 3D - Invalid Catalog Name */
case 'F': /* Class 3F - Invalid Schema Name */
return ProgrammingError;
}
break;
case '4':
switch (sqlstate[1]) {
case '0': /* Class 40 - Transaction Rollback */
return TransactionRollbackError;
case '2': /* Class 42 - Syntax Error or Access Rule Violation */
case '4': /* Class 44 - WITH CHECK OPTION Violation */
return ProgrammingError;
}
break;
case '5':
/* Class 53 - Insufficient Resources
Class 54 - Program Limit Exceeded
Class 55 - Object Not In Prerequisite State
Class 57 - Operator Intervention
Class 58 - System Error (errors external to PostgreSQL itself) */
if (!strcmp(sqlstate, "57014"))
return QueryCanceledError;
else
return OperationalError;
case 'F': /* Class F0 - Configuration File Error */
return InternalError;
case 'H': /* Class HV - Foreign Data Wrapper Error (SQL/MED) */
return OperationalError;
case 'P': /* Class P0 - PL/pgSQL Error */
return InternalError;
case 'X': /* Class XX - Internal Error */
return InternalError;
}
/* return DatabaseError as a fallback */
return DatabaseError;
}
static const char pgerror_doc[] =
"The error message returned by the backend, if available, else None";
static const char pgcode_doc[] =
"The error code returned by the backend, if available, else None";
static const char cursor_doc[] =
"The cursor that raised the exception, if available, else None";
static const char diag_doc[] =
"A Diagnostics object to get further information about the error";
static PyMemberDef error_members[] = {
{ "pgerror", T_OBJECT, offsetof(errorObject, pgerror),
READONLY, (char *)pgerror_doc },
{ "pgcode", T_OBJECT, offsetof(errorObject, pgcode),
READONLY, (char *)pgcode_doc },
{ "cursor", T_OBJECT, offsetof(errorObject, cursor),
READONLY, (char *)cursor_doc },
{ NULL }
};
static PyObject *
error_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
return ((PyTypeObject *)PyExc_StandardError)->tp_new(
type, args, kwargs);
}
static int
error_init(errorObject *self, PyObject *args, PyObject *kwargs)
{
if (((PyTypeObject *)PyExc_StandardError)->tp_init(
(PyObject *)self, args, kwargs) < 0) {
return -1;
}
return 0;
}
static int
error_traverse(errorObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->pgerror);
Py_VISIT(self->pgcode);
Py_VISIT(self->cursor);
Py_VISIT(self->pydecoder);
return ((PyTypeObject *)PyExc_StandardError)->tp_traverse(
(PyObject *)self, visit, arg);
}
static int
error_clear(errorObject *self)
{
Py_CLEAR(self->pgerror);
Py_CLEAR(self->pgcode);
Py_CLEAR(self->cursor);
Py_CLEAR(self->pydecoder);
return ((PyTypeObject *)PyExc_StandardError)->tp_clear((PyObject *)self);
}
static void
error_dealloc(errorObject *self)
{
PyObject_GC_UnTrack((PyObject *)self);
error_clear(self);
CLEARPGRES(self->pgres);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *
error_get_diag(errorObject *self, void *closure)
{
return PyObject_CallFunctionObjArgs(
(PyObject *)&diagnosticsType, (PyObject *)self, NULL);
}
static struct PyGetSetDef error_getsets[] = {
{ "diag", (getter)error_get_diag, NULL, (char *)diag_doc },
{ NULL }
};
/* Error.__reduce__
*
* The method is required to make exceptions picklable: set the cursor
* attribute to None. Only working from Py 2.5: previous versions
* would require implementing __getstate__, and as of 2012 it's a little
* bit too late to care. */
static PyObject *
error_reduce(errorObject *self, PyObject *dummy)
{
PyObject *meth = NULL;
PyObject *tuple = NULL;
PyObject *dict = NULL;
PyObject *rv = NULL;
if (!(meth = PyObject_GetAttrString(PyExc_StandardError, "__reduce__"))) {
goto error;
}
if (!(tuple = PyObject_CallFunctionObjArgs(meth, self, NULL))) {
goto error;
}
/* tuple is (type, args): convert it to (type, args, dict)
* with our extra items in the dict.
*
* If these checks fail, we can still return a valid object. Pickle
* will likely fail downstream, but there's nothing else we can do here */
if (!PyTuple_Check(tuple)) { goto exit; }
if (2 != PyTuple_GET_SIZE(tuple)) { goto exit; }
if (!(dict = PyDict_New())) { goto error; }
if (self->pgerror) {
if (0 != PyDict_SetItemString(dict, "pgerror", self->pgerror)) {
goto error;
}
}
if (self->pgcode) {
if (0 != PyDict_SetItemString(dict, "pgcode", self->pgcode)) {
goto error;
}
}
{
PyObject *newtuple;
if (!(newtuple = PyTuple_Pack(3,
PyTuple_GET_ITEM(tuple, 0),
PyTuple_GET_ITEM(tuple, 1),
dict))) {
goto error;
}
Py_DECREF(tuple);
tuple = newtuple;
}
exit:
rv = tuple;
tuple = NULL;
error:
Py_XDECREF(dict);
Py_XDECREF(tuple);
Py_XDECREF(meth);
return rv;
}
PyObject *
error_setstate(errorObject *self, PyObject *state)
{
PyObject *rv = NULL;
/* we don't call the StandartError's setstate as it would try to load the
* dict content as attributes */
if (state == Py_None) {
goto exit;
}
if (!PyDict_Check(state)) {
PyErr_SetString(PyExc_TypeError, "state is not a dictionary");
goto error;
}
/* load the dict content in the structure */
Py_CLEAR(self->pgerror);
self->pgerror = PyDict_GetItemString(state, "pgerror");
Py_XINCREF(self->pgerror);
Py_CLEAR(self->pgcode);
self->pgcode = PyDict_GetItemString(state, "pgcode");
Py_XINCREF(self->pgcode);
Py_CLEAR(self->cursor);
/* We never expect a cursor in the state as it's not picklable.
* at most there could be a None here, coming from Psycopg < 2.5 */
exit:
rv = Py_None;
Py_INCREF(rv);
error:
return rv;
}
static PyMethodDef error_methods[] = {
/* Make Error and all its subclasses picklable. */
{"__reduce__", (PyCFunction)error_reduce, METH_NOARGS },
{"__setstate__", (PyCFunction)error_setstate, METH_O },
{NULL}
};
PyTypeObject errorType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.Error",
sizeof(errorObject), 0,
(destructor)error_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|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
Error_doc, /*tp_doc*/
(traverseproc)error_traverse, /*tp_traverse*/
(inquiry)error_clear, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
error_methods, /*tp_methods*/
error_members, /*tp_members*/
error_getsets, /*tp_getset*/
0, /*tp_base Will be set to StandardError in module init */
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)error_init, /*tp_init*/
0, /*tp_alloc*/
error_new, /*tp_new*/
};

210
psycopg/green.c Normal file
View File

@ -0,0 +1,210 @@
/* green.c - cooperation with coroutine libraries.
*
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/green.h"
#include "psycopg/connection.h"
#include "psycopg/pqpath.h"
HIDDEN PyObject *wait_callback = NULL;
static PyObject *have_wait_callback(void);
static void green_panic(connectionObject *conn);
/* Register a callback function to block waiting for data.
*
* The function is exported by the _psycopg module.
*/
PyObject *
psyco_set_wait_callback(PyObject *self, PyObject *obj)
{
Py_XDECREF(wait_callback);
if (obj != Py_None) {
wait_callback = obj;
Py_INCREF(obj);
}
else {
wait_callback = NULL;
}
Py_RETURN_NONE;
}
/* Return the currently registered wait callback function.
*
* The function is exported by the _psycopg module.
*/
PyObject *
psyco_get_wait_callback(PyObject *self, PyObject *obj)
{
PyObject *ret;
ret = wait_callback;
if (!ret) {
ret = Py_None;
}
Py_INCREF(ret);
return ret;
}
/* Return nonzero if a wait callback should be called. */
int
psyco_green()
{
return (NULL != wait_callback);
}
/* Return the wait callback if available.
*
* If not available, set a Python exception and return.
*
* The function returns a new reference: decref after use.
*/
static PyObject *
have_wait_callback()
{
PyObject *cb;
cb = wait_callback;
if (!cb) {
PyErr_SetString(OperationalError, "wait callback not available");
return NULL;
}
Py_INCREF(cb);
return cb;
}
/* Block waiting for data available in an async connection.
*
* This function assumes `wait_callback` to be available:
* raise `InterfaceError` if it is not. Use `psyco_green()` to check if
* the function is to be called.
*
* Return 0 on success, else nonzero and set a Python exception.
*/
int
psyco_wait(connectionObject *conn)
{
PyObject *rv;
PyObject *cb;
Dprintf("psyco_wait");
if (!(cb = have_wait_callback())) {
return -1;
}
rv = PyObject_CallFunctionObjArgs(cb, conn, NULL);
Py_DECREF(cb);
if (NULL != rv) {
Py_DECREF(rv);
return 0;
} else {
Dprintf("psyco_wait: error in wait callback");
return -1;
}
}
/* Replacement for PQexec using the user-provided wait function.
*
* The function should be called helding the connection lock, and
* the GIL because some Python code is expected to be called.
*
* If PGresult is NULL, there may have been either a libpq error
* or an exception raised by Python code: before raising an exception
* check if there is already one using `PyErr_Occurred()` */
PGresult *
psyco_exec_green(connectionObject *conn, const char *command)
{
PGresult *result = NULL;
/* Check that there is a single concurrently executing query */
if (conn->async_cursor) {
PyErr_SetString(ProgrammingError,
"a single async query can be executed on the same connection");
goto end;
}
/* we don't care about which cursor is executing the query, and
* it may also be that no cursor is involved at all and this is
* an internal query. So just store anything in the async_cursor,
* respecting the code expecting it to be a weakref */
if (!(conn->async_cursor = PyWeakref_NewRef((PyObject*)conn, NULL))) {
goto end;
}
/* Send the query asynchronously */
if (0 == pq_send_query(conn, command)) {
goto end;
}
/* Enter the poll loop with a write. When writing is finished the poll
implementation will set the status to ASYNC_READ without exiting the
loop. If read is finished the status is finally set to ASYNC_DONE.
*/
conn->async_status = ASYNC_WRITE;
if (0 != psyco_wait(conn)) {
green_panic(conn);
goto end;
}
/* the result is now in the connection: take its ownership */
result = conn->pgres;
conn->pgres = NULL;
end:
CLEARPGRES(conn->pgres);
conn->async_status = ASYNC_DONE;
Py_CLEAR(conn->async_cursor);
return result;
}
/* There has been a communication error during query execution. It may have
* happened e.g. for a network error or an error in the callback, and we
* cannot tell the two apart.
* Trying to PQcancel or PQgetResult to put the connection back into a working
* state doesn't work nice (issue #113): the program blocks and the
* interpreter won't even respond to SIGINT. PQreset could work async, but the
* python program would have then a connection made but not configured where
* it is probably not designed to handled. So for the moment we do the kindest
* thing we can: we close the connection. A long-running program should
* already have a way to discard broken connections; a short-lived one would
* benefit of working ctrl-c.
*/
static void
green_panic(connectionObject *conn)
{
Dprintf("green_panic: closing the connection");
conn_close_locked(conn);
}

76
psycopg/green.h Normal file
View File

@ -0,0 +1,76 @@
/* green.c - cooperation with coroutine libraries.
*
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_GREEN_H
#define PSYCOPG_GREEN_H 1
#include <libpq-fe.h>
#include "psycopg/connection.h"
#ifdef __cplusplus
extern "C" {
#endif
#define psyco_set_wait_callback_doc \
"Register a callback function to block waiting for data.\n" \
"\n" \
"The callback should have signature :samp:`fun({conn})` and\n" \
"is called to wait for data available whenever a blocking function from the\n" \
"libpq is called. Use `!set_wait_callback(None)` to revert to the\n" \
"original behaviour (i.e. using blocking libpq functions).\n" \
"\n" \
"The function is an hook to allow coroutine-based libraries (such as\n" \
"Eventlet_ or gevent_) to switch when Psycopg is blocked, allowing\n" \
"other coroutines to run concurrently.\n" \
"\n" \
"See `~psycopg2.extras.wait_select()` for an example of a wait callback\n" \
"implementation.\n" \
"\n" \
".. _Eventlet: https://eventlet.net/\n" \
".. _gevent: http://www.gevent.org/\n"
HIDDEN PyObject *psyco_set_wait_callback(PyObject *self, PyObject *obj);
#define psyco_get_wait_callback_doc \
"Return the currently registered wait callback.\n" \
"\n" \
"Return `!None` if no callback is currently registered.\n"
HIDDEN PyObject *psyco_get_wait_callback(PyObject *self, PyObject *obj);
HIDDEN int psyco_green(void);
HIDDEN int psyco_wait(connectionObject *conn);
HIDDEN PGresult *psyco_exec_green(connectionObject *conn, const char *command);
#define EXC_IF_GREEN(cmd) \
if (psyco_green()) { \
PyErr_SetString(ProgrammingError, #cmd " cannot be used " \
"with an asynchronous callback."); \
return NULL; }
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_GREEN_H) */

105
psycopg/libpq_support.c Normal file
View File

@ -0,0 +1,105 @@
/* libpq_support.c - functions not provided by libpq, but which are
* required for advanced communication with the server, such as
* streaming replication
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/libpq_support.h"
/* htonl(), ntohl() */
#ifdef _WIN32
#include <winsock2.h>
/* gettimeofday() */
#include "psycopg/win32_support.h"
#else
#include <arpa/inet.h>
#endif
/* support routines taken from pg_basebackup/streamutil.c */
/*
* Frontend version of GetCurrentTimestamp(), since we are not linked with
* backend code. The protocol always uses integer timestamps, regardless of
* server setting.
*/
int64_t
feGetCurrentTimestamp(void)
{
int64_t result;
struct timeval tp;
gettimeofday(&tp, NULL);
result = (int64_t) tp.tv_sec -
((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
result = (result * USECS_PER_SEC) + tp.tv_usec;
return result;
}
/*
* Converts an int64 to network byte order.
*/
void
fe_sendint64(int64_t i, char *buf)
{
uint32_t n32;
/* High order half first, since we're doing MSB-first */
n32 = (uint32_t) (i >> 32);
n32 = htonl(n32);
memcpy(&buf[0], &n32, 4);
/* Now the low order half */
n32 = (uint32_t) i;
n32 = htonl(n32);
memcpy(&buf[4], &n32, 4);
}
/*
* Converts an int64 from network byte order to native format.
*/
int64_t
fe_recvint64(char *buf)
{
int64_t result;
uint32_t h32;
uint32_t l32;
memcpy(&h32, buf, 4);
memcpy(&l32, buf + 4, 4);
h32 = ntohl(h32);
l32 = ntohl(l32);
result = h32;
result <<= 32;
result |= l32;
return result;
}

49
psycopg/libpq_support.h Normal file
View File

@ -0,0 +1,49 @@
/* libpq_support.h - definitions for libpq_support.c
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_LIBPQ_SUPPORT_H
#define PSYCOPG_LIBPQ_SUPPORT_H 1
#include "psycopg/config.h"
/* type and constant definitions from internal postgres include */
typedef uint64_t XLogRecPtr;
/* have to use lowercase %x, as PyString_FromFormat can't do %X */
#define XLOGFMTSTR "%x/%x"
#define XLOGFMTARGS(x) ((uint32_t)((x) >> 32)), ((uint32_t)((x) & 0xFFFFFFFF))
/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */
#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */
#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
#define SECS_PER_DAY 86400
#define USECS_PER_SEC 1000000LL
HIDDEN int64_t feGetCurrentTimestamp(void);
HIDDEN void fe_sendint64(int64_t i, char *buf);
HIDDEN int64_t fe_recvint64(char *buf);
#endif /* !defined(PSYCOPG_LIBPQ_SUPPORT_H) */

102
psycopg/lobject.h Normal file
View File

@ -0,0 +1,102 @@
/* lobject.h - definition for the psycopg lobject type
*
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_LOBJECT_H
#define PSYCOPG_LOBJECT_H 1
#include <libpq/libpq-fs.h>
#include "psycopg/connection.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject lobjectType;
typedef struct {
PyObject HEAD;
connectionObject *conn; /* connection owning the lobject */
long int mark; /* copied from conn->mark */
char *smode; /* string mode if lobject was opened */
int mode; /* numeric version of smode */
int fd; /* the file descriptor for file-like ops */
Oid oid; /* the oid for this lobject */
} lobjectObject;
/* functions exported from lobject_int.c */
RAISES_NEG HIDDEN int lobject_open(lobjectObject *self, connectionObject *conn,
Oid oid, const char *smode, Oid new_oid,
const char *new_file);
RAISES_NEG HIDDEN int lobject_unlink(lobjectObject *self);
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 Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence);
RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self);
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);
#define lobject_is_closed(self) \
((self)->fd < 0 || !(self)->conn || (self)->conn->closed)
/* exception-raising macros */
#define EXC_IF_LOBJ_CLOSED(self) \
if (lobject_is_closed(self)) { \
PyErr_SetString(InterfaceError, "lobject already closed"); \
return NULL; }
#define EXC_IF_LOBJ_LEVEL0(self) \
if (self->conn->autocommit) { \
psyco_set_error(ProgrammingError, NULL, \
"can't use a lobject outside of transactions"); \
return NULL; \
}
#define EXC_IF_LOBJ_UNMARKED(self) \
if (self->conn->mark != self->mark) { \
psyco_set_error(ProgrammingError, NULL, \
"lobject isn't valid anymore"); \
return NULL; \
}
/* Values for the lobject mode */
#define LOBJECT_READ 1
#define LOBJECT_WRITE 2
#define LOBJECT_BINARY 4
#define LOBJECT_TEXT 8
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_LOBJECT_H) */

486
psycopg/lobject_int.c Normal file
View File

@ -0,0 +1,486 @@
/* lobject_int.c - code used by the lobject object
*
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/pqpath.h"
#include <string.h>
static void
collect_error(connectionObject *conn)
{
conn_set_error(conn, PQerrorMessage(conn->pgconn));
}
/* Check if the mode passed to the large object is valid.
* In case of success return a value >= 0
* On error return a value < 0 and set an exception.
*
* Valid mode are [r|w|rw|n][t|b]
*/
RAISES_NEG static int
_lobject_parse_mode(const char *mode)
{
int rv = 0;
size_t pos = 0;
if (0 == strncmp("rw", mode, 2)) {
rv |= LOBJECT_READ | LOBJECT_WRITE;
pos += 2;
}
else {
switch (mode[0]) {
case 'r':
rv |= LOBJECT_READ;
pos += 1;
break;
case 'w':
rv |= LOBJECT_WRITE;
pos += 1;
break;
case 'n':
pos += 1;
break;
default:
rv |= LOBJECT_READ;
break;
}
}
switch (mode[pos]) {
case 't':
rv |= LOBJECT_TEXT;
pos += 1;
break;
case 'b':
rv |= LOBJECT_BINARY;
pos += 1;
break;
default:
rv |= LOBJECT_TEXT;
break;
}
if (pos != strlen(mode)) {
PyErr_Format(PyExc_ValueError,
"bad mode for lobject: '%s'", mode);
rv = -1;
}
return rv;
}
/* Return a string representing the lobject mode.
*
* The return value is a new string allocated on the Python heap.
*
* The function must be called holding the GIL.
*/
static char *
_lobject_unparse_mode(int mode)
{
char *buf;
char *c;
/* the longest is 'rwt' */
if (!(c = buf = PyMem_Malloc(4))) {
PyErr_NoMemory();
return NULL;
}
if (mode & LOBJECT_READ) { *c++ = 'r'; }
if (mode & LOBJECT_WRITE) { *c++ = 'w'; }
if (buf == c) {
/* neither read nor write */
*c++ = 'n';
}
else {
if (mode & LOBJECT_TEXT) {
*c++ = 't';
}
else {
*c++ = 'b';
}
}
*c = '\0';
return buf;
}
/* lobject_open - create a new/open an existing lo */
RAISES_NEG int
lobject_open(lobjectObject *self, connectionObject *conn,
Oid oid, const char *smode, Oid new_oid, const char *new_file)
{
int retvalue = -1;
int pgmode = 0;
int mode;
if (0 > (mode = _lobject_parse_mode(smode))) {
return -1;
}
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
retvalue = pq_begin_locked(self->conn, &_save);
if (retvalue < 0)
goto end;
/* 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_file */
if (oid == InvalidOid) {
if (new_file)
self->oid = lo_import(self->conn->pgconn, new_file);
else {
/* Use lo_creat when possible to be more middleware-friendly.
See ticket #88. */
if (new_oid != InvalidOid)
self->oid = lo_create(self->conn->pgconn, new_oid);
else
self->oid = lo_creat(self->conn->pgconn, INV_READ | INV_WRITE);
}
Dprintf("lobject_open: large object created with oid = %u",
self->oid);
if (self->oid == InvalidOid) {
collect_error(self->conn);
retvalue = -1;
goto end;
}
mode = (mode & ~LOBJECT_READ) | LOBJECT_WRITE;
}
else {
self->oid = oid;
}
/* if the oid is a real one we try to open with the given mode */
if (mode & LOBJECT_READ) { pgmode |= INV_READ; }
if (mode & LOBJECT_WRITE) { pgmode |= INV_WRITE; }
if (pgmode) {
self->fd = lo_open(self->conn->pgconn, self->oid, pgmode);
Dprintf("lobject_open: large object opened with mode = %i fd = %d",
pgmode, self->fd);
if (self->fd == -1) {
collect_error(self->conn);
retvalue = -1;
goto end;
}
}
/* set the mode for future reference */
self->mode = mode;
Py_BLOCK_THREADS;
self->smode = _lobject_unparse_mode(mode);
Py_UNBLOCK_THREADS;
if (NULL == self->smode) {
retvalue = 1; /* exception already set */
goto end;
}
retvalue = 0;
end:
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(self->conn);
/* if retvalue > 0, an exception is already set */
return retvalue;
}
/* lobject_close - close an existing lo */
RAISES_NEG static int
lobject_close_locked(lobjectObject *self)
{
int retvalue;
Dprintf("lobject_close_locked: conn->closed %ld", self->conn->closed);
switch (self->conn->closed) {
case 0:
/* Connection is open, go ahead */
break;
case 1:
/* Connection is closed, return a success */
return 0;
break;
default:
conn_set_error(self->conn, "the connection is broken");
return -1;
break;
}
if (self->conn->autocommit ||
self->conn->mark != self->mark ||
self->fd == -1)
return 0;
retvalue = lo_close(self->conn->pgconn, self->fd);
self->fd = -1;
if (retvalue < 0)
collect_error(self->conn);
return retvalue;
}
RAISES_NEG int
lobject_close(lobjectObject *self)
{
int retvalue;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
retvalue = lobject_close_locked(self);
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(self->conn);
return retvalue;
}
/* lobject_unlink - remove an lo from database */
RAISES_NEG int
lobject_unlink(lobjectObject *self)
{
int retvalue = -1;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
retvalue = pq_begin_locked(self->conn, &_save);
if (retvalue < 0)
goto end;
/* first we make sure the lobject is closed and then we unlink */
retvalue = lobject_close_locked(self);
if (retvalue < 0)
goto end;
retvalue = lo_unlink(self->conn->pgconn, self->oid);
if (retvalue < 0)
collect_error(self->conn);
end:
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(self->conn);
return retvalue;
}
/* lobject_write - write bytes to a lo */
RAISES_NEG Py_ssize_t
lobject_write(lobjectObject *self, const char *buf, size_t len)
{
Py_ssize_t written;
Dprintf("lobject_writing: fd = %d, len = " FORMAT_CODE_SIZE_T,
self->fd, len);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
written = lo_write(self->conn->pgconn, self->fd, buf, len);
if (written < 0)
collect_error(self->conn);
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (written < 0)
pq_complete_error(self->conn);
return written;
}
/* lobject_read - read bytes from a lo */
RAISES_NEG Py_ssize_t
lobject_read(lobjectObject *self, char *buf, size_t len)
{
Py_ssize_t n_read;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
n_read = lo_read(self->conn->pgconn, self->fd, buf, len);
if (n_read < 0)
collect_error(self->conn);
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (n_read < 0)
pq_complete_error(self->conn);
return n_read;
}
/* lobject_seek - move the current position in the lo */
RAISES_NEG Py_ssize_t
lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence)
{
Py_ssize_t where;
Dprintf("lobject_seek: fd = %d, pos = " FORMAT_CODE_PY_SSIZE_T ", whence = %d",
self->fd, pos, whence);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
} else {
where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
}
#else
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
#endif
Dprintf("lobject_seek: where = " FORMAT_CODE_PY_SSIZE_T, where);
if (where < 0)
collect_error(self->conn);
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (where < 0)
pq_complete_error(self->conn);
return where;
}
/* lobject_tell - tell the current position in the lo */
RAISES_NEG Py_ssize_t
lobject_tell(lobjectObject *self)
{
Py_ssize_t where;
Dprintf("lobject_tell: fd = %d", self->fd);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
} else {
where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd);
}
#else
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
#endif
Dprintf("lobject_tell: where = " FORMAT_CODE_PY_SSIZE_T, where);
if (where < 0)
collect_error(self->conn);
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (where < 0)
pq_complete_error(self->conn);
return where;
}
/* lobject_export - export to a local file */
RAISES_NEG int
lobject_export(lobjectObject *self, const char *filename)
{
int retvalue;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
retvalue = pq_begin_locked(self->conn, &_save);
if (retvalue < 0)
goto end;
retvalue = lo_export(self->conn->pgconn, self->oid, filename);
if (retvalue < 0)
collect_error(self->conn);
end:
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(self->conn);
return retvalue;
}
RAISES_NEG int
lobject_truncate(lobjectObject *self, size_t len)
{
int retvalue;
Dprintf("lobject_truncate: fd = %d, len = " FORMAT_CODE_SIZE_T,
self->fd, 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);
pthread_mutex_unlock(&(self->conn->lock));
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(self->conn);
return retvalue;
}

471
psycopg/lobject_type.c Normal file
View File

@ -0,0 +1,471 @@
/* lobject_type.c - python interface to lobject objects
*
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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*/
};

277
psycopg/microprotocols.c Normal file
View File

@ -0,0 +1,277 @@
/* microprotocols.c - minimalist and non-validating protocols implementation
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/microprotocols.h"
#include "psycopg/microprotocols_proto.h"
#include "psycopg/cursor.h"
#include "psycopg/connection.h"
/** the adapters registry **/
PyObject *psyco_adapters;
/* microprotocols_init - initialize the adapters dictionary */
RAISES_NEG int
microprotocols_init(PyObject *module)
{
/* create adapters dictionary and put it in module namespace */
if (!(psyco_adapters = PyDict_New())) {
return -1;
}
Py_INCREF(psyco_adapters);
if (0 > PyModule_AddObject(module, "adapters", psyco_adapters)) {
Py_DECREF(psyco_adapters);
return -1;
}
return 0;
}
/* microprotocols_add - add a reverse type-caster to the dictionary
*
* Return 0 on success, else -1 and set an exception.
*/
RAISES_NEG int
microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast)
{
PyObject *key = NULL;
int rv = -1;
if (proto == NULL) proto = (PyObject*)&isqlquoteType;
if (!(key = PyTuple_Pack(2, (PyObject*)type, proto))) { goto exit; }
if (0 != PyDict_SetItem(psyco_adapters, key, cast)) { goto exit; }
rv = 0;
exit:
Py_XDECREF(key);
return rv;
}
/* Check if one of `obj` superclasses has an adapter for `proto`.
*
* If it does, return a *borrowed reference* to the adapter, else to None.
*/
BORROWED static PyObject *
_get_superclass_adapter(PyObject *obj, PyObject *proto)
{
PyTypeObject *type;
PyObject *mro, *st;
PyObject *key, *adapter;
Py_ssize_t i, ii;
type = Py_TYPE(obj);
if (!(type->tp_mro)) {
/* has no mro */
return Py_None;
}
/* Walk the mro from the most specific subclass. */
mro = type->tp_mro;
for (i = 1, ii = PyTuple_GET_SIZE(mro); i < ii; ++i) {
st = PyTuple_GET_ITEM(mro, i);
if (!(key = PyTuple_Pack(2, st, proto))) { return NULL; }
adapter = PyDict_GetItem(psyco_adapters, key);
Py_DECREF(key);
if (adapter) {
Dprintf(
"microprotocols_adapt: using '%s' adapter to adapt '%s'",
((PyTypeObject *)st)->tp_name, type->tp_name);
/* register this adapter as good for the subclass too,
* so that the next time it will be found in the fast path */
/* Well, no, maybe this is not a good idea.
* It would become a leak in case of dynamic
* classes generated in a loop (think namedtuples). */
/* key = PyTuple_Pack(2, (PyObject*)type, proto);
* PyDict_SetItem(psyco_adapters, key, adapter);
* Py_DECREF(key);
*/
return adapter;
}
}
return Py_None;
}
/* microprotocols_adapt - adapt an object to the built-in protocol */
PyObject *
microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
{
PyObject *adapter, *adapted, *key, *meth;
char buffer[256];
/* 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 */
Dprintf("microprotocols_adapt: trying to adapt %s",
Py_TYPE(obj)->tp_name);
/* look for an adapter in the registry */
if (!(key = PyTuple_Pack(2, Py_TYPE(obj), proto))) { return NULL; }
adapter = PyDict_GetItem(psyco_adapters, key);
Py_DECREF(key);
if (adapter) {
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
return adapted;
}
/* try to have the protocol adapt this object*/
if ((meth = PyObject_GetAttrString(proto, "__adapt__"))) {
adapted = PyObject_CallFunctionObjArgs(meth, obj, NULL);
Py_DECREF(meth);
if (adapted && adapted != Py_None) return adapted;
Py_XDECREF(adapted);
if (PyErr_Occurred()) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
} else {
return NULL;
}
}
}
else {
/* proto.__adapt__ not found. */
PyErr_Clear();
}
/* then try to have the object adapt itself */
if ((meth = PyObject_GetAttrString(obj, "__conform__"))) {
adapted = PyObject_CallFunctionObjArgs(meth, proto, NULL);
Py_DECREF(meth);
if (adapted && adapted != Py_None) return adapted;
Py_XDECREF(adapted);
if (PyErr_Occurred()) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
} else {
return NULL;
}
}
}
else {
/* obj.__conform__ not found. */
PyErr_Clear();
}
/* Finally check if a superclass can be adapted and use the same adapter. */
if (!(adapter = _get_superclass_adapter(obj, proto))) {
return NULL;
}
if (Py_None != adapter) {
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
return adapted;
}
/* else set the right exception and return NULL */
PyOS_snprintf(buffer, 255, "can't adapt type '%s'",
Py_TYPE(obj)->tp_name);
psyco_set_error(ProgrammingError, NULL, buffer);
return NULL;
}
/* microprotocol_getquoted - utility function that adapt and call getquoted.
*
* Return a bytes string, NULL on error.
*/
PyObject *
microprotocol_getquoted(PyObject *obj, connectionObject *conn)
{
PyObject *res = NULL;
PyObject *prepare = NULL;
PyObject *adapted;
if (!(adapted = microprotocols_adapt(obj, (PyObject*)&isqlquoteType, NULL))) {
goto exit;
}
Dprintf("microprotocol_getquoted: adapted to %s",
Py_TYPE(adapted)->tp_name);
/* if requested prepare the object passing it the connection */
if (conn) {
if ((prepare = PyObject_GetAttrString(adapted, "prepare"))) {
res = PyObject_CallFunctionObjArgs(
prepare, (PyObject *)conn, NULL);
if (res) {
Py_DECREF(res);
res = NULL;
} else {
goto exit;
}
}
else {
/* adapted.prepare not found */
PyErr_Clear();
}
}
/* call the getquoted method on adapted (that should exist because we
adapted to the right protocol) */
res = PyObject_CallMethod(adapted, "getquoted", NULL);
/* Convert to bytes. */
if (res && PyUnicode_CheckExact(res)) {
PyObject *b;
b = conn_encode(conn, res);
Py_DECREF(res);
res = b;
}
exit:
Py_XDECREF(adapted);
Py_XDECREF(prepare);
/* we return res with one extra reference, the caller shall free it */
return res;
}
/** 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);
}

64
psycopg/microprotocols.h Normal file
View File

@ -0,0 +1,64 @@
/* microprotocols.c - definitions for minimalist and non-validating protocols
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_MICROPROTOCOLS_H
#define PSYCOPG_MICROPROTOCOLS_H 1
#include "psycopg/connection.h"
#include "psycopg/cursor.h"
#ifdef __cplusplus
extern "C" {
#endif
/** adapters registry **/
extern HIDDEN 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 */
HIDDEN RAISES_NEG int microprotocols_init(PyObject *dict);
HIDDEN RAISES_NEG int microprotocols_add(
PyTypeObject *type, PyObject *proto, PyObject *cast);
HIDDEN PyObject *microprotocols_adapt(
PyObject *obj, PyObject *proto, PyObject *alt);
HIDDEN PyObject *microprotocol_getquoted(
PyObject *obj, connectionObject *conn);
HIDDEN PyObject *
psyco_microprotocols_adapt(cursorObject *self, PyObject *args);
#define psyco_microprotocols_adapt_doc \
"adapt(obj, protocol, alternate) -> object -- adapt obj to given protocol"
#endif /* !defined(PSYCOPG_MICROPROTOCOLS_H) */

View File

@ -0,0 +1,180 @@
/* microprotocol_proto.c - psycopg protocols
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/microprotocols_proto.h"
#include <string.h>
/** void protocol implementation **/
/* getquoted - return quoted representation for object */
#define isqlquote_getquoted_doc \
"getquoted() -- return SQL-quoted representation of this object"
static PyObject *
isqlquote_getquoted(isqlquoteObject *self, PyObject *args)
{
Py_RETURN_NONE;
}
/* getbinary - return quoted representation for object */
#define isqlquote_getbinary_doc \
"getbinary() -- return SQL-quoted binary representation of this object"
static PyObject *
isqlquote_getbinary(isqlquoteObject *self, PyObject *args)
{
Py_RETURN_NONE;
}
/* getbuffer - return quoted representation for object */
#define isqlquote_getbuffer_doc \
"getbuffer() -- return this object"
static PyObject *
isqlquote_getbuffer(isqlquoteObject *self, PyObject *args)
{
Py_RETURN_NONE;
}
/** the ISQLQuote object **/
/* object method list */
static struct PyMethodDef isqlquoteObject_methods[] = {
{"getquoted", (PyCFunction)isqlquote_getquoted,
METH_NOARGS, isqlquote_getquoted_doc},
{"getbinary", (PyCFunction)isqlquote_getbinary,
METH_NOARGS, isqlquote_getbinary_doc},
{"getbuffer", (PyCFunction)isqlquote_getbuffer,
METH_NOARGS, isqlquote_getbuffer_doc},
{NULL}
};
/* object member list */
static struct PyMemberDef isqlquoteObject_members[] = {
/* DBAPI-2.0 extensions (exception objects) */
{"_wrapped", T_OBJECT, offsetof(isqlquoteObject, wrapped), READONLY},
{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);
Py_TYPE(obj)->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);
}
/* object type */
#define isqlquoteType_doc \
"Abstract ISQLQuote protocol\n\n" \
"An object conform to this protocol should expose a ``getquoted()`` method\n" \
"returning the SQL representation of the object.\n\n"
PyTypeObject isqlquoteType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.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*/
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*/
0, /*tp_alloc*/
isqlquote_new, /*tp_new*/
};

View File

@ -0,0 +1,47 @@
/* microporotocols_proto.h - definition for psycopg's protocols
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_ISQLQUOTE_H
#define PSYCOPG_ISQLQUOTE_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject isqlquoteType;
typedef struct {
PyObject_HEAD
PyObject *wrapped;
} isqlquoteObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_ISQLQUOTE_H) */

41
psycopg/notify.h Normal file
View File

@ -0,0 +1,41 @@
/* notify.h - definition for the psycopg Notify type
*
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_NOTIFY_H
#define PSYCOPG_NOTIFY_H 1
extern HIDDEN PyTypeObject notifyType;
typedef struct {
PyObject_HEAD
PyObject *pid;
PyObject *channel;
PyObject *payload;
} notifyObject;
#endif /* PSYCOPG_NOTIFY_H */

298
psycopg/notify_type.c Normal file
View File

@ -0,0 +1,298 @@
/* notify_type.c - python interface to Notify objects
*
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/notify.h"
static const char notify_doc[] =
"A notification received from the backend.\n\n"
"`!Notify` instances are made available upon reception on the\n"
"`~connection.notifies` member of the listening connection. The object\n"
"can be also accessed as a 2 items tuple returning the members\n"
":samp:`({pid},{channel})` for backward compatibility.\n\n"
"See :ref:`async-notify` for details.";
static const char pid_doc[] =
"The ID of the backend process that sent the notification.\n\n"
"Note: if the sending session was handled by Psycopg, you can use\n"
"`~connection.info.backend_pid` to know its PID.";
static const char channel_doc[] =
"The name of the channel to which the notification was sent.";
static const char payload_doc[] =
"The payload message of the notification.\n\n"
"Attaching a payload to a notification is only available since\n"
"PostgreSQL 9.0: for notifications received from previous versions\n"
"of the server this member is always the empty string.";
static PyMemberDef notify_members[] = {
{ "pid", T_OBJECT, offsetof(notifyObject, pid), READONLY, (char *)pid_doc },
{ "channel", T_OBJECT, offsetof(notifyObject, channel), READONLY, (char *)channel_doc },
{ "payload", T_OBJECT, offsetof(notifyObject, payload), READONLY, (char *)payload_doc },
{ NULL }
};
static PyObject *
notify_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
return type->tp_alloc(type, 0);
}
static int
notify_init(notifyObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {"pid", "channel", "payload", NULL};
PyObject *pid = NULL, *channel = NULL, *payload = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O", kwlist,
&pid, &channel, &payload)) {
return -1;
}
if (!payload) {
payload = Text_FromUTF8("");
}
Py_INCREF(pid);
self->pid = pid;
Py_INCREF(channel);
self->channel = channel;
Py_INCREF(payload);
self->payload = payload;
return 0;
}
static void
notify_dealloc(notifyObject *self)
{
Py_CLEAR(self->pid);
Py_CLEAR(self->channel);
Py_CLEAR(self->payload);
Py_TYPE(self)->tp_free((PyObject *)self);
}
/* Convert a notify into a 2 or 3 items tuple. */
static PyObject *
notify_astuple(notifyObject *self, int with_payload)
{
PyObject *tself;
if (!(tself = PyTuple_New(with_payload ? 3 : 2))) { return NULL; }
Py_INCREF(self->pid);
PyTuple_SET_ITEM(tself, 0, self->pid);
Py_INCREF(self->channel);
PyTuple_SET_ITEM(tself, 1, self->channel);
if (with_payload) {
Py_INCREF(self->payload);
PyTuple_SET_ITEM(tself, 2, self->payload);
}
return tself;
}
/* note on Notify-tuple comparison.
*
* Such a comparison is required otherwise a check n == (pid, channel)
* would fail. We also want to compare two notifies, and the obvious meaning is
* "check that all the attributes are equal". Unfortunately this leads to an
* inconsistent situation:
* Notify(pid, channel, payload1)
* == (pid, channel)
* == Notify(pid, channel, payload2)
* even when payload1 != payload2. We can probably live with that, but hashing
* makes things worse: hashability is a desirable property for a Notify, and
* to maintain compatibility we should put a notify object in the same bucket
* of a 2-item tuples... but we can't put all the payloads with the same
* (pid, channel) in the same bucket: it would be an extremely poor hash.
* So we maintain compatibility in the sense that notify without payload
* behave as 2-item tuples in term of hashability, but if a payload is present
* the (pid, channel) pair is no more equivalent as dict key to the Notify.
*/
static PyObject *
notify_richcompare(notifyObject *self, PyObject *other, int op)
{
PyObject *rv = NULL;
PyObject *tself = NULL;
PyObject *tother = NULL;
if (Py_TYPE(other) == &notifyType) {
if (!(tself = notify_astuple(self, 1))) { goto exit; }
if (!(tother = notify_astuple((notifyObject *)other, 1))) { goto exit; }
rv = PyObject_RichCompare(tself, tother, op);
}
else if (PyTuple_Check(other)) {
if (!(tself = notify_astuple(self, 0))) { goto exit; }
rv = PyObject_RichCompare(tself, other, op);
}
else {
Py_INCREF(Py_False);
rv = Py_False;
}
exit:
Py_XDECREF(tself);
Py_XDECREF(tother);
return rv;
}
static Py_hash_t
notify_hash(notifyObject *self)
{
Py_hash_t rv = -1L;
PyObject *tself = NULL;
/* if self == a tuple, then their hashes are the same. */
int has_payload = PyObject_IsTrue(self->payload);
if (!(tself = notify_astuple(self, has_payload))) { goto exit; }
rv = PyObject_Hash(tself);
exit:
Py_XDECREF(tself);
return rv;
}
static PyObject*
notify_repr(notifyObject *self)
{
PyObject *rv = NULL;
PyObject *format = NULL;
PyObject *args = NULL;
if (!(format = Text_FromUTF8("Notify(%r, %r, %r)"))) {
goto exit;
}
if (!(args = PyTuple_New(3))) { goto exit; }
Py_INCREF(self->pid);
PyTuple_SET_ITEM(args, 0, self->pid);
Py_INCREF(self->channel);
PyTuple_SET_ITEM(args, 1, self->channel);
Py_INCREF(self->payload);
PyTuple_SET_ITEM(args, 2, self->payload);
rv = Text_Format(format, args);
exit:
Py_XDECREF(args);
Py_XDECREF(format);
return rv;
}
/* Notify can be accessed as a 2 items tuple for backward compatibility */
static Py_ssize_t
notify_len(notifyObject *self)
{
return 2;
}
static PyObject *
notify_getitem(notifyObject *self, Py_ssize_t item)
{
if (item < 0)
item += 2;
switch (item) {
case 0:
Py_INCREF(self->pid);
return self->pid;
case 1:
Py_INCREF(self->channel);
return self->channel;
default:
PyErr_SetString(PyExc_IndexError, "index out of range");
return NULL;
}
}
static PySequenceMethods notify_sequence = {
(lenfunc)notify_len, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
(ssizeargfunc)notify_getitem, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
0, /* sq_contains */
0, /* sq_inplace_concat */
0, /* sq_inplace_repeat */
};
PyTypeObject notifyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Notify",
sizeof(notifyObject), 0,
(destructor)notify_dealloc, /* tp_dealloc */
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)notify_repr, /*tp_repr*/
0, /*tp_as_number*/
&notify_sequence, /*tp_as_sequence*/
0, /*tp_as_mapping*/
(hashfunc)notify_hash, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
/* Notify is not GC as it only has string attributes */
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
notify_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
(richcmpfunc)notify_richcompare, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
notify_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)notify_init, /*tp_init*/
0, /*tp_alloc*/
notify_new, /*tp_new*/
};

65
psycopg/pgtypes.h Normal file
View File

@ -0,0 +1,65 @@
#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 PG_TYPE_RELTYPE_OID 71
#define PG_ATTRIBUTE_RELTYPE_OID 75
#define PG_PROC_RELTYPE_OID 81
#define PG_CLASS_RELTYPE_OID 83
#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 INT4ARRAYOID 1007
#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

1834
psycopg/pqpath.c Normal file

File diff suppressed because it is too large Load Diff

74
psycopg/pqpath.h Normal file
View File

@ -0,0 +1,74 @@
/* pqpath.h - definitions for pqpath.c
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_PQPATH_H
#define PSYCOPG_PQPATH_H 1
#include "psycopg/cursor.h"
#include "psycopg/connection.h"
#include "psycopg/replication_cursor.h"
#include "psycopg/replication_message.h"
/* macro to clean the pg result */
#define CLEARPGRES(pgres) do { PQclear(pgres); pgres = NULL; } while (0)
/* exported functions */
RAISES_NEG HIDDEN int pq_fetch(cursorObject *curs, int no_result);
RAISES_NEG HIDDEN int pq_execute(cursorObject *curs, const char *query,
int async, int no_result, int no_begin);
HIDDEN int pq_send_query(connectionObject *conn, const char *query);
HIDDEN int pq_begin_locked(connectionObject *conn, PyThreadState **tstate);
HIDDEN int pq_commit(connectionObject *conn);
RAISES_NEG HIDDEN int pq_abort_locked(connectionObject *conn,
PyThreadState **tstate);
RAISES_NEG HIDDEN int pq_abort(connectionObject *conn);
HIDDEN int pq_reset_locked(connectionObject *conn, PyThreadState **tstate);
RAISES_NEG HIDDEN int pq_reset(connectionObject *conn);
HIDDEN char *pq_get_guc_locked(connectionObject *conn, const char *param,
PyThreadState **tstate);
HIDDEN int pq_set_guc_locked(connectionObject *conn, const char *param,
const char *value, PyThreadState **tstate);
HIDDEN int pq_tpc_command_locked(connectionObject *conn,
const char *cmd, const char *tid,
PyThreadState **tstate);
RAISES_NEG HIDDEN int pq_get_result_async(connectionObject *conn);
HIDDEN int pq_flush(connectionObject *conn);
HIDDEN void pq_clear_async(connectionObject *conn);
RAISES_NEG HIDDEN int pq_set_non_blocking(connectionObject *conn, int arg);
HIDDEN void pq_set_critical(connectionObject *conn, const char *msg);
HIDDEN int pq_execute_command_locked(connectionObject *conn, const char *query,
PyThreadState **tstate);
RAISES HIDDEN void pq_complete_error(connectionObject *conn);
/* replication protocol support */
HIDDEN int pq_copy_both(replicationCursorObject *repl, PyObject *consumer);
HIDDEN int pq_read_replication_message(replicationCursorObject *repl,
replicationMessageObject **msg);
HIDDEN int pq_send_replication_feedback(replicationCursorObject *repl, int reply_requested);
#endif /* !defined(PSYCOPG_PQPATH_H) */

107
psycopg/psycopg.h Normal file
View File

@ -0,0 +1,107 @@
/* psycopg.h - definitions for the psycopg python module
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_H
#define PSYCOPG_H 1
#if PG_VERSION_NUM < 90100
#error "Psycopg requires PostgreSQL client library (libpq) >= 9.1"
#endif
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <libpq-fe.h>
#include "psycopg/config.h"
#include "psycopg/python.h"
#include "psycopg/utils.h"
#ifdef __cplusplus
extern "C" {
#endif
/* DBAPI compliance parameters */
#define APILEVEL "2.0"
#define THREADSAFETY 2
#define PARAMSTYLE "pyformat"
/* global exceptions */
extern HIDDEN PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
*InternalError, *OperationalError, *ProgrammingError,
*IntegrityError, *DataError, *NotSupportedError;
extern HIDDEN PyObject *QueryCanceledError, *TransactionRollbackError;
/* sqlstate -> exception map */
extern HIDDEN PyObject *sqlstate_errors;
/* postgresql<->python encoding map */
extern HIDDEN PyObject *psycoEncodings;
/* SQL NULL */
extern HIDDEN PyObject *psyco_null;
/* Exceptions docstrings */
#define Error_doc \
"Base class for error exceptions."
#define Warning_doc \
"A database warning."
#define InterfaceError_doc \
"Error related to the database interface."
#define DatabaseError_doc \
"Error related to the database engine."
#define InternalError_doc \
"The database encountered an internal error."
#define OperationalError_doc \
"Error related to database operation (disconnect, memory allocation etc)."
#define ProgrammingError_doc \
"Error related to database programming (SQL error, table not found etc)."
#define IntegrityError_doc \
"Error related to database integrity."
#define DataError_doc \
"Error related to problems with the processed data."
#define NotSupportedError_doc \
"A method or database API was used which is not supported by the database."
#define QueryCanceledError_doc \
"Error related to SQL query cancellation."
#define TransactionRollbackError_doc \
"Error causing transaction rollback (deadlocks, serialization failures, etc)."
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_H) */

1030
psycopg/psycopgmodule.c Normal file

File diff suppressed because it is too large Load Diff

99
psycopg/python.h Normal file
View File

@ -0,0 +1,99 @@
/* python.h - python version compatibility stuff
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_PYTHON_H
#define PSYCOPG_PYTHON_H 1
#if PY_VERSION_HEX < 0x03060000
#error "psycopg requires Python 3.6"
#endif
#include <structmember.h>
/* Since Py_TYPE() is changed to the inline static function,
* Py_TYPE(obj) = new_type must be replaced with Py_SET_TYPE(obj, new_type)
* https://docs.python.org/3.10/whatsnew/3.10.html#id2
*/
#if PY_VERSION_HEX < 0x030900A4
#define Py_SET_TYPE(obj, type) ((Py_TYPE(obj) = (type)), (void)0)
#endif
/* FORMAT_CODE_PY_SSIZE_T is for Py_ssize_t: */
#define FORMAT_CODE_PY_SSIZE_T "%" PY_FORMAT_SIZE_T "d"
/* FORMAT_CODE_SIZE_T is for plain size_t, not for Py_ssize_t: */
#ifdef _MSC_VER
/* For MSVC: */
#define FORMAT_CODE_SIZE_T "%Iu"
#else
/* C99 standard format code: */
#define FORMAT_CODE_SIZE_T "%zu"
#endif
#define Text_Type PyUnicode_Type
#define Text_Check(s) PyUnicode_Check(s)
#define Text_Format(f,a) PyUnicode_Format(f,a)
#define Text_FromUTF8(s) PyUnicode_FromString(s)
#define Text_FromUTF8AndSize(s,n) PyUnicode_FromStringAndSize(s,n)
#define PyInt_Type PyLong_Type
#define PyInt_Check PyLong_Check
#define PyInt_AsLong PyLong_AsLong
#define PyInt_FromLong PyLong_FromLong
#define PyInt_FromString PyLong_FromString
#define PyInt_FromSsize_t PyLong_FromSsize_t
#define PyExc_StandardError PyExc_Exception
#define PyString_FromFormat PyUnicode_FromFormat
#define Py_TPFLAGS_HAVE_ITER 0L
#define Py_TPFLAGS_HAVE_RICHCOMPARE 0L
#define Py_TPFLAGS_HAVE_WEAKREFS 0L
#ifndef PyNumber_Int
#define PyNumber_Int PyNumber_Long
#endif
#define Bytes_Type PyBytes_Type
#define Bytes_Check PyBytes_Check
#define Bytes_CheckExact PyBytes_CheckExact
#define Bytes_AS_STRING PyBytes_AS_STRING
#define Bytes_GET_SIZE PyBytes_GET_SIZE
#define Bytes_Size PyBytes_Size
#define Bytes_AsString PyBytes_AsString
#define Bytes_AsStringAndSize PyBytes_AsStringAndSize
#define Bytes_FromString PyBytes_FromString
#define Bytes_FromStringAndSize PyBytes_FromStringAndSize
#define Bytes_FromFormat PyBytes_FromFormat
#define Bytes_ConcatAndDel PyBytes_ConcatAndDel
#define _Bytes_Resize _PyBytes_Resize
#define INIT_MODULE(m) PyInit_ ## m
#define PyLong_FromOid(x) (PyLong_FromUnsignedLong((unsigned long)(x)))
/* expose Oid attributes in Python C objects */
#define T_OID T_UINT
#endif /* !defined(PSYCOPG_PYTHON_H) */

View File

@ -0,0 +1,53 @@
/* replication_connection.h - definition for the psycopg replication connection type
*
* Copyright (C) 2015-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_REPLICATION_CONNECTION_H
#define PSYCOPG_REPLICATION_CONNECTION_H 1
#include "psycopg/connection.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject replicationConnectionType;
typedef struct replicationConnectionObject {
connectionObject conn;
long int type;
} replicationConnectionObject;
/* The funny constant values should help to avoid mixups with some
commonly used numbers like 1 and 2. */
#define REPLICATION_PHYSICAL 12345678
#define REPLICATION_LOGICAL 87654321
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_REPLICATION_CONNECTION_H) */

View File

@ -0,0 +1,193 @@
/* replication_connection_type.c - python interface to replication connection objects
*
* Copyright (C) 2015-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/replication_connection.h"
#include "psycopg/replication_message.h"
#include "psycopg/green.h"
#include "psycopg/pqpath.h"
#include <string.h>
#include <stdlib.h>
#define psyco_repl_conn_type_doc \
"replication_type -- the replication connection type"
static PyObject *
psyco_repl_conn_get_type(replicationConnectionObject *self)
{
return PyInt_FromLong(self->type);
}
static int
replicationConnection_init(replicationConnectionObject *self,
PyObject *args, PyObject *kwargs)
{
PyObject *dsn = NULL, *async = Py_False,
*item = NULL, *extras = NULL, *cursor = NULL,
*newdsn = NULL, *newargs = NULL, *dsnopts = NULL;
int ret = -1;
long int replication_type;
/* 'replication_type' is not actually optional, but there's no
good way to put it before 'async' in the list */
static char *kwlist[] = {"dsn", "async", "replication_type", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Ol", kwlist,
&dsn, &async, &replication_type)) {
return ret;
}
/*
We have to call make_dsn() to add replication-specific
connection parameters, because the DSN might be an URI (if there
were no keyword arguments to connect() it is passed unchanged).
*/
if (!(dsnopts = PyDict_New())) { return ret; }
/* all the nice stuff is located in python-level ReplicationCursor class */
if (!(extras = PyImport_ImportModule("psycopg2.extras"))) { goto exit; }
if (!(cursor = PyObject_GetAttrString(extras, "ReplicationCursor"))) { goto exit; }
if (replication_type == REPLICATION_PHYSICAL) {
self->type = REPLICATION_PHYSICAL;
#define SET_ITEM(k, v) \
if (!(item = Text_FromUTF8(#v))) { goto exit; } \
if (PyDict_SetItemString(dsnopts, #k, item) != 0) { goto exit; } \
Py_DECREF(item); \
item = NULL;
SET_ITEM(replication, true);
SET_ITEM(dbname, replication); /* required for .pgpass lookup */
} else if (replication_type == REPLICATION_LOGICAL) {
self->type = REPLICATION_LOGICAL;
SET_ITEM(replication, database);
#undef SET_ITEM
} else {
PyErr_SetString(PyExc_TypeError,
"replication_type must be either "
"REPLICATION_PHYSICAL or REPLICATION_LOGICAL");
goto exit;
}
if (!(newdsn = psyco_make_dsn(dsn, dsnopts))) { goto exit; }
if (!(newargs = PyTuple_Pack(2, newdsn, async))) { goto exit; }
/* only attempt the connection once we've handled all possible errors */
if ((ret = connectionType.tp_init((PyObject *)self, newargs, NULL)) < 0) {
goto exit;
}
self->conn.autocommit = 1;
Py_INCREF(cursor);
self->conn.cursor_factory = cursor;
exit:
Py_XDECREF(item);
Py_XDECREF(extras);
Py_XDECREF(cursor);
Py_XDECREF(newdsn);
Py_XDECREF(newargs);
Py_XDECREF(dsnopts);
return ret;
}
static PyObject *
replicationConnection_repr(replicationConnectionObject *self)
{
return PyString_FromFormat(
"<ReplicationConnection object at %p; dsn: '%s', closed: %ld>",
self, self->conn.dsn, self->conn.closed);
}
/* object calculated member list */
static struct PyGetSetDef replicationConnectionObject_getsets[] = {
/* override to prevent user tweaking these: */
{ "autocommit", NULL, NULL, NULL },
{ "isolation_level", NULL, NULL, NULL },
{ "set_session", NULL, NULL, NULL },
{ "set_isolation_level", NULL, NULL, NULL },
{ "reset", NULL, NULL, NULL },
/* an actual getter */
{ "replication_type",
(getter)psyco_repl_conn_get_type, NULL,
psyco_repl_conn_type_doc, NULL },
{NULL}
};
/* object type */
#define replicationConnectionType_doc \
"A replication connection."
PyTypeObject replicationConnectionType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.ReplicationConnection",
sizeof(replicationConnectionObject), 0,
0, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)replicationConnection_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
(reprfunc)replicationConnection_repr, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER |
Py_TPFLAGS_HAVE_GC, /*tp_flags*/
replicationConnectionType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
0, /*tp_members*/
replicationConnectionObject_getsets, /*tp_getset*/
&connectionType, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)replicationConnection_init, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
};

View File

@ -0,0 +1,66 @@
/* replication_cursor.h - definition for the psycopg replication cursor type
*
* Copyright (C) 2015-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_REPLICATION_CURSOR_H
#define PSYCOPG_REPLICATION_CURSOR_H 1
#include "psycopg/cursor.h"
#include "libpq_support.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject replicationCursorType;
typedef struct replicationCursorObject {
cursorObject cur;
int consuming:1; /* if running the consume loop */
int decode:1; /* if we should use character decoding on the messages */
struct timeval last_io; /* timestamp of the last exchange with the server */
struct timeval status_interval; /* time between status packets sent to the server */
XLogRecPtr write_lsn; /* LSNs for replication feedback messages */
XLogRecPtr flush_lsn;
XLogRecPtr apply_lsn;
XLogRecPtr wal_end; /* WAL end pointer from the last exchange with the server */
XLogRecPtr last_msg_data_start; /* WAL pointer to the last non-keepalive message from the server */
struct timeval last_feedback; /* timestamp of the last feedback message to the server */
XLogRecPtr explicitly_flushed_lsn; /* the flush LSN explicitly set by the send_feedback call */
} replicationCursorObject;
RAISES_NEG HIDDEN int repl_curs_datetime_init(void);
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_REPLICATION_CURSOR_H) */

View File

@ -0,0 +1,394 @@
/* replication_cursor_type.c - python interface to replication cursor objects
*
* Copyright (C) 2015-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/replication_cursor.h"
#include "psycopg/replication_message.h"
#include "psycopg/green.h"
#include "psycopg/pqpath.h"
#include <string.h>
#include <stdlib.h>
/* python */
#include "datetime.h"
static void set_status_interval(replicationCursorObject *self, double status_interval)
{
self->status_interval.tv_sec = (int)status_interval;
self->status_interval.tv_usec = (long)((status_interval - self->status_interval.tv_sec)*1.0e6);
}
#define start_replication_expert_doc \
"start_replication_expert(command, decode=False, status_interval=10) -- Start replication with a given command."
static PyObject *
start_replication_expert(replicationCursorObject *self,
PyObject *args, PyObject *kwargs)
{
cursorObject *curs = &self->cur;
connectionObject *conn = self->cur.conn;
PyObject *res = NULL;
PyObject *command = NULL;
double status_interval = 10;
long int decode = 0;
static char *kwlist[] = {"command", "decode", "status_interval", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|ld", kwlist,
&command, &decode, &status_interval)) {
return NULL;
}
EXC_IF_CURS_CLOSED(curs);
EXC_IF_GREEN(start_replication_expert);
EXC_IF_TPC_PREPARED(conn, start_replication_expert);
if (!(command = curs_validate_sql_basic((cursorObject *)self, command))) {
goto exit;
}
if (status_interval < 1.0) {
psyco_set_error(ProgrammingError, curs, "status_interval must be >= 1 (sec)");
return NULL;
}
Dprintf("start_replication_expert: '%s'; decode: %ld",
Bytes_AS_STRING(command), decode);
if (pq_execute(curs, Bytes_AS_STRING(command), conn->async,
1 /* no_result */, 1 /* no_begin */) >= 0) {
res = Py_None;
Py_INCREF(res);
set_status_interval(self, status_interval);
self->decode = decode;
gettimeofday(&self->last_io, NULL);
}
exit:
Py_XDECREF(command);
return res;
}
#define consume_stream_doc \
"consume_stream(consumer, keepalive_interval=None) -- Consume replication stream."
static PyObject *
consume_stream(replicationCursorObject *self,
PyObject *args, PyObject *kwargs)
{
cursorObject *curs = &self->cur;
PyObject *consume = NULL, *interval = NULL, *res = NULL;
double keepalive_interval = 0;
static char *kwlist[] = {"consume", "keepalive_interval", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist,
&consume, &interval)) {
return NULL;
}
EXC_IF_CURS_CLOSED(curs);
EXC_IF_CURS_ASYNC(curs, consume_stream);
EXC_IF_GREEN(consume_stream);
EXC_IF_TPC_PREPARED(self->cur.conn, consume_stream);
Dprintf("consume_stream");
if (interval && interval != Py_None) {
if (PyFloat_Check(interval)) {
keepalive_interval = PyFloat_AsDouble(interval);
} else if (PyLong_Check(interval)) {
keepalive_interval = PyLong_AsDouble(interval);
} else if (PyInt_Check(interval)) {
keepalive_interval = PyInt_AsLong(interval);
} else {
psyco_set_error(ProgrammingError, curs, "keepalive_interval must be int or float");
return NULL;
}
if (keepalive_interval < 1.0) {
psyco_set_error(ProgrammingError, curs, "keepalive_interval must be >= 1 (sec)");
return NULL;
}
}
if (self->consuming) {
PyErr_SetString(ProgrammingError,
"consume_stream cannot be used when already in the consume loop");
return NULL;
}
if (curs->pgres == NULL || PQresultStatus(curs->pgres) != PGRES_COPY_BOTH) {
PyErr_SetString(ProgrammingError,
"consume_stream: not replicating, call start_replication first");
return NULL;
}
CLEARPGRES(curs->pgres);
self->consuming = 1;
if (keepalive_interval > 0) {
set_status_interval(self, keepalive_interval);
}
if (pq_copy_both(self, consume) >= 0) {
res = Py_None;
Py_INCREF(res);
}
self->consuming = 0;
return res;
}
#define read_message_doc \
"read_message() -- Try reading a replication message from the server (non-blocking)."
static PyObject *
read_message(replicationCursorObject *self, PyObject *dummy)
{
cursorObject *curs = &self->cur;
replicationMessageObject *msg = NULL;
EXC_IF_CURS_CLOSED(curs);
EXC_IF_GREEN(read_message);
EXC_IF_TPC_PREPARED(self->cur.conn, read_message);
if (pq_read_replication_message(self, &msg) < 0) {
return NULL;
}
if (msg) {
return (PyObject *)msg;
}
Py_RETURN_NONE;
}
#define send_feedback_doc \
"send_feedback(write_lsn=0, flush_lsn=0, apply_lsn=0, reply=False, force=False) -- Update a replication feedback, optionally request a reply or force sending a feedback message regardless of the timeout."
static PyObject *
send_feedback(replicationCursorObject *self,
PyObject *args, PyObject *kwargs)
{
cursorObject *curs = &self->cur;
XLogRecPtr write_lsn = 0, flush_lsn = 0, apply_lsn = 0;
int reply = 0, force = 0;
static char* kwlist[] = {"write_lsn", "flush_lsn", "apply_lsn", "reply", "force", NULL};
EXC_IF_CURS_CLOSED(curs);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|KKKii", kwlist,
&write_lsn, &flush_lsn, &apply_lsn, &reply, &force)) {
return NULL;
}
if (write_lsn > self->write_lsn)
self->write_lsn = write_lsn;
if (flush_lsn > self->explicitly_flushed_lsn)
self->explicitly_flushed_lsn = flush_lsn;
if (flush_lsn > self->flush_lsn)
self->flush_lsn = flush_lsn;
if (apply_lsn > self->apply_lsn)
self->apply_lsn = apply_lsn;
if ((force || reply) && pq_send_replication_feedback(self, reply) < 0) {
return NULL;
}
Py_RETURN_NONE;
}
RAISES_NEG int
repl_curs_datetime_init(void)
{
PyDateTime_IMPORT;
if (!PyDateTimeAPI) {
PyErr_SetString(PyExc_ImportError, "datetime initialization failed");
return -1;
}
return 0;
}
#define repl_curs_io_timestamp_doc \
"io_timestamp -- the timestamp of latest IO with the server"
static PyObject *
repl_curs_get_io_timestamp(replicationCursorObject *self)
{
cursorObject *curs = &self->cur;
PyObject *tval, *res = NULL;
double seconds;
EXC_IF_CURS_CLOSED(curs);
seconds = self->last_io.tv_sec + self->last_io.tv_usec / 1.0e6;
tval = Py_BuildValue("(d)", seconds);
if (tval) {
res = PyDateTime_FromTimestamp(tval);
Py_DECREF(tval);
}
return res;
}
#define repl_curs_feedback_timestamp_doc \
"feedback_timestamp -- the timestamp of the latest feedback message sent to the server"
static PyObject *
repl_curs_get_feedback_timestamp(replicationCursorObject *self)
{
cursorObject *curs = &self->cur;
PyObject *tval, *res = NULL;
double seconds;
EXC_IF_CURS_CLOSED(curs);
seconds = self->last_feedback.tv_sec + self->last_feedback.tv_usec / 1.0e6;
tval = Py_BuildValue("(d)", seconds);
if (tval) {
res = PyDateTime_FromTimestamp(tval);
Py_DECREF(tval);
}
return res;
}
/* object member list */
#define OFFSETOF(x) offsetof(replicationCursorObject, x)
static struct PyMemberDef replicationCursorObject_members[] = {
{"wal_end", T_ULONGLONG, OFFSETOF(wal_end), READONLY,
"LSN position of the current end of WAL on the server."},
{NULL}
};
/* object method list */
static struct PyMethodDef replicationCursorObject_methods[] = {
{"start_replication_expert", (PyCFunction)start_replication_expert,
METH_VARARGS|METH_KEYWORDS, start_replication_expert_doc},
{"consume_stream", (PyCFunction)consume_stream,
METH_VARARGS|METH_KEYWORDS, consume_stream_doc},
{"read_message", (PyCFunction)read_message,
METH_NOARGS, read_message_doc},
{"send_feedback", (PyCFunction)send_feedback,
METH_VARARGS|METH_KEYWORDS, send_feedback_doc},
{NULL}
};
/* object calculated member list */
static struct PyGetSetDef replicationCursorObject_getsets[] = {
{ "io_timestamp",
(getter)repl_curs_get_io_timestamp, NULL,
repl_curs_io_timestamp_doc, NULL },
{ "feedback_timestamp",
(getter)repl_curs_get_feedback_timestamp, NULL,
repl_curs_feedback_timestamp_doc, NULL },
{NULL}
};
static int
replicationCursor_init(PyObject *obj, PyObject *args, PyObject *kwargs)
{
replicationCursorObject *self = (replicationCursorObject *)obj;
self->consuming = 0;
self->decode = 0;
self->wal_end = 0;
self->write_lsn = 0;
self->flush_lsn = 0;
self->apply_lsn = 0;
return cursorType.tp_init(obj, args, kwargs);
}
static PyObject *
replicationCursor_repr(replicationCursorObject *self)
{
return PyString_FromFormat(
"<ReplicationCursor object at %p; closed: %d>", self, self->cur.closed);
}
/* object type */
#define replicationCursorType_doc \
"A database replication cursor."
PyTypeObject replicationCursorType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.ReplicationCursor",
sizeof(replicationCursorObject), 0,
0, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)replicationCursor_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
(reprfunc)replicationCursor_repr, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER |
Py_TPFLAGS_HAVE_GC, /*tp_flags*/
replicationCursorType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
replicationCursorObject_methods, /*tp_methods*/
replicationCursorObject_members, /*tp_members*/
replicationCursorObject_getsets, /*tp_getset*/
&cursorType, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
replicationCursor_init, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
};

View File

@ -0,0 +1,58 @@
/* replication_message.h - definition for the psycopg ReplicationMessage type
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_REPLICATION_MESSAGE_H
#define PSYCOPG_REPLICATION_MESSAGE_H 1
#include "cursor.h"
#include "libpq_support.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject replicationMessageType;
/* the typedef is forward-declared in psycopg.h */
struct replicationMessageObject {
PyObject_HEAD
cursorObject *cursor;
PyObject *payload;
int data_size;
XLogRecPtr data_start;
XLogRecPtr wal_end;
int64_t send_time;
};
RAISES_NEG HIDDEN int replmsg_datetime_init(void);
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_REPLICATION_MESSAGE_H) */

View File

@ -0,0 +1,195 @@
/* replication_message_type.c - python interface to ReplcationMessage objects
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/replication_message.h"
#include "datetime.h"
RAISES_NEG int
replmsg_datetime_init(void)
{
PyDateTime_IMPORT;
if (!PyDateTimeAPI) {
PyErr_SetString(PyExc_ImportError, "datetime initialization failed");
return -1;
}
return 0;
}
static PyObject *
replmsg_repr(replicationMessageObject *self)
{
return PyString_FromFormat(
"<ReplicationMessage object at %p; data_size: %d; "
"data_start: "XLOGFMTSTR"; wal_end: "XLOGFMTSTR"; send_time: %ld>",
self, self->data_size, XLOGFMTARGS(self->data_start), XLOGFMTARGS(self->wal_end),
(long int)self->send_time);
}
static int
replmsg_init(PyObject *obj, PyObject *args, PyObject *kwargs)
{
PyObject *cur = NULL;
replicationMessageObject *self = (replicationMessageObject *)obj;
if (!PyArg_ParseTuple(
args, "O!O", &cursorType, &cur, &self->payload)) {
return -1;
}
Py_INCREF(cur);
self->cursor = (cursorObject *)cur;
Py_INCREF(self->payload);
self->data_size = 0;
self->data_start = 0;
self->wal_end = 0;
self->send_time = 0;
return 0;
}
static int
replmsg_traverse(replicationMessageObject *self, visitproc visit, void *arg)
{
Py_VISIT((PyObject *)self->cursor);
Py_VISIT(self->payload);
return 0;
}
static int
replmsg_clear(replicationMessageObject *self)
{
Py_CLEAR(self->cursor);
Py_CLEAR(self->payload);
return 0;
}
static void
replmsg_dealloc(PyObject* obj)
{
PyObject_GC_UnTrack(obj);
replmsg_clear((replicationMessageObject*) obj);
Py_TYPE(obj)->tp_free(obj);
}
#define replmsg_send_time_doc \
"send_time - Timestamp of the replication message departure from the server."
static PyObject *
replmsg_get_send_time(replicationMessageObject *self)
{
PyObject *tval, *res = NULL;
double t;
t = (double)self->send_time / USECS_PER_SEC +
((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
tval = Py_BuildValue("(d)", t);
if (tval) {
res = PyDateTime_FromTimestamp(tval);
Py_DECREF(tval);
}
return res;
}
#define OFFSETOF(x) offsetof(replicationMessageObject, x)
/* object member list */
static struct PyMemberDef replicationMessageObject_members[] = {
{"cursor", T_OBJECT, OFFSETOF(cursor), READONLY,
"Related ReplcationCursor object."},
{"payload", T_OBJECT, OFFSETOF(payload), READONLY,
"The actual message data."},
{"data_size", T_INT, OFFSETOF(data_size), READONLY,
"Raw size of the message data in bytes."},
{"data_start", T_ULONGLONG, OFFSETOF(data_start), READONLY,
"LSN position of the start of this message."},
{"wal_end", T_ULONGLONG, OFFSETOF(wal_end), READONLY,
"LSN position of the current end of WAL on the server."},
{NULL}
};
static struct PyGetSetDef replicationMessageObject_getsets[] = {
{ "send_time", (getter)replmsg_get_send_time, NULL,
replmsg_send_time_doc, NULL },
{NULL}
};
/* object type */
#define replicationMessageType_doc \
"A replication protocol message."
PyTypeObject replicationMessageType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.ReplicationMessage",
sizeof(replicationMessageObject), 0,
replmsg_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)replmsg_repr, /*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 |
Py_TPFLAGS_HAVE_GC, /*tp_flags*/
replicationMessageType_doc, /*tp_doc*/
(traverseproc)replmsg_traverse, /*tp_traverse*/
(inquiry)replmsg_clear, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
replicationMessageObject_members, /*tp_members*/
replicationMessageObject_getsets, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
replmsg_init, /*tp_init*/
0, /*tp_alloc*/
PyType_GenericNew, /*tp_new*/
};

58
psycopg/solaris_support.c Normal file
View File

@ -0,0 +1,58 @@
/* solaris_support.c - emulate functions missing on Solaris
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018, Joyent, Inc.
* Copyright (C) 2020-2021 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/solaris_support.h"
#if defined(__sun) && defined(__SVR4)
/* timeradd is missing on Solaris 10 */
#ifndef timeradd
void
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec + b->tv_sec;
c->tv_usec = a->tv_usec + b->tv_usec;
if (c->tv_usec >= 1000000) {
c->tv_usec -= 1000000;
c->tv_sec += 1;
}
}
/* timersub is missing on Solaris */
void
timersub(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec - b->tv_sec;
c->tv_usec = a->tv_usec - b->tv_usec;
if (c->tv_usec < 0) {
c->tv_usec += 1000000;
c->tv_sec -= 1;
}
}
#endif /* timeradd */
#endif /* defined(__sun) && defined(__SVR4) */

48
psycopg/solaris_support.h Normal file
View File

@ -0,0 +1,48 @@
/* solaris_support.h - definitions for solaris_support.c
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018-2019, Joyent, Inc.
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_SOLARIS_SUPPORT_H
#define PSYCOPG_SOLARIS_SUPPORT_H
#include "psycopg/config.h"
#if defined(__sun) && defined(__SVR4)
#include <sys/time.h>
#ifndef timeradd
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
#endif
#ifndef timercmp
#define timercmp(a, b, cmp) \
(((a)->tv_sec == (b)->tv_sec) ? \
((a)->tv_usec cmp (b)->tv_usec) : \
((a)->tv_sec cmp (b)->tv_sec))
#endif
#endif
#endif /* !defined(PSYCOPG_SOLARIS_SUPPORT_H) */

335
psycopg/sqlstate_errors.h Normal file
View File

@ -0,0 +1,335 @@
/*
* Autogenerated by 'scripts/make_errors.py'.
*/
/* Class 02 - No Data (this is also a warning class per the SQL standard) */
{"02000", "NoData"},
{"02001", "NoAdditionalDynamicResultSetsReturned"},
/* Class 03 - SQL Statement Not Yet Complete */
{"03000", "SqlStatementNotYetComplete"},
/* Class 08 - Connection Exception */
{"08000", "ConnectionException"},
{"08001", "SqlclientUnableToEstablishSqlconnection"},
{"08003", "ConnectionDoesNotExist"},
{"08004", "SqlserverRejectedEstablishmentOfSqlconnection"},
{"08006", "ConnectionFailure"},
{"08007", "TransactionResolutionUnknown"},
{"08P01", "ProtocolViolation"},
/* Class 09 - Triggered Action Exception */
{"09000", "TriggeredActionException"},
/* Class 0A - Feature Not Supported */
{"0A000", "FeatureNotSupported"},
/* Class 0B - Invalid Transaction Initiation */
{"0B000", "InvalidTransactionInitiation"},
/* Class 0F - Locator Exception */
{"0F000", "LocatorException"},
{"0F001", "InvalidLocatorSpecification"},
/* Class 0L - Invalid Grantor */
{"0L000", "InvalidGrantor"},
{"0LP01", "InvalidGrantOperation"},
/* Class 0P - Invalid Role Specification */
{"0P000", "InvalidRoleSpecification"},
/* Class 0Z - Diagnostics Exception */
{"0Z000", "DiagnosticsException"},
{"0Z002", "StackedDiagnosticsAccessedWithoutActiveHandler"},
/* Class 20 - Case Not Found */
{"20000", "CaseNotFound"},
/* Class 21 - Cardinality Violation */
{"21000", "CardinalityViolation"},
/* Class 22 - Data Exception */
{"22000", "DataException"},
{"22001", "StringDataRightTruncation"},
{"22002", "NullValueNoIndicatorParameter"},
{"22003", "NumericValueOutOfRange"},
{"22004", "NullValueNotAllowed"},
{"22005", "ErrorInAssignment"},
{"22007", "InvalidDatetimeFormat"},
{"22008", "DatetimeFieldOverflow"},
{"22009", "InvalidTimeZoneDisplacementValue"},
{"2200B", "EscapeCharacterConflict"},
{"2200C", "InvalidUseOfEscapeCharacter"},
{"2200D", "InvalidEscapeOctet"},
{"2200F", "ZeroLengthCharacterString"},
{"2200G", "MostSpecificTypeMismatch"},
{"2200H", "SequenceGeneratorLimitExceeded"},
{"2200L", "NotAnXmlDocument"},
{"2200M", "InvalidXmlDocument"},
{"2200N", "InvalidXmlContent"},
{"2200S", "InvalidXmlComment"},
{"2200T", "InvalidXmlProcessingInstruction"},
{"22010", "InvalidIndicatorParameterValue"},
{"22011", "SubstringError"},
{"22012", "DivisionByZero"},
{"22013", "InvalidPrecedingOrFollowingSize"},
{"22014", "InvalidArgumentForNtileFunction"},
{"22015", "IntervalFieldOverflow"},
{"22016", "InvalidArgumentForNthValueFunction"},
{"22018", "InvalidCharacterValueForCast"},
{"22019", "InvalidEscapeCharacter"},
{"2201B", "InvalidRegularExpression"},
{"2201E", "InvalidArgumentForLogarithm"},
{"2201F", "InvalidArgumentForPowerFunction"},
{"2201G", "InvalidArgumentForWidthBucketFunction"},
{"2201W", "InvalidRowCountInLimitClause"},
{"2201X", "InvalidRowCountInResultOffsetClause"},
{"22021", "CharacterNotInRepertoire"},
{"22022", "IndicatorOverflow"},
{"22023", "InvalidParameterValue"},
{"22024", "UnterminatedCString"},
{"22025", "InvalidEscapeSequence"},
{"22026", "StringDataLengthMismatch"},
{"22027", "TrimError"},
{"2202E", "ArraySubscriptError"},
{"2202G", "InvalidTablesampleRepeat"},
{"2202H", "InvalidTablesampleArgument"},
{"22030", "DuplicateJsonObjectKeyValue"},
{"22031", "InvalidArgumentForSqlJsonDatetimeFunction"},
{"22032", "InvalidJsonText"},
{"22033", "InvalidSqlJsonSubscript"},
{"22034", "MoreThanOneSqlJsonItem"},
{"22035", "NoSqlJsonItem"},
{"22036", "NonNumericSqlJsonItem"},
{"22037", "NonUniqueKeysInAJsonObject"},
{"22038", "SingletonSqlJsonItemRequired"},
{"22039", "SqlJsonArrayNotFound"},
{"2203A", "SqlJsonMemberNotFound"},
{"2203B", "SqlJsonNumberNotFound"},
{"2203C", "SqlJsonObjectNotFound"},
{"2203D", "TooManyJsonArrayElements"},
{"2203E", "TooManyJsonObjectMembers"},
{"2203F", "SqlJsonScalarRequired"},
{"22P01", "FloatingPointException"},
{"22P02", "InvalidTextRepresentation"},
{"22P03", "InvalidBinaryRepresentation"},
{"22P04", "BadCopyFileFormat"},
{"22P05", "UntranslatableCharacter"},
{"22P06", "NonstandardUseOfEscapeCharacter"},
/* Class 23 - Integrity Constraint Violation */
{"23000", "IntegrityConstraintViolation"},
{"23001", "RestrictViolation"},
{"23502", "NotNullViolation"},
{"23503", "ForeignKeyViolation"},
{"23505", "UniqueViolation"},
{"23514", "CheckViolation"},
{"23P01", "ExclusionViolation"},
/* Class 24 - Invalid Cursor State */
{"24000", "InvalidCursorState"},
/* Class 25 - Invalid Transaction State */
{"25000", "InvalidTransactionState"},
{"25001", "ActiveSqlTransaction"},
{"25002", "BranchTransactionAlreadyActive"},
{"25003", "InappropriateAccessModeForBranchTransaction"},
{"25004", "InappropriateIsolationLevelForBranchTransaction"},
{"25005", "NoActiveSqlTransactionForBranchTransaction"},
{"25006", "ReadOnlySqlTransaction"},
{"25007", "SchemaAndDataStatementMixingNotSupported"},
{"25008", "HeldCursorRequiresSameIsolationLevel"},
{"25P01", "NoActiveSqlTransaction"},
{"25P02", "InFailedSqlTransaction"},
{"25P03", "IdleInTransactionSessionTimeout"},
/* Class 26 - Invalid SQL Statement Name */
{"26000", "InvalidSqlStatementName"},
/* Class 27 - Triggered Data Change Violation */
{"27000", "TriggeredDataChangeViolation"},
/* Class 28 - Invalid Authorization Specification */
{"28000", "InvalidAuthorizationSpecification"},
{"28P01", "InvalidPassword"},
/* Class 2B - Dependent Privilege Descriptors Still Exist */
{"2B000", "DependentPrivilegeDescriptorsStillExist"},
{"2BP01", "DependentObjectsStillExist"},
/* Class 2D - Invalid Transaction Termination */
{"2D000", "InvalidTransactionTermination"},
/* Class 2F - SQL Routine Exception */
{"2F000", "SqlRoutineException"},
{"2F002", "ModifyingSqlDataNotPermitted"},
{"2F003", "ProhibitedSqlStatementAttempted"},
{"2F004", "ReadingSqlDataNotPermitted"},
{"2F005", "FunctionExecutedNoReturnStatement"},
/* Class 34 - Invalid Cursor Name */
{"34000", "InvalidCursorName"},
/* Class 38 - External Routine Exception */
{"38000", "ExternalRoutineException"},
{"38001", "ContainingSqlNotPermitted"},
{"38002", "ModifyingSqlDataNotPermittedExt"},
{"38003", "ProhibitedSqlStatementAttemptedExt"},
{"38004", "ReadingSqlDataNotPermittedExt"},
/* Class 39 - External Routine Invocation Exception */
{"39000", "ExternalRoutineInvocationException"},
{"39001", "InvalidSqlstateReturned"},
{"39004", "NullValueNotAllowedExt"},
{"39P01", "TriggerProtocolViolated"},
{"39P02", "SrfProtocolViolated"},
{"39P03", "EventTriggerProtocolViolated"},
/* Class 3B - Savepoint Exception */
{"3B000", "SavepointException"},
{"3B001", "InvalidSavepointSpecification"},
/* Class 3D - Invalid Catalog Name */
{"3D000", "InvalidCatalogName"},
/* Class 3F - Invalid Schema Name */
{"3F000", "InvalidSchemaName"},
/* Class 40 - Transaction Rollback */
{"40000", "TransactionRollback"},
{"40001", "SerializationFailure"},
{"40002", "TransactionIntegrityConstraintViolation"},
{"40003", "StatementCompletionUnknown"},
{"40P01", "DeadlockDetected"},
/* Class 42 - Syntax Error or Access Rule Violation */
{"42000", "SyntaxErrorOrAccessRuleViolation"},
{"42501", "InsufficientPrivilege"},
{"42601", "SyntaxError"},
{"42602", "InvalidName"},
{"42611", "InvalidColumnDefinition"},
{"42622", "NameTooLong"},
{"42701", "DuplicateColumn"},
{"42702", "AmbiguousColumn"},
{"42703", "UndefinedColumn"},
{"42704", "UndefinedObject"},
{"42710", "DuplicateObject"},
{"42712", "DuplicateAlias"},
{"42723", "DuplicateFunction"},
{"42725", "AmbiguousFunction"},
{"42803", "GroupingError"},
{"42804", "DatatypeMismatch"},
{"42809", "WrongObjectType"},
{"42830", "InvalidForeignKey"},
{"42846", "CannotCoerce"},
{"42883", "UndefinedFunction"},
{"428C9", "GeneratedAlways"},
{"42939", "ReservedName"},
{"42P01", "UndefinedTable"},
{"42P02", "UndefinedParameter"},
{"42P03", "DuplicateCursor"},
{"42P04", "DuplicateDatabase"},
{"42P05", "DuplicatePreparedStatement"},
{"42P06", "DuplicateSchema"},
{"42P07", "DuplicateTable"},
{"42P08", "AmbiguousParameter"},
{"42P09", "AmbiguousAlias"},
{"42P10", "InvalidColumnReference"},
{"42P11", "InvalidCursorDefinition"},
{"42P12", "InvalidDatabaseDefinition"},
{"42P13", "InvalidFunctionDefinition"},
{"42P14", "InvalidPreparedStatementDefinition"},
{"42P15", "InvalidSchemaDefinition"},
{"42P16", "InvalidTableDefinition"},
{"42P17", "InvalidObjectDefinition"},
{"42P18", "IndeterminateDatatype"},
{"42P19", "InvalidRecursion"},
{"42P20", "WindowingError"},
{"42P21", "CollationMismatch"},
{"42P22", "IndeterminateCollation"},
/* Class 44 - WITH CHECK OPTION Violation */
{"44000", "WithCheckOptionViolation"},
/* Class 53 - Insufficient Resources */
{"53000", "InsufficientResources"},
{"53100", "DiskFull"},
{"53200", "OutOfMemory"},
{"53300", "TooManyConnections"},
{"53400", "ConfigurationLimitExceeded"},
/* Class 54 - Program Limit Exceeded */
{"54000", "ProgramLimitExceeded"},
{"54001", "StatementTooComplex"},
{"54011", "TooManyColumns"},
{"54023", "TooManyArguments"},
/* Class 55 - Object Not In Prerequisite State */
{"55000", "ObjectNotInPrerequisiteState"},
{"55006", "ObjectInUse"},
{"55P02", "CantChangeRuntimeParam"},
{"55P03", "LockNotAvailable"},
{"55P04", "UnsafeNewEnumValueUsage"},
/* Class 57 - Operator Intervention */
{"57000", "OperatorIntervention"},
{"57014", "QueryCanceled"},
{"57P01", "AdminShutdown"},
{"57P02", "CrashShutdown"},
{"57P03", "CannotConnectNow"},
{"57P04", "DatabaseDropped"},
/* Class 58 - System Error (errors external to PostgreSQL itself) */
{"58000", "SystemError"},
{"58030", "IoError"},
{"58P01", "UndefinedFile"},
{"58P02", "DuplicateFile"},
/* Class 72 - Snapshot Failure */
{"72000", "SnapshotTooOld"},
/* Class F0 - Configuration File Error */
{"F0000", "ConfigFileError"},
{"F0001", "LockFileExists"},
/* Class HV - Foreign Data Wrapper Error (SQL/MED) */
{"HV000", "FdwError"},
{"HV001", "FdwOutOfMemory"},
{"HV002", "FdwDynamicParameterValueNeeded"},
{"HV004", "FdwInvalidDataType"},
{"HV005", "FdwColumnNameNotFound"},
{"HV006", "FdwInvalidDataTypeDescriptors"},
{"HV007", "FdwInvalidColumnName"},
{"HV008", "FdwInvalidColumnNumber"},
{"HV009", "FdwInvalidUseOfNullPointer"},
{"HV00A", "FdwInvalidStringFormat"},
{"HV00B", "FdwInvalidHandle"},
{"HV00C", "FdwInvalidOptionIndex"},
{"HV00D", "FdwInvalidOptionName"},
{"HV00J", "FdwOptionNameNotFound"},
{"HV00K", "FdwReplyHandle"},
{"HV00L", "FdwUnableToCreateExecution"},
{"HV00M", "FdwUnableToCreateReply"},
{"HV00N", "FdwUnableToEstablishConnection"},
{"HV00P", "FdwNoSchemas"},
{"HV00Q", "FdwSchemaNotFound"},
{"HV00R", "FdwTableNotFound"},
{"HV010", "FdwFunctionSequenceError"},
{"HV014", "FdwTooManyHandles"},
{"HV021", "FdwInconsistentDescriptorInformation"},
{"HV024", "FdwInvalidAttributeValue"},
{"HV090", "FdwInvalidStringLengthOrBufferLength"},
{"HV091", "FdwInvalidDescriptorFieldIdentifier"},
/* Class P0 - PL/pgSQL Error */
{"P0000", "PlpgsqlError"},
{"P0001", "RaiseException"},
{"P0002", "NoDataFound"},
{"P0003", "TooManyRows"},
{"P0004", "AssertFailure"},
/* Class XX - Internal Error */
{"XX000", "InternalError_"},
{"XX001", "DataCorrupted"},
{"XX002", "IndexCorrupted"},

620
psycopg/typecast.c Normal file
View File

@ -0,0 +1,620 @@
/* typecast.c - basic utility functions related to typecasting
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/typecast.h"
#include "psycopg/cursor.h"
/* useful function used by some typecasters */
static const char *
skip_until_space2(const char *s, Py_ssize_t *len)
{
while (*len > 0 && *s && *s != ' ') {
s++; (*len)--;
}
return s;
}
static int
typecast_parse_date(const char* s, const char** t, Py_ssize_t* len,
int* year, int* month, int* day)
{
int acc = -1, cz = 0;
Dprintf("typecast_parse_date: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s",
*len, s);
while (cz < 3 && *len > 0 && *s) {
switch (*s) {
case '-':
case ' ':
case 'T':
if (cz == 0) *year = acc;
else if (cz == 1) *month = acc;
else if (cz == 2) *day = acc;
acc = -1; cz++;
break;
default:
acc = (acc == -1 ? 0 : acc*10) + ((int)*s - (int)'0');
break;
}
s++; (*len)--;
}
if (acc != -1) {
*day = acc;
cz += 1;
}
/* Is this a BC date? If so, adjust the year value. However
* Python datetime module does not support BC dates, so this will raise
* an exception downstream. */
if (*len >= 2 && s[*len-2] == 'B' && s[*len-1] == 'C')
*year = -(*year);
if (t != NULL) *t = s;
return cz;
}
static int
typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
int* hh, int* mm, int* ss, int* us, int* tz)
{
int acc = -1, cz = 0;
int tzsign = 1, tzhh = 0, tzmm = 0, tzss = 0;
int usd = 0;
/* sets microseconds and timezone to 0 because they may be missing */
*us = *tz = 0;
Dprintf("typecast_parse_time: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s",
*len, s);
while (cz < 7 && *len > 0 && *s) {
switch (*s) {
case ':':
if (cz == 0) *hh = acc;
else if (cz == 1) *mm = acc;
else if (cz == 2) *ss = acc;
else if (cz == 3) *us = acc;
else if (cz == 4) tzhh = acc;
else if (cz == 5) tzmm = acc;
acc = -1; cz++;
break;
case '.':
/* we expect seconds and if we don't get them we return an error */
if (cz != 2) return -1;
*ss = acc;
acc = -1; cz++;
break;
case '+':
case '-':
/* seconds or microseconds here, anything else is an error */
if (cz < 2 || cz > 3) return -1;
if (*s == '-') tzsign = -1;
if (cz == 2) *ss = acc;
else if (cz == 3) *us = acc;
acc = -1; cz = 4;
break;
case ' ':
case 'B':
case 'C':
/* Ignore the " BC" suffix, if passed -- it is handled
* when parsing the date portion. */
break;
default:
acc = (acc == -1 ? 0 : acc*10) + ((int)*s - (int)'0');
if (cz == 3) usd += 1;
break;
}
s++; (*len)--;
}
if (acc != -1) {
if (cz == 0) { *hh = acc; cz += 1; }
else if (cz == 1) { *mm = acc; cz += 1; }
else if (cz == 2) { *ss = acc; cz += 1; }
else if (cz == 3) { *us = acc; cz += 1; }
else if (cz == 4) { tzhh = acc; cz += 1; }
else if (cz == 5) { tzmm = acc; cz += 1; }
else if (cz == 6) tzss = acc;
}
if (t != NULL) *t = s;
*tz = tzsign * (3600 * tzhh + 60 * tzmm + tzss);
if (*us != 0) {
while (usd++ < 6) *us *= 10;
}
/* 24:00:00 -> 00:00:00 (ticket #278) */
if (*hh == 24) { *hh = 0; }
return cz;
}
/** include casting objects **/
#include "psycopg/typecast_basic.c"
#include "psycopg/typecast_binary.c"
#include "psycopg/typecast_datetime.c"
#include "psycopg/typecast_array.c"
static long int typecast_default_DEFAULT[] = {0};
static typecastObject_initlist typecast_default = {
"DEFAULT", typecast_default_DEFAULT, typecast_STRING_cast};
static PyObject *
typecast_UNKNOWN_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
Dprintf("typecast_UNKNOWN_cast: str = '%s',"
" len = " FORMAT_CODE_PY_SSIZE_T, str, len);
return typecast_default.cast(str, len, curs);
}
#include "psycopg/typecast_builtins.c"
#define typecast_PYDATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYDATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYDATEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYTIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYINTERVALARRAY_cast typecast_GENERIC_ARRAY_cast
/* a list of initializers, used to make the typecasters accessible anyway */
static typecastObject_initlist typecast_pydatetime[] = {
{"PYDATETIME", typecast_DATETIME_types, typecast_PYDATETIME_cast},
{"PYDATETIMETZ", typecast_DATETIMETZ_types, typecast_PYDATETIMETZ_cast},
{"PYTIME", typecast_TIME_types, typecast_PYTIME_cast},
{"PYDATE", typecast_DATE_types, typecast_PYDATE_cast},
{"PYINTERVAL", typecast_INTERVAL_types, typecast_PYINTERVAL_cast},
{"PYDATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_PYDATETIMEARRAY_cast, "PYDATETIME"},
{"PYDATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_PYDATETIMETZARRAY_cast, "PYDATETIMETZ"},
{"PYTIMEARRAY", typecast_TIMEARRAY_types, typecast_PYTIMEARRAY_cast, "PYTIME"},
{"PYDATEARRAY", typecast_DATEARRAY_types, typecast_PYDATEARRAY_cast, "PYDATE"},
{"PYINTERVALARRAY", typecast_INTERVALARRAY_types, typecast_PYINTERVALARRAY_cast, "PYINTERVAL"},
{NULL, NULL, NULL}
};
/** the type dictionary and associated functions **/
PyObject *psyco_types;
PyObject *psyco_default_cast;
PyObject *psyco_binary_types;
PyObject *psyco_default_binary_cast;
/* typecast_init - initialize the dictionary and create default types */
RAISES_NEG int
typecast_init(PyObject *module)
{
int i;
int rv = -1;
typecastObject *t = NULL;
PyObject *dict = NULL;
if (!(dict = PyModule_GetDict(module))) { goto exit; }
/* create type dictionary and put it in module namespace */
if (!(psyco_types = PyDict_New())) { goto exit; }
PyDict_SetItemString(dict, "string_types", psyco_types);
if (!(psyco_binary_types = PyDict_New())) { goto exit; }
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++) {
t = (typecastObject *)typecast_from_c(&(typecast_builtins[i]), dict);
if (t == NULL) { goto exit; }
if (typecast_add((PyObject *)t, NULL, 0) < 0) { goto exit; }
PyDict_SetItem(dict, t->name, (PyObject *)t);
/* export binary object */
if (typecast_builtins[i].values == typecast_BINARY_types) {
Py_INCREF((PyObject *)t);
psyco_default_binary_cast = (PyObject *)t;
}
Py_DECREF((PyObject *)t);
t = NULL;
}
/* create and save a default cast object (but do not register it) */
psyco_default_cast = typecast_from_c(&typecast_default, dict);
/* register the date/time typecasters with their original names */
if (0 > typecast_datetime_init()) { goto exit; }
for (i = 0; typecast_pydatetime[i].name != NULL; i++) {
t = (typecastObject *)typecast_from_c(&(typecast_pydatetime[i]), dict);
if (t == NULL) { goto exit; }
PyDict_SetItem(dict, t->name, (PyObject *)t);
Py_DECREF((PyObject *)t);
t = NULL;
}
rv = 0;
exit:
Py_XDECREF((PyObject *)t);
return rv;
}
/* typecast_add - add a type object to the dictionary */
RAISES_NEG int
typecast_add(PyObject *obj, PyObject *dict, int binary)
{
PyObject *val;
Py_ssize_t len, i;
typecastObject *type = (typecastObject *)obj;
if (dict == NULL)
dict = (binary ? psyco_binary_types : psyco_types);
len = PyTuple_Size(type->values);
for (i = 0; i < len; i++) {
val = PyTuple_GetItem(type->values, i);
PyDict_SetItem(dict, val, obj);
}
return 0;
}
/** typecast type **/
#define OFFSETOF(x) offsetof(typecastObject, x)
static int
typecast_cmp(PyObject *obj1, PyObject* obj2)
{
typecastObject *self = (typecastObject*)obj1;
typecastObject *other = NULL;
PyObject *number = NULL;
Py_ssize_t i, j;
int res = -1;
if (PyObject_TypeCheck(obj2, &typecastType)) {
other = (typecastObject*)obj2;
}
else {
number = PyNumber_Int(obj2);
}
Dprintf("typecast_cmp: other = %p, number = %p", other, number);
for (i=0; i < PyObject_Length(self->values) && res == -1; i++) {
long int val = PyInt_AsLong(PyTuple_GET_ITEM(self->values, i));
if (other != NULL) {
for (j=0; j < PyObject_Length(other->values); j++) {
if (PyInt_AsLong(PyTuple_GET_ITEM(other->values, j)) == val) {
res = 0; break;
}
}
}
else if (number != NULL) {
if (PyInt_AsLong(number) == val) {
res = 0; break;
}
}
}
Py_XDECREF(number);
return res;
}
static PyObject*
typecast_richcompare(PyObject *obj1, PyObject* obj2, int opid)
{
int res = typecast_cmp(obj1, obj2);
if (PyErr_Occurred()) return NULL;
return PyBool_FromLong((opid == Py_EQ && res == 0) || (opid != Py_EQ && res != 0));
}
static struct PyMemberDef typecastObject_members[] = {
{"name", T_OBJECT, OFFSETOF(name), READONLY},
{"values", T_OBJECT, OFFSETOF(values), READONLY},
{NULL}
};
static int
typecast_clear(typecastObject *self)
{
Py_CLEAR(self->values);
Py_CLEAR(self->name);
Py_CLEAR(self->pcast);
Py_CLEAR(self->bcast);
return 0;
}
static void
typecast_dealloc(typecastObject *self)
{
PyObject_GC_UnTrack(self);
typecast_clear(self);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static int
typecast_traverse(typecastObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->values);
Py_VISIT(self->name);
Py_VISIT(self->pcast);
Py_VISIT(self->bcast);
return 0;
}
static PyObject *
typecast_repr(PyObject *self)
{
PyObject *name = ((typecastObject *)self)->name;
PyObject *rv;
Py_INCREF(name);
if (!(name = psyco_ensure_bytes(name))) {
return NULL;
}
rv = PyString_FromFormat("<%s '%s' at %p>",
Py_TYPE(self)->tp_name, Bytes_AS_STRING(name), self);
Py_DECREF(name);
return rv;
}
static PyObject *
typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs)
{
const char *string;
Py_ssize_t length;
PyObject *cursor;
if (!PyArg_ParseTuple(args, "z#O", &string, &length, &cursor)) {
return NULL;
}
// If the string is not a string but a None value we're being called
// from a Python-defined caster.
if (!string) {
Py_RETURN_NONE;
}
return typecast_cast(obj, string, length, cursor);
}
PyTypeObject typecastType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.type",
sizeof(typecastObject), 0,
(destructor)typecast_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_reserved*/
typecast_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
typecast_call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_RICHCOMPARE |
Py_TPFLAGS_HAVE_GC, /*tp_flags*/
"psycopg type-casting object", /*tp_doc*/
(traverseproc)typecast_traverse, /*tp_traverse*/
(inquiry)typecast_clear, /*tp_clear*/
typecast_richcompare, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
typecastObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
0, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
};
static PyObject *
typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base)
{
typecastObject *obj;
obj = PyObject_GC_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;
obj->bcast = base;
if (obj->bcast) Py_INCREF(obj->bcast);
/* FIXME: raise an exception when None is passed as Python caster */
if (cast && cast != Py_None) {
Py_INCREF(cast);
obj->pcast = cast;
}
PyObject_GC_Track(obj);
return (PyObject *)obj;
}
PyObject *
typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds)
{
PyObject *v, *name = NULL, *cast = NULL, *base = NULL;
static char *kwlist[] = {"values", "name", "castobj", "baseobj", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!OO", kwlist,
&PyTuple_Type, &v,
&Text_Type, &name,
&cast, &base)) {
return NULL;
}
return typecast_new(name, v, cast, base);
}
PyObject *
typecast_array_from_python(PyObject *self, PyObject *args, PyObject *keywds)
{
PyObject *values, *name = NULL, *base = NULL;
typecastObject *obj = NULL;
static char *kwlist[] = {"values", "name", "baseobj", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O!O!", kwlist,
&PyTuple_Type, &values,
&Text_Type, &name,
&typecastType, &base)) {
return NULL;
}
if ((obj = (typecastObject *)typecast_new(name, values, NULL, base))) {
obj->ccast = typecast_GENERIC_ARRAY_cast;
obj->pcast = NULL;
}
return (PyObject *)obj;
}
PyObject *
typecast_from_c(typecastObject_initlist *type, PyObject *dict)
{
PyObject *name = NULL, *values = NULL, *base = NULL;
typecastObject *obj = NULL;
Py_ssize_t i, len = 0;
/* before doing anything else we look for the base */
if (type->base) {
/* NOTE: base is a borrowed reference! */
base = PyDict_GetItemString(dict, type->base);
if (!base) {
PyErr_Format(Error, "typecast base not found: %s", type->base);
goto end;
}
}
name = Text_FromUTF8(type->name);
if (!name) goto end;
while (type->values[len] != 0) len++;
values = PyTuple_New(len);
if (!values) goto end;
for (i = 0; i < len ; i++) {
PyTuple_SET_ITEM(values, i, PyInt_FromLong(type->values[i]));
}
obj = (typecastObject *)typecast_new(name, values, NULL, base);
if (obj) {
obj->ccast = type->cast;
obj->pcast = NULL;
}
end:
Py_XDECREF(values);
Py_XDECREF(name);
return (PyObject *)obj;
}
PyObject *
typecast_cast(PyObject *obj, const char *str, Py_ssize_t len, PyObject *curs)
{
PyObject *old, *res = NULL;
typecastObject *self = (typecastObject *)obj;
Py_INCREF(obj);
old = ((cursorObject*)curs)->caster;
((cursorObject*)curs)->caster = obj;
if (self->ccast) {
res = self->ccast(str, len, curs);
}
else if (self->pcast) {
PyObject *s;
/* XXX we have bytes in the adapters and strings in the typecasters.
* are you sure this is ok?
* Notice that this way it is about impossible to create a python
* typecaster on a binary type. */
if (str) {
s = conn_decode(((cursorObject *)curs)->conn, str, len);
}
else {
Py_INCREF(Py_None);
s = Py_None;
}
if (s) {
res = PyObject_CallFunctionObjArgs(self->pcast, s, curs, NULL);
Py_DECREF(s);
}
}
else {
PyErr_SetString(Error, "internal error: no casting function found");
}
((cursorObject*)curs)->caster = old;
Py_DECREF(obj);
return res;
}

91
psycopg/typecast.h Normal file
View File

@ -0,0 +1,91 @@
/* typecast.h - definitions for typecasters
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_TYPECAST_H
#define PSYCOPG_TYPECAST_H 1
#ifdef __cplusplus
extern "C" {
#endif
/* type of type-casting functions (both C and Python) */
typedef PyObject *(*typecast_function)(const char *str, Py_ssize_t len,
PyObject *cursor);
/** typecast type **/
extern HIDDEN 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 */
PyObject *bcast; /* base cast, used by array typecasters */
} typecastObject;
/* the initialization values are stored here */
typedef struct {
char *name;
long int *values;
typecast_function cast;
/* base is the base typecaster for arrays */
char *base;
} typecastObject_initlist;
/* the type dictionary, much faster to access it globally */
extern HIDDEN PyObject *psyco_types;
extern HIDDEN PyObject *psyco_binary_types;
/* the default casting objects, used when no other objects are available */
extern HIDDEN PyObject *psyco_default_cast;
extern HIDDEN PyObject *psyco_default_binary_cast;
/** exported functions **/
/* used by module.c to init the type system and register types */
RAISES_NEG HIDDEN int typecast_init(PyObject *dict);
RAISES_NEG HIDDEN int typecast_add(PyObject *obj, PyObject *dict, int binary);
/* the C callable typecastObject creator function */
HIDDEN PyObject *typecast_from_c(typecastObject_initlist *type, PyObject *d);
/* the python callable typecast creator functions */
HIDDEN PyObject *typecast_from_python(
PyObject *self, PyObject *args, PyObject *keywds);
HIDDEN PyObject *typecast_array_from_python(
PyObject *self, PyObject *args, PyObject *keywds);
/* the function used to dispatch typecasting calls */
HIDDEN PyObject *typecast_cast(
PyObject *self, const char *str, Py_ssize_t len, PyObject *curs);
#endif /* !defined(PSYCOPG_TYPECAST_H) */

298
psycopg/typecast_array.c Normal file
View File

@ -0,0 +1,298 @@
/* typecast_array.c - array typecasters
*
* Copyright (C) 2005-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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 MAX_DIMENSIONS 16
/** typecast_array_cleanup - remove the horrible [...]= stuff **/
static int
typecast_array_cleanup(const char **str, Py_ssize_t *len)
{
Py_ssize_t i, depth = 1;
if ((*str)[0] != '[') return -1;
for (i=1 ; depth > 0 && i < *len ; i++) {
if ((*str)[i] == '[')
depth += 1;
else if ((*str)[i] == ']')
depth -= 1;
}
if ((*str)[i] != '=') return -1;
*str = &((*str)[i+1]);
*len = *len - i - 1;
return 0;
}
/** typecast_array_scan - scan a string looking for array items **/
#define ASCAN_ERROR -1
#define ASCAN_EOF 0
#define ASCAN_BEGIN 1
#define ASCAN_END 2
#define ASCAN_TOKEN 3
#define ASCAN_QUOTED 4
static int
typecast_array_tokenize(const char *str, Py_ssize_t strlength,
Py_ssize_t *pos, char** token,
Py_ssize_t *length, int *quotes)
{
/* FORTRAN glory */
Py_ssize_t i, l;
int q, b, res;
Dprintf("typecast_array_tokenize: '%s', "
FORMAT_CODE_PY_SSIZE_T "/" FORMAT_CODE_PY_SSIZE_T,
&str[*pos], *pos, strlength);
/* we always get called with pos pointing at the start of a token, so a
fast check is enough for ASCAN_EOF, ASCAN_BEGIN and ASCAN_END */
if (*pos == strlength) {
return ASCAN_EOF;
}
else if (str[*pos] == '{') {
*pos += 1;
return ASCAN_BEGIN;
}
else if (str[*pos] == '}') {
*pos += 1;
if (str[*pos] == ',')
*pos += 1;
return ASCAN_END;
}
/* now we start looking for the first unquoted ',' or '}', the only two
tokens that can limit an array element */
q = 0; /* if q is odd we're inside quotes */
b = 0; /* if b is 1 we just encountered a backslash */
res = ASCAN_TOKEN;
for (i = *pos ; i < strlength ; i++) {
switch (str[i]) {
case '"':
if (b == 0)
q += 1;
else
b = 0;
break;
case '\\':
res = ASCAN_QUOTED;
if (b == 0)
b = 1;
else
/* we're backslashing a backslash */
b = 0;
break;
case '}':
case ',':
if (b == 0 && ((q&1) == 0))
goto tokenize;
break;
default:
/* reset the backslash counter */
b = 0;
break;
}
}
tokenize:
/* remove initial quoting character and calculate raw length */
*quotes = 0;
l = i - *pos;
if (str[*pos] == '"') {
*pos += 1;
l -= 2;
*quotes = 1;
}
if (res == ASCAN_QUOTED) {
const char *j, *jj;
char *buffer = PyMem_Malloc(l+1);
if (buffer == NULL) {
PyErr_NoMemory();
return ASCAN_ERROR;
}
*token = buffer;
for (j = str + *pos, jj = j + l; j < jj; ++j) {
if (*j == '\\') { ++j; }
*(buffer++) = *j;
}
*buffer = '\0';
/* The variable that was used to indicate the size of buffer is of type
* Py_ssize_t, so a subsegment of buffer couldn't possibly exceed
* PY_SSIZE_T_MAX: */
*length = (Py_ssize_t) (buffer - *token);
}
else {
*token = (char *)&str[*pos];
*length = l;
}
*pos = i;
/* skip the comma and set position to the start of next token */
if (str[i] == ',') *pos += 1;
return res;
}
RAISES_NEG static int
typecast_array_scan(const char *str, Py_ssize_t strlength,
PyObject *curs, PyObject *base, PyObject *array)
{
int state, quotes = 0;
Py_ssize_t length = 0, pos = 0;
char *token;
PyObject *stack[MAX_DIMENSIONS];
size_t stack_index = 0;
while (1) {
token = NULL;
state = typecast_array_tokenize(str, strlength,
&pos, &token, &length, &quotes);
Dprintf("typecast_array_scan: state = %d,"
" length = " FORMAT_CODE_PY_SSIZE_T ", token = '%s'",
state, length, token);
if (state == ASCAN_TOKEN || state == ASCAN_QUOTED) {
PyObject *obj;
if (!quotes && length == 4
&& (token[0] == 'n' || token[0] == 'N')
&& (token[1] == 'u' || token[1] == 'U')
&& (token[2] == 'l' || token[2] == 'L')
&& (token[3] == 'l' || token[3] == 'L'))
{
obj = typecast_cast(base, NULL, 0, curs);
} else {
obj = typecast_cast(base, token, length, curs);
}
/* before anything else we free the memory */
if (state == ASCAN_QUOTED) PyMem_Free(token);
if (obj == NULL) return -1;
PyList_Append(array, obj);
Py_DECREF(obj);
}
else if (state == ASCAN_BEGIN) {
PyObject *sub = PyList_New(0);
if (sub == NULL) return -1;
PyList_Append(array, sub);
Py_DECREF(sub);
if (stack_index == MAX_DIMENSIONS) {
PyErr_SetString(DataError, "excessive array dimensions");
return -1;
}
stack[stack_index++] = array;
array = sub;
}
else if (state == ASCAN_ERROR) {
return -1;
}
else if (state == ASCAN_END) {
if (stack_index == 0) {
PyErr_SetString(DataError, "unbalanced braces in array");
return -1;
}
array = stack[--stack_index];
}
else if (state == ASCAN_EOF)
break;
}
return 0;
}
/** GENERIC - a generic typecaster that can be used when no special actions
have to be taken on the single items **/
static PyObject *
typecast_GENERIC_ARRAY_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
PyObject *obj = NULL;
PyObject *base = ((typecastObject*)((cursorObject*)curs)->caster)->bcast;
Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s',"
" len = " FORMAT_CODE_PY_SSIZE_T, str, len);
if (str == NULL) { Py_RETURN_NONE; }
if (str[0] == '[')
typecast_array_cleanup(&str, &len);
if (str[0] != '{') {
PyErr_SetString(DataError, "array does not start with '{'");
return NULL;
}
if (str[1] == '\0') {
PyErr_SetString(DataError, "malformed array: '{'");
return NULL;
}
Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s',"
" len = " FORMAT_CODE_PY_SSIZE_T, str, len);
if (!(obj = PyList_New(0))) { return NULL; }
/* scan the array skipping the first level of {} */
if (typecast_array_scan(&str[1], len-2, curs, base, obj) < 0) {
Py_CLEAR(obj);
}
return obj;
}
/** almost all the basic array typecasters are derived from GENERIC **/
#define typecast_LONGINTEGERARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_INTEGERARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_FLOATARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_DECIMALARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_STRINGARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_UNICODEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_BYTESARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_BOOLEANARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_DATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_DATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_DATEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_TIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_INTERVALARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_BINARYARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_ROWIDARRAY_cast typecast_GENERIC_ARRAY_cast

150
psycopg/typecast_basic.c Normal file
View File

@ -0,0 +1,150 @@
/* pgcasts_basic.c - basic typecasting functions to python types
*
* Copyright (C) 2001-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
/** INTEGER - cast normal integers (4 bytes) to python int **/
#define typecast_INTEGER_cast typecast_LONGINTEGER_cast
/** LONGINTEGER - cast long integers (8 bytes) to python long **/
static PyObject *
typecast_LONGINTEGER_cast(const char *s, Py_ssize_t len, PyObject *curs)
{
char buffer[24];
if (s == NULL) { Py_RETURN_NONE; }
if (s[len] != '\0') {
strncpy(buffer, s, (size_t) len); buffer[len] = '\0';
s = buffer;
}
return PyLong_FromString((char *)s, NULL, 0);
}
/** FLOAT - cast floating point numbers to python float **/
static PyObject *
typecast_FLOAT_cast(const char *s, Py_ssize_t len, PyObject *curs)
{
PyObject *str = NULL, *flo = NULL;
if (s == NULL) { Py_RETURN_NONE; }
if (!(str = Text_FromUTF8AndSize(s, len))) { return NULL; }
flo = PyFloat_FromString(str);
Py_DECREF(str);
return flo;
}
/** BYTES - cast strings of any type to python bytes **/
static PyObject *
typecast_BYTES_cast(const char *s, Py_ssize_t len, PyObject *curs)
{
if (s == NULL) { Py_RETURN_NONE; }
return Bytes_FromStringAndSize(s, len);
}
/** UNICODE - cast strings of any type to a python unicode object **/
static PyObject *
typecast_UNICODE_cast(const char *s, Py_ssize_t len, PyObject *curs)
{
connectionObject *conn;
if (s == NULL) { Py_RETURN_NONE; }
conn = ((cursorObject*)curs)->conn;
return conn_decode(conn, s, len);
}
/** STRING - cast strings of any type to python string **/
#define typecast_STRING_cast typecast_UNICODE_cast
/** BOOLEAN - cast boolean value into right python object **/
static PyObject *
typecast_BOOLEAN_cast(const char *s, Py_ssize_t len, PyObject *curs)
{
PyObject *res = NULL;
if (s == NULL) { Py_RETURN_NONE; }
switch (s[0]) {
case 't':
case 'T':
res = Py_True;
break;
case 'f':
case 'F':
res = Py_False;
break;
default:
PyErr_Format(InterfaceError, "can't parse boolean: '%s'", s);
break;
}
Py_XINCREF(res);
return res;
}
/** DECIMAL - cast any kind of number into a Python Decimal object **/
static PyObject *
typecast_DECIMAL_cast(const char *s, Py_ssize_t len, PyObject *curs)
{
PyObject *res = NULL;
PyObject *decimalType;
char *buffer;
if (s == NULL) { Py_RETURN_NONE; }
if ((buffer = PyMem_Malloc(len+1)) == NULL)
return PyErr_NoMemory();
strncpy(buffer, s, (size_t) len); buffer[len] = '\0';
decimalType = psyco_get_decimal_type();
/* Fall back on float if decimal is not available */
if (decimalType != NULL) {
res = PyObject_CallFunction(decimalType, "s", buffer);
Py_DECREF(decimalType);
}
else {
PyErr_Clear();
res = PyObject_CallFunction((PyObject*)&PyFloat_Type, "s", buffer);
}
PyMem_Free(buffer);
return res;
}
/* some needed aliases */
#define typecast_NUMBER_cast typecast_FLOAT_cast
#define typecast_ROWID_cast typecast_INTEGER_cast

275
psycopg/typecast_binary.c Normal file
View File

@ -0,0 +1,275 @@
/* typecast_binary.c - binary typecasting functions to python types
*
* Copyright (C) 2001-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#include "typecast_binary.h"
#include <stdlib.h>
/* Python object holding a memory chunk. The memory is deallocated when
the object is destroyed. This type is used to let users directly access
memory chunks holding unescaped binary data through the buffer interface.
*/
static void
chunk_dealloc(chunkObject *self)
{
Dprintf("chunk_dealloc: deallocating memory at %p, size "
FORMAT_CODE_PY_SSIZE_T,
self->base, self->len
);
PyMem_Free(self->base);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *
chunk_repr(chunkObject *self)
{
return PyString_FromFormat(
"<memory chunk at %p size " FORMAT_CODE_PY_SSIZE_T ">",
self->base, self->len
);
}
/* 3.0 buffer interface */
int chunk_getbuffer(PyObject *_self, Py_buffer *view, int flags)
{
int rv;
chunkObject *self = (chunkObject*)_self;
rv = PyBuffer_FillInfo(view, _self, self->base, self->len, 1, flags);
if (rv == 0) {
view->format = "c";
}
return rv;
}
static PyBufferProcs chunk_as_buffer =
{
chunk_getbuffer,
NULL,
};
#define chunk_doc "memory chunk"
PyTypeObject chunkType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.chunk",
sizeof(chunkObject), 0,
(destructor) chunk_dealloc, /* tp_dealloc*/
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc) chunk_repr, /* 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 */
&chunk_as_buffer, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
chunk_doc /* tp_doc */
};
static char *parse_hex(
const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout);
static char *parse_escape(
const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout);
/* The function is not static and not hidden as we use ctypes to test it. */
PyObject *
typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs)
{
chunkObject *chunk = NULL;
PyObject *res = NULL;
char *buffer = NULL;
Py_ssize_t len;
if (s == NULL) { Py_RETURN_NONE; }
if (s[0] == '\\' && s[1] == 'x') {
/* This is a buffer escaped in hex format: libpq before 9.0 can't
* parse it and we can't detect reliably the libpq version at runtime.
* So the only robust option is to parse it ourselves - luckily it's
* an easy format.
*/
if (NULL == (buffer = parse_hex(s, l, &len))) {
goto exit;
}
}
else {
/* This is a buffer in the classic bytea format. So we can handle it
* to the PQunescapeBytea to have it parsed, right? ...Wrong. We
* could, but then we'd have to record whether buffer was allocated by
* Python or by the libpq to dispose it properly. Furthermore the
* PQunescapeBytea interface is not the most brilliant as it wants a
* null-terminated string even if we have known its length thus
* requiring a useless memcpy and strlen.
* So we'll just have our better integrated parser, let's finish this
* story.
*/
if (NULL == (buffer = parse_escape(s, l, &len))) {
goto exit;
}
}
chunk = (chunkObject *) PyObject_New(chunkObject, &chunkType);
if (chunk == NULL) goto exit;
/* **Transfer** ownership of buffer's memory to the chunkObject: */
chunk->base = buffer;
buffer = NULL;
chunk->len = (Py_ssize_t)len;
if ((res = PyMemoryView_FromObject((PyObject*)chunk)) == NULL)
goto exit;
exit:
Py_XDECREF((PyObject *)chunk);
PyMem_Free(buffer);
return res;
}
static const char hex_lut[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
/* Parse a bytea output buffer encoded in 'hex' format.
*
* the format is described in
* https://www.postgresql.org/docs/current/static/datatype-binary.html
*
* Parse the buffer in 'bufin', whose length is 'sizein'.
* Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size.
* In case of error set an exception and return NULL.
*/
static char *
parse_hex(const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout)
{
char *ret = NULL;
const char *bufend = bufin + sizein;
const char *pi = bufin + 2; /* past the \x */
char *bufout;
char *po;
po = bufout = PyMem_Malloc((sizein - 2) >> 1); /* output size upper bound */
if (NULL == bufout) {
PyErr_NoMemory();
goto exit;
}
/* Implementation note: we call this function upon database response, not
* user input (because we are parsing the output format of a buffer) so we
* don't expect errors. On bad input we reserve the right to return a bad
* output, not an error.
*/
while (pi < bufend) {
char c;
while (-1 == (c = hex_lut[*pi++ & '\x7f'])) {
if (pi >= bufend) { goto endloop; }
}
*po = c << 4;
while (-1 == (c = hex_lut[*pi++ & '\x7f'])) {
if (pi >= bufend) { goto endloop; }
}
*po++ |= c;
}
endloop:
ret = bufout;
*sizeout = po - bufout;
exit:
return ret;
}
/* Parse a bytea output buffer encoded in 'escape' format.
*
* the format is described in
* https://www.postgresql.org/docs/current/static/datatype-binary.html
*
* Parse the buffer in 'bufin', whose length is 'sizein'.
* Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size.
* In case of error set an exception and return NULL.
*/
static char *
parse_escape(const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout)
{
char *ret = NULL;
const char *bufend = bufin + sizein;
const char *pi = bufin;
char *bufout;
char *po;
po = bufout = PyMem_Malloc(sizein); /* output size upper bound */
if (NULL == bufout) {
PyErr_NoMemory();
goto exit;
}
while (pi < bufend) {
if (*pi != '\\') {
/* Unescaped char */
*po++ = *pi++;
continue;
}
if ((pi[1] >= '0' && pi[1] <= '3') &&
(pi[2] >= '0' && pi[2] <= '7') &&
(pi[3] >= '0' && pi[3] <= '7'))
{
/* Escaped octal value */
*po++ = ((pi[1] - '0') << 6) |
((pi[2] - '0') << 3) |
((pi[3] - '0'));
pi += 4;
}
else {
/* Escaped char */
*po++ = pi[1];
pi += 2;
}
}
ret = bufout;
*sizeout = po - bufout;
exit:
return ret;
}

50
psycopg/typecast_binary.h Normal file
View File

@ -0,0 +1,50 @@
/* typecast_binary.h - definitions for binary typecaster
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_TYPECAST_BINARY_H
#define PSYCOPG_TYPECAST_BINARY_H 1
#ifdef __cplusplus
extern "C" {
#endif
/** chunk type **/
extern HIDDEN PyTypeObject chunkType;
typedef struct {
PyObject_HEAD
void *base; /* Pointer to the memory chunk. */
Py_ssize_t len; /* Size in bytes of the memory chunk. */
} chunkObject;
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_TYPECAST_BINARY_H) */

View File

@ -0,0 +1,71 @@
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_STRING_types[] = {19, 18, 25, 1042, 1043, 0};
static long int typecast_BOOLEAN_types[] = {16, 0};
static long int typecast_DATETIME_types[] = {1114, 0};
static long int typecast_DATETIMETZ_types[] = {1184, 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};
static long int typecast_LONGINTEGERARRAY_types[] = {1016, 0};
static long int typecast_INTEGERARRAY_types[] = {1005, 1006, 1007, 0};
static long int typecast_FLOATARRAY_types[] = {1021, 1022, 0};
static long int typecast_DECIMALARRAY_types[] = {1231, 0};
static long int typecast_STRINGARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0};
static long int typecast_BOOLEANARRAY_types[] = {1000, 0};
static long int typecast_DATETIMEARRAY_types[] = {1115, 0};
static long int typecast_DATETIMETZARRAY_types[] = {1185, 0};
static long int typecast_TIMEARRAY_types[] = {1183, 1270, 0};
static long int typecast_DATEARRAY_types[] = {1182, 0};
static long int typecast_INTERVALARRAY_types[] = {1187, 0};
static long int typecast_BINARYARRAY_types[] = {1001, 0};
static long int typecast_ROWIDARRAY_types[] = {1028, 1013, 0};
static long int typecast_INETARRAY_types[] = {1041, 0};
static long int typecast_CIDRARRAY_types[] = {651, 0};
static long int typecast_MACADDRARRAY_types[] = {1040, 0};
static long int typecast_UNKNOWN_types[] = {705, 0};
static typecastObject_initlist typecast_builtins[] = {
{"NUMBER", typecast_NUMBER_types, typecast_NUMBER_cast, NULL},
{"LONGINTEGER", typecast_LONGINTEGER_types, typecast_LONGINTEGER_cast, NULL},
{"INTEGER", typecast_INTEGER_types, typecast_INTEGER_cast, NULL},
{"FLOAT", typecast_FLOAT_types, typecast_FLOAT_cast, NULL},
{"DECIMAL", typecast_DECIMAL_types, typecast_DECIMAL_cast, NULL},
{"UNICODE", typecast_STRING_types, typecast_UNICODE_cast, NULL},
{"BYTES", typecast_STRING_types, typecast_BYTES_cast, NULL},
{"STRING", typecast_STRING_types, typecast_STRING_cast, NULL},
{"BOOLEAN", typecast_BOOLEAN_types, typecast_BOOLEAN_cast, NULL},
{"DATETIME", typecast_DATETIME_types, typecast_DATETIME_cast, NULL},
{"DATETIMETZ", typecast_DATETIMETZ_types, typecast_DATETIMETZ_cast, NULL},
{"TIME", typecast_TIME_types, typecast_TIME_cast, NULL},
{"DATE", typecast_DATE_types, typecast_DATE_cast, NULL},
{"INTERVAL", typecast_INTERVAL_types, typecast_INTERVAL_cast, NULL},
{"BINARY", typecast_BINARY_types, typecast_BINARY_cast, NULL},
{"ROWID", typecast_ROWID_types, typecast_ROWID_cast, NULL},
{"LONGINTEGERARRAY", typecast_LONGINTEGERARRAY_types, typecast_LONGINTEGERARRAY_cast, "LONGINTEGER"},
{"INTEGERARRAY", typecast_INTEGERARRAY_types, typecast_INTEGERARRAY_cast, "INTEGER"},
{"FLOATARRAY", typecast_FLOATARRAY_types, typecast_FLOATARRAY_cast, "FLOAT"},
{"DECIMALARRAY", typecast_DECIMALARRAY_types, typecast_DECIMALARRAY_cast, "DECIMAL"},
{"UNICODEARRAY", typecast_STRINGARRAY_types, typecast_UNICODEARRAY_cast, "UNICODE"},
{"BYTESARRAY", typecast_STRINGARRAY_types, typecast_BYTESARRAY_cast, "BYTES"},
{"STRINGARRAY", typecast_STRINGARRAY_types, typecast_STRINGARRAY_cast, "STRING"},
{"BOOLEANARRAY", typecast_BOOLEANARRAY_types, typecast_BOOLEANARRAY_cast, "BOOLEAN"},
{"DATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_DATETIMEARRAY_cast, "DATETIME"},
{"DATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_DATETIMETZARRAY_cast, "DATETIMETZ"},
{"TIMEARRAY", typecast_TIMEARRAY_types, typecast_TIMEARRAY_cast, "TIME"},
{"DATEARRAY", typecast_DATEARRAY_types, typecast_DATEARRAY_cast, "DATE"},
{"INTERVALARRAY", typecast_INTERVALARRAY_types, typecast_INTERVALARRAY_cast, "INTERVAL"},
{"BINARYARRAY", typecast_BINARYARRAY_types, typecast_BINARYARRAY_cast, "BINARY"},
{"ROWIDARRAY", typecast_ROWIDARRAY_types, typecast_ROWIDARRAY_cast, "ROWID"},
{"UNKNOWN", typecast_UNKNOWN_types, typecast_UNKNOWN_cast, NULL},
{"INETARRAY", typecast_INETARRAY_types, typecast_STRINGARRAY_cast, "STRING"},
{"CIDRARRAY", typecast_CIDRARRAY_types, typecast_STRINGARRAY_cast, "STRING"},
{"MACADDRARRAY", typecast_MACADDRARRAY_types, typecast_STRINGARRAY_cast, "STRING"},
{NULL, NULL, NULL, NULL}
};

486
psycopg/typecast_datetime.c Normal file
View File

@ -0,0 +1,486 @@
/* typecast_datetime.c - date and time typecasting functions to python types
*
* Copyright (C) 2001-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#include <math.h>
#include "datetime.h"
RAISES_NEG static int
typecast_datetime_init(void)
{
PyDateTime_IMPORT;
if (!PyDateTimeAPI) {
PyErr_SetString(PyExc_ImportError, "datetime initialization failed");
return -1;
}
return 0;
}
/** DATE - cast a date into a date python object **/
static PyObject *
typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
PyObject* obj = NULL;
int n, y=0, m=0, d=0;
if (str == NULL) { Py_RETURN_NONE; }
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
if (str[0] == '-') {
obj = PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateType, "min");
}
else {
obj = PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateType, "max");
}
}
else {
n = typecast_parse_date(str, NULL, &len, &y, &m, &d);
Dprintf("typecast_PYDATE_cast: "
"n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", "
"y = %d, m = %d, d = %d",
n, len, y, m, d);
if (n != 3) {
PyErr_SetString(DataError, "unable to parse date");
return NULL;
}
else {
if (y > 9999) y = 9999;
obj = PyObject_CallFunction(
(PyObject*)PyDateTimeAPI->DateType, "iii", y, m, d);
}
}
return obj;
}
/* convert the strings -infinity and infinity into a datetime with timezone */
static PyObject *
_parse_inftz(const char *str, PyObject *curs)
{
PyObject *rv = NULL;
PyObject *m = NULL;
PyObject *tzinfo_factory = NULL;
PyObject *tzinfo = NULL;
PyObject *args = NULL;
PyObject *kwargs = NULL;
PyObject *replace = NULL;
if (!(m = PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateTimeType,
(str[0] == '-' ? "min" : "max")))) {
goto exit;
}
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (tzinfo_factory == Py_None) {
rv = m;
m = NULL;
goto exit;
}
#if PY_VERSION_HEX < 0x03070000
{
PyObject *tzoff;
if (!(tzoff = PyDelta_FromDSU(0, 0, 0))) { goto exit; }
tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL);
Py_DECREF(tzoff);
if (!tzinfo) { goto exit; }
}
#else
tzinfo = PyDateTime_TimeZone_UTC;
Py_INCREF(tzinfo);
#endif
/* m.replace(tzinfo=tzinfo) */
if (!(args = PyTuple_New(0))) { goto exit; }
if (!(kwargs = PyDict_New())) { goto exit; }
if (0 != PyDict_SetItemString(kwargs, "tzinfo", tzinfo)) { goto exit; }
if (!(replace = PyObject_GetAttrString(m, "replace"))) { goto exit; }
rv = PyObject_Call(replace, args, kwargs);
exit:
Py_XDECREF(replace);
Py_XDECREF(args);
Py_XDECREF(kwargs);
Py_XDECREF(tzinfo);
Py_XDECREF(m);
return rv;
}
static PyObject *
_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
{
PyObject* rv = NULL;
PyObject *tzoff = NULL;
PyObject *tzinfo = NULL;
PyObject *tzinfo_factory;
int n, y=0, m=0, d=0;
int hh=0, mm=0, ss=0, us=0, tzsec=0;
const char *tp = NULL;
Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str);
n = typecast_parse_date(str, &tp, &len, &y, &m, &d);
Dprintf("typecast_PYDATE_cast: tp = %p "
"n = %d, len = " FORMAT_CODE_PY_SSIZE_T ","
" y = %d, m = %d, d = %d",
tp, n, len, y, m, d);
if (n != 3) {
PyErr_SetString(DataError, "unable to parse date");
goto exit;
}
if (len > 0) {
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tzsec);
Dprintf("typecast_PYDATETIMETZ_cast: n = %d,"
" len = " FORMAT_CODE_PY_SSIZE_T ","
" hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d",
n, len, hh, mm, ss, us, tzsec);
if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time");
goto exit;
}
}
if (ss > 59) {
mm += 1;
ss -= 60;
}
if (y > 9999)
y = 9999;
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (n >= 5 && tzinfo_factory != Py_None) {
/* we have a time zone, calculate minutes and create
appropriate tzinfo object calling the factory */
Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tzsec);
#if PY_VERSION_HEX < 0x03070000
/* Before Python 3.7 the timezone offset had to be a whole number
* of minutes, so round the seconds to the closest minute */
tzsec = 60 * (int)round(tzsec / 60.0);
#endif
if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; }
if (!(tzinfo = PyObject_CallFunctionObjArgs(
tzinfo_factory, tzoff, NULL))) {
goto exit;
}
}
else {
Py_INCREF(Py_None);
tzinfo = Py_None;
}
Dprintf("typecast_PYDATETIMETZ_cast: tzinfo: %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
tzinfo, Py_REFCNT(tzinfo));
rv = PyObject_CallFunction(
(PyObject*)PyDateTimeAPI->DateTimeType, "iiiiiiiO",
y, m, d, hh, mm, ss, us, tzinfo);
exit:
Py_XDECREF(tzoff);
Py_XDECREF(tzinfo);
return rv;
}
/** DATETIME - cast a timestamp into a datetime python object **/
static PyObject *
typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
if (str == NULL) { Py_RETURN_NONE; }
/* check for infinity */
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
return PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateTimeType,
(str[0] == '-' ? "min" : "max"));
}
return _parse_noninftz(str, len, curs);
}
/** DATETIMETZ - cast a timestamptz into a datetime python object **/
static PyObject *
typecast_PYDATETIMETZ_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
if (str == NULL) { Py_RETURN_NONE; }
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
return _parse_inftz(str, curs);
}
return _parse_noninftz(str, len, curs);
}
/** TIME - parse time into a time object **/
static PyObject *
typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
PyObject* rv = NULL;
PyObject *tzoff = NULL;
PyObject *tzinfo = NULL;
PyObject *tzinfo_factory;
int n, hh=0, mm=0, ss=0, us=0, tzsec=0;
if (str == NULL) { Py_RETURN_NONE; }
n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tzsec);
Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", "
"hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d",
n, len, hh, mm, ss, us, tzsec);
if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time");
return NULL;
}
if (ss > 59) {
mm += 1;
ss -= 60;
}
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (n >= 5 && tzinfo_factory != Py_None) {
/* we have a time zone, calculate seconds and create
appropriate tzinfo object calling the factory */
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tzsec);
#if PY_VERSION_HEX < 0x03070000
/* Before Python 3.7 the timezone offset had to be a whole number
* of minutes, so round the seconds to the closest minute */
tzsec = 60 * (int)round(tzsec / 60.0);
#endif
if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; }
if (!(tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL))) {
goto exit;
}
}
else {
Py_INCREF(Py_None);
tzinfo = Py_None;
}
rv = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
hh, mm, ss, us, tzinfo);
exit:
Py_XDECREF(tzoff);
Py_XDECREF(tzinfo);
return rv;
}
/* Attempt parsing a number as microseconds
* Redshift is reported returning this stuff, see #558
*
* Return a new `timedelta()` object in case of success or NULL and set an error
*/
static PyObject *
interval_from_usecs(const char *str)
{
PyObject *us = NULL;
char *pend;
PyObject *rv = NULL;
Dprintf("interval_from_usecs: %s", str);
if (!(us = PyLong_FromString((char *)str, &pend, 0))) {
Dprintf("interval_from_usecs: parsing long failed");
goto exit;
}
if (*pend != '\0') {
/* there are trailing chars, it's not just micros. Barf. */
Dprintf("interval_from_usecs: spurious chars %s", pend);
PyErr_Format(PyExc_ValueError,
"expected number of microseconds, got %s", str);
goto exit;
}
rv = PyObject_CallFunction(
(PyObject*)PyDateTimeAPI->DeltaType, "iiO", 0, 0, us);
exit:
Py_XDECREF(us);
return rv;
}
/** INTERVAL - parse an interval into a timedelta object **/
static PyObject *
typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0;
PY_LONG_LONG days = 0, seconds = 0;
int sign = 1, denom = 1, part = 0;
const char *orig = str;
if (str == NULL) { Py_RETURN_NONE; }
Dprintf("typecast_PYINTERVAL_cast: s = %s", str);
while (len-- > 0 && *str) {
switch (*str) {
case '-':
sign = -1;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
long v1;
v1 = v * 10 + (*str - '0');
/* detect either a rollover, happening if v is really too short,
* or too big value. On Win where long == int the 2nd check
* is useless. */
if (v1 < v || v1 > (long)INT_MAX) {
/* uhm, oops... but before giving up, maybe it's redshift
* returning microseconds? See #558 */
PyObject *rv;
if ((rv = interval_from_usecs(orig))) {
return rv;
}
else {
PyErr_Clear();
}
PyErr_SetString(
PyExc_OverflowError, "interval component too big");
return NULL;
}
v = v1;
}
if (part == 6) {
denom *= 10;
}
break;
case 'y':
if (part == 0) {
years = v * sign;
v = 0; sign = 1; part = 1;
str = skip_until_space2(str, &len);
}
break;
case 'm':
if (part <= 1) {
months = v * sign;
v = 0; sign = 1; part = 2;
str = skip_until_space2(str, &len);
}
break;
case 'd':
if (part <= 2) {
days = v * sign;
v = 0; sign = 1; part = 3;
str = skip_until_space2(str, &len);
}
break;
case ':':
if (part <= 3) {
hours = v;
v = 0; part = 4;
}
else if (part == 4) {
minutes = v;
v = 0; part = 5;
}
break;
case '.':
if (part == 5) {
seconds = v;
v = 0; part = 6;
}
break;
case 'P':
PyErr_SetString(NotSupportedError,
"iso_8601 intervalstyle currently not supported");
return NULL;
default:
break;
}
str++;
}
/* manage last value, be it minutes or seconds or microseconds */
if (part == 4) {
minutes = v;
}
else if (part == 5) {
seconds = v;
}
else if (part == 6) {
micros = v;
if (denom < 1000000L) {
do {
micros *= 10;
denom *= 10;
} while (denom < 1000000L);
}
else if (denom > 1000000L) {
micros = (long)round((double)micros / denom * 1000000.0);
}
}
else if (part == 0) {
/* Parsing failed, maybe it's just an integer? Assume usecs */
return interval_from_usecs(orig);
}
/* add hour, minutes, seconds together and include the sign */
seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours;
if (sign < 0) {
seconds = -seconds;
micros = -micros;
}
/* add the days, months years together - they already include a sign */
days += 30 * (PY_LONG_LONG)months + 365 * (PY_LONG_LONG)years;
return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "LLl",
days, seconds, micros);
}
/* psycopg defaults to using python datetime types */
#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
#define typecast_DATETIMETZ_cast typecast_PYDATETIMETZ_cast

456
psycopg/utils.c Normal file
View File

@ -0,0 +1,456 @@
/* utils.c - miscellaneous utility functions
*
* Copyright (C) 2008-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/connection.h"
#include "psycopg/cursor.h"
#include "psycopg/pgtypes.h"
#include "psycopg/error.h"
#include <string.h>
#include <stdlib.h>
/* Escape a string for sql inclusion.
*
* The function must be called holding the GIL.
*
* Return a pointer to a new string on the Python heap on success, else NULL
* and set an exception. The returned string includes quotes and leading E if
* needed.
*
* `len` is optional: if < 0 it will be calculated.
*
* If tolen is set, it will contain the length of the escaped string,
* including quotes.
*/
char *
psyco_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
char *to, Py_ssize_t *tolen)
{
Py_ssize_t ql;
int eq = (conn && (conn->equote)) ? 1 : 0;
if (len < 0) {
len = strlen(from);
} else if (strchr(from, '\0') != from + len) {
PyErr_Format(PyExc_ValueError,
"A string literal cannot contain NUL (0x00) characters.");
return NULL;
}
if (to == NULL) {
to = (char *)PyMem_Malloc((len * 2 + 4) * sizeof(char));
if (to == NULL) {
PyErr_NoMemory();
return NULL;
}
}
{
int err;
if (conn && conn->pgconn)
ql = PQescapeStringConn(conn->pgconn, to+eq+1, from, len, &err);
else
ql = PQescapeString(to+eq+1, from, len);
}
if (eq) {
to[0] = 'E';
to[1] = to[ql+2] = '\'';
to[ql+3] = '\0';
}
else {
to[0] = to[ql+1] = '\'';
to[ql+2] = '\0';
}
if (tolen)
*tolen = ql+eq+2;
return to;
}
/* Escape a string for inclusion in a query as identifier.
*
* 'len' is optional: if < 0 it will be calculated.
*
* Return a string allocated by Postgres: free it using PQfreemem
* In case of error set a Python exception.
*/
char *
psyco_escape_identifier(connectionObject *conn, const char *str, Py_ssize_t len)
{
char *rv = NULL;
if (!conn || !conn->pgconn) {
PyErr_SetString(InterfaceError, "connection not valid");
goto exit;
}
if (len < 0) { len = strlen(str); }
rv = PQescapeIdentifier(conn->pgconn, str, len);
if (!rv) {
char *msg;
msg = PQerrorMessage(conn->pgconn);
if (!msg || !msg[0]) {
msg = "no message provided";
}
PyErr_Format(InterfaceError, "failed to escape identifier: %s", msg);
}
exit:
return rv;
}
/* Duplicate a string.
*
* Allocate a new buffer on the Python heap containing the new string.
* 'len' is optional: if < 0 the length is calculated.
*
* Store the return in 'to' and return 0 in case of success, else return -1
* and raise an exception.
*
* If from is null, store null into to.
*/
RAISES_NEG int
psyco_strdup(char **to, const char *from, Py_ssize_t len)
{
if (!from) {
*to = NULL;
return 0;
}
if (len < 0) { len = strlen(from); }
if (!(*to = PyMem_Malloc(len + 1))) {
PyErr_NoMemory();
return -1;
}
strcpy(*to, from);
return 0;
}
/* Ensure a Python object is a bytes string.
*
* Useful when a char * is required out of it.
*
* The function is ref neutral: steals a ref from obj and adds one to the
* return value. This also means that you shouldn't call the function on a
* borrowed ref, if having the object unallocated is not what you want.
*
* It is safe to call the function on NULL.
*/
STEALS(1) PyObject *
psyco_ensure_bytes(PyObject *obj)
{
PyObject *rv = NULL;
if (!obj) { return NULL; }
if (PyUnicode_Check(obj)) {
rv = PyUnicode_AsUTF8String(obj);
Py_DECREF(obj);
}
else if (Bytes_Check(obj)) {
rv = obj;
}
else {
PyErr_Format(PyExc_TypeError,
"Expected bytes or unicode string, got %s instead",
Py_TYPE(obj)->tp_name);
Py_DECREF(obj); /* steal the ref anyway */
}
return rv;
}
/* Take a Python object and return text from it.
*
* This means converting bytes to unicode.
*
* The function is ref neutral: steals a ref from obj and adds one to the
* return value. It is safe to call it on NULL.
*/
STEALS(1) PyObject *
psyco_ensure_text(PyObject *obj)
{
if (obj) {
/* bytes to unicode in Py3 */
PyObject *rv = PyUnicode_FromEncodedObject(obj, "utf8", "replace");
Py_DECREF(obj);
return rv;
}
else {
return NULL;
}
}
/* Check if a file derives from TextIOBase.
*
* Return 1 if it does, else 0, -1 on errors.
*/
int
psyco_is_text_file(PyObject *f)
{
/* NULL before any call.
* then io.TextIOBase if exists, else None. */
static PyObject *base;
/* Try to import os.TextIOBase */
if (NULL == base) {
PyObject *m;
Dprintf("psyco_is_text_file: importing io.TextIOBase");
if (!(m = PyImport_ImportModule("io"))) {
Dprintf("psyco_is_text_file: io module not found");
PyErr_Clear();
Py_INCREF(Py_None);
base = Py_None;
}
else {
if (!(base = PyObject_GetAttrString(m, "TextIOBase"))) {
Dprintf("psyco_is_text_file: io.TextIOBase not found");
PyErr_Clear();
Py_INCREF(Py_None);
base = Py_None;
}
}
Py_XDECREF(m);
}
if (base != Py_None) {
return PyObject_IsInstance(f, base);
} else {
return 0;
}
}
/* Make a dict out of PQconninfoOption array */
PyObject *
psyco_dict_from_conninfo_options(PQconninfoOption *options, int include_password)
{
PyObject *dict, *res = NULL;
PQconninfoOption *o;
if (!(dict = PyDict_New())) { goto exit; }
for (o = options; o->keyword != NULL; o++) {
if (o->val != NULL &&
(include_password || strcmp(o->keyword, "password") != 0)) {
PyObject *value;
if (!(value = Text_FromUTF8(o->val))) { goto exit; }
if (PyDict_SetItemString(dict, o->keyword, value) != 0) {
Py_DECREF(value);
goto exit;
}
Py_DECREF(value);
}
}
res = dict;
dict = NULL;
exit:
Py_XDECREF(dict);
return res;
}
/* Make a connection string out of a string and a dictionary of arguments.
*
* Helper to call psycopg2.extensions.make_dsn()
*/
PyObject *
psyco_make_dsn(PyObject *dsn, PyObject *kwargs)
{
PyObject *ext = NULL, *make_dsn = NULL;
PyObject *args = NULL, *rv = NULL;
if (!(ext = PyImport_ImportModule("psycopg2.extensions"))) { goto exit; }
if (!(make_dsn = PyObject_GetAttrString(ext, "make_dsn"))) { goto exit; }
if (!(args = PyTuple_Pack(1, dsn))) { goto exit; }
rv = PyObject_Call(make_dsn, args, kwargs);
exit:
Py_XDECREF(args);
Py_XDECREF(make_dsn);
Py_XDECREF(ext);
return rv;
}
/* Convert a C string into Python Text using a specified codec.
*
* The codec is the python function codec.getdecoder(enc).
*
* len is optional: use -1 to have it calculated by the function.
*/
PyObject *
psyco_text_from_chars_safe(const char *str, Py_ssize_t len, PyObject *decoder)
{
static PyObject *replace = NULL;
PyObject *rv = NULL;
PyObject *b = NULL;
PyObject *t = NULL;
if (!str) { Py_RETURN_NONE; }
if (len < 0) { len = strlen(str); }
if (decoder) {
if (!replace) {
if (!(replace = PyUnicode_FromString("replace"))) { goto exit; }
}
if (!(b = PyBytes_FromStringAndSize(str, len))) { goto exit; }
if (!(t = PyObject_CallFunctionObjArgs(decoder, b, replace, NULL))) {
goto exit;
}
if (!(rv = PyTuple_GetItem(t, 0))) { goto exit; }
Py_INCREF(rv);
}
else {
rv = PyUnicode_DecodeASCII(str, len, "replace");
}
exit:
Py_XDECREF(t);
Py_XDECREF(b);
return rv;
}
/* psyco_set_error
*
* Create a new error of the given type with extra attributes.
*/
RAISES BORROWED PyObject *
psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg)
{
PyObject *pymsg;
PyObject *err = NULL;
connectionObject *conn = NULL;
if (curs) {
conn = ((cursorObject *)curs)->conn;
}
if ((pymsg = conn_text_from_chars(conn, msg))) {
err = PyObject_CallFunctionObjArgs(exc, pymsg, NULL);
Py_DECREF(pymsg);
}
else {
/* what's better than an error in an error handler in the morning?
* Anyway, some error was set, refcount is ok... get outta here. */
return NULL;
}
if (err && PyObject_TypeCheck(err, &errorType)) {
errorObject *perr = (errorObject *)err;
if (curs) {
Py_CLEAR(perr->cursor);
Py_INCREF(curs);
perr->cursor = curs;
}
}
if (err) {
PyErr_SetObject(exc, err);
Py_DECREF(err);
}
return err;
}
/* Return nonzero if the current one is the main interpreter */
static int
psyco_is_main_interp(void)
{
#if PY_VERSION_HEX >= 0x03080000
/* tested with Python 3.8.0a2 */
return _PyInterpreterState_Get() == PyInterpreterState_Main();
#else
static PyInterpreterState *main_interp = NULL; /* Cached reference */
PyInterpreterState *interp;
if (main_interp) {
return (main_interp == PyThreadState_Get()->interp);
}
/* No cached value: cache the proper value and try again. */
interp = PyInterpreterState_Head();
while (interp->next)
interp = interp->next;
main_interp = interp;
assert (main_interp);
return psyco_is_main_interp();
#endif
}
/* psyco_get_decimal_type
Return a new reference to the decimal type.
The function returns a cached version of the object, but only in the main
interpreter because subinterpreters are confusing.
*/
PyObject *
psyco_get_decimal_type(void)
{
static PyObject *cachedType = NULL;
PyObject *decimalType = NULL;
PyObject *decimal;
/* Use the cached object if running from the main interpreter. */
int can_cache = psyco_is_main_interp();
if (can_cache && cachedType) {
Py_INCREF(cachedType);
return cachedType;
}
/* Get a new reference to the Decimal type. */
decimal = PyImport_ImportModule("decimal");
if (decimal) {
decimalType = PyObject_GetAttrString(decimal, "Decimal");
Py_DECREF(decimal);
}
else {
decimalType = NULL;
}
/* Store the object from future uses. */
if (can_cache && !cachedType && decimalType) {
Py_INCREF(decimalType);
cachedType = decimalType;
}
return decimalType;
}

65
psycopg/utils.h Normal file
View File

@ -0,0 +1,65 @@
/* utils.h - function definitions for utility file
*
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef UTILS_H
#define UTILS_H 1
/* forward declarations */
typedef struct cursorObject cursorObject;
typedef struct connectionObject connectionObject;
typedef struct replicationMessageObject replicationMessageObject;
HIDDEN char *psyco_escape_string(
connectionObject *conn,
const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen);
HIDDEN char *psyco_escape_identifier(
connectionObject *conn, const char *str, Py_ssize_t len);
HIDDEN int psyco_strdup(char **to, const char *from, Py_ssize_t len);
STEALS(1) HIDDEN PyObject * psyco_ensure_bytes(PyObject *obj);
STEALS(1) HIDDEN PyObject * psyco_ensure_text(PyObject *obj);
HIDDEN int psyco_is_text_file(PyObject *f);
HIDDEN PyObject *psyco_dict_from_conninfo_options(
PQconninfoOption *options, int include_password);
HIDDEN PyObject *psyco_make_dsn(PyObject *dsn, PyObject *kwargs);
HIDDEN PyObject *psyco_text_from_chars_safe(
const char *str, Py_ssize_t len, PyObject *decoder);
HIDDEN RAISES BORROWED PyObject *psyco_set_error(
PyObject *exc, cursorObject *curs, const char *msg);
HIDDEN PyObject *psyco_get_decimal_type(void);
HIDDEN PyObject *Bytes_Format(PyObject *format, PyObject *args);
#endif /* !defined(UTILS_H) */

90
psycopg/win32_support.c Normal file
View File

@ -0,0 +1,90 @@
/* win32_support.c - emulate some functions missing on Win32
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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/win32_support.h"
#ifdef _WIN32
#ifndef __MINGW32__
/* millisecond-precision port of gettimeofday for Win32, taken from
src/port/gettimeofday.c in PostgreSQL core */
/* FILETIME of Jan 1 1970 00:00:00. */
static const unsigned __int64 epoch = ((unsigned __int64) 116444736000000000ULL);
/*
* timezone information is stored outside the kernel so tzp isn't used anymore.
*
* Note: this function is not for Win32 high precision timing purpose. See
* elapsed_time().
*/
int
gettimeofday(struct timeval * tp, void * tzp)
{
FILETIME file_time;
SYSTEMTIME system_time;
ULARGE_INTEGER ularge;
GetSystemTime(&system_time);
SystemTimeToFileTime(&system_time, &file_time);
ularge.LowPart = file_time.dwLowDateTime;
ularge.HighPart = file_time.dwHighDateTime;
tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L);
tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
return 0;
}
/* timeradd missing on MS VC */
void
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec + b->tv_sec;
c->tv_usec = a->tv_usec + b->tv_usec;
if(c->tv_usec >= 1000000L) {
c->tv_usec -= 1000000L;
c->tv_sec += 1;
}
}
#endif /* !defined(__MINGW32__) */
/* timersub is missing on mingw & MS VC */
void
timersub(struct timeval *a, struct timeval *b, struct timeval *c)
{
c->tv_sec = a->tv_sec - b->tv_sec;
c->tv_usec = a->tv_usec - b->tv_usec;
if (c->tv_usec < 0) {
c->tv_usec += 1000000;
c->tv_sec -= 1;
}
}
#endif /* defined(_WIN32) */

56
psycopg/win32_support.h Normal file
View File

@ -0,0 +1,56 @@
/* win32_support.h - definitions for win32_support.c
*
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_WIN32_SUPPORT_H
#define PSYCOPG_WIN32_SUPPORT_H
#include "psycopg/config.h"
#ifdef _WIN32
#include <time.h>
#endif
#ifdef __MINGW32__
#include <sys/time.h>
#endif
#ifdef _WIN32
#ifndef __MINGW32__
extern HIDDEN int gettimeofday(struct timeval * tp, void * tzp);
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
#elif
#endif
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
#ifndef timercmp
#define timercmp(a, b, cmp) \
(((a)->tv_sec == (b)->tv_sec) ? \
((a)->tv_usec cmp (b)->tv_usec) : \
((a)->tv_sec cmp (b)->tv_sec))
#endif
#endif
#endif /* !defined(PSYCOPG_WIN32_SUPPORT_H) */

52
psycopg/xid.h Normal file
View File

@ -0,0 +1,52 @@
/* xid.h - definition for the psycopg Xid type
*
* Copyright (C) 2008-2019 James Henstridge <james@jamesh.id.au>
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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.
*/
#ifndef PSYCOPG_XID_H
#define PSYCOPG_XID_H 1
extern HIDDEN PyTypeObject xidType;
typedef struct {
PyObject_HEAD
/* the Python-style three-part transaction ID */
PyObject *format_id;
PyObject *gtrid;
PyObject *bqual;
/* Additional information PostgreSQL exposes about prepared transactions */
PyObject *prepared;
PyObject *owner;
PyObject *database;
} xidObject;
HIDDEN xidObject *xid_ensure(PyObject *oxid);
HIDDEN xidObject *xid_from_string(PyObject *s);
HIDDEN PyObject *xid_get_tid(xidObject *self);
HIDDEN PyObject *xid_recover(PyObject *conn);
#endif /* PSYCOPG_XID_H */

665
psycopg/xid_type.c Normal file
View File

@ -0,0 +1,665 @@
/* xid_type.c - python interface to Xid objects
*
* Copyright (C) 2008 Canonical Ltd.
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 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/xid.h"
#include "psycopg/cursor.h"
static const char xid_doc[] =
"A transaction identifier used for two-phase commit.\n\n"
"Usually returned by the connection methods `~connection.xid()` and\n"
"`~connection.tpc_recover()`.\n"
"`!Xid` instances can be unpacked as a 3-item tuples containing the items\n"
":samp:`({format_id},{gtrid},{bqual})`.\n"
"The `!str()` of the object returns the *transaction ID* used\n"
"in the commands sent to the server.\n\n"
"See :ref:`tpc` for an introduction.";
static const char format_id_doc[] =
"Format ID in a XA transaction.\n\n"
"A non-negative 32 bit integer.\n"
"`!None` if the transaction doesn't follow the XA standard.";
static const char gtrid_doc[] =
"Global transaction ID in a XA transaction.\n\n"
"If the transaction doesn't follow the XA standard, it is the plain\n"
"*transaction ID* used in the server commands.";
static const char bqual_doc[] =
"Branch qualifier of the transaction.\n\n"
"In a XA transaction every resource participating to a transaction\n"
"receives a distinct branch qualifier.\n"
"`!None` if the transaction doesn't follow the XA standard.";
static const char prepared_doc[] =
"Timestamp (with timezone) in which a recovered transaction was prepared.";
static const char owner_doc[] =
"Name of the user who prepared a recovered transaction.";
static const char database_doc[] =
"Database the recovered transaction belongs to.";
static PyMemberDef xid_members[] = {
{ "format_id", T_OBJECT, offsetof(xidObject, format_id), READONLY, (char *)format_id_doc },
{ "gtrid", T_OBJECT, offsetof(xidObject, gtrid), READONLY, (char *)gtrid_doc },
{ "bqual", T_OBJECT, offsetof(xidObject, bqual), READONLY, (char *)bqual_doc },
{ "prepared", T_OBJECT, offsetof(xidObject, prepared), READONLY, (char *)prepared_doc },
{ "owner", T_OBJECT, offsetof(xidObject, owner), READONLY, (char *)owner_doc },
{ "database", T_OBJECT, offsetof(xidObject, database), READONLY, (char *)database_doc },
{ NULL }
};
static PyObject *
xid_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
return type->tp_alloc(type, 0);
}
static int
xid_init(xidObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {"format_id", "gtrid", "bqual", NULL};
int format_id;
size_t i, gtrid_len, bqual_len;
const char *gtrid, *bqual;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iss", kwlist,
&format_id, &gtrid, &bqual))
return -1;
if (format_id < 0 || format_id > 0x7fffffff) {
PyErr_SetString(PyExc_ValueError,
"format_id must be a non-negative 32-bit integer");
return -1;
}
/* make sure that gtrid is no more than 64 characters long and
made of printable characters (which we're defining as those
between 0x20 and 0x7f). */
gtrid_len = strlen(gtrid);
if (gtrid_len > 64) {
PyErr_SetString(PyExc_ValueError,
"gtrid must be a string no longer than 64 characters");
return -1;
}
for (i = 0; i < gtrid_len; i++) {
if (gtrid[i] < 0x20 || gtrid[i] >= 0x7f) {
PyErr_SetString(PyExc_ValueError,
"gtrid must contain only printable characters.");
return -1;
}
}
/* Same for bqual */
bqual_len = strlen(bqual);
if (bqual_len > 64) {
PyErr_SetString(PyExc_ValueError,
"bqual must be a string no longer than 64 characters");
return -1;
}
for (i = 0; i < bqual_len; i++) {
if (bqual[i] < 0x20 || bqual[i] >= 0x7f) {
PyErr_SetString(PyExc_ValueError,
"bqual must contain only printable characters.");
return -1;
}
}
if (!(self->format_id = PyInt_FromLong(format_id))) { return -1; }
if (!(self->gtrid = Text_FromUTF8(gtrid))) { return -1; }
if (!(self->bqual = Text_FromUTF8(bqual))) { return -1; }
Py_INCREF(Py_None); self->prepared = Py_None;
Py_INCREF(Py_None); self->owner = Py_None;
Py_INCREF(Py_None); self->database = Py_None;
return 0;
}
static void
xid_dealloc(xidObject *self)
{
Py_CLEAR(self->format_id);
Py_CLEAR(self->gtrid);
Py_CLEAR(self->bqual);
Py_CLEAR(self->prepared);
Py_CLEAR(self->owner);
Py_CLEAR(self->database);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static Py_ssize_t
xid_len(xidObject *self)
{
return 3;
}
static PyObject *
xid_getitem(xidObject *self, Py_ssize_t item)
{
if (item < 0)
item += 3;
switch (item) {
case 0:
Py_INCREF(self->format_id);
return self->format_id;
case 1:
Py_INCREF(self->gtrid);
return self->gtrid;
case 2:
Py_INCREF(self->bqual);
return self->bqual;
default:
PyErr_SetString(PyExc_IndexError, "index out of range");
return NULL;
}
}
static PyObject *
xid_str(xidObject *self)
{
return xid_get_tid(self);
}
static PyObject *
xid_repr(xidObject *self)
{
PyObject *rv = NULL;
PyObject *format = NULL;
PyObject *args = NULL;
if (Py_None == self->format_id) {
if (!(format = Text_FromUTF8("<Xid: %r (unparsed)>"))) {
goto exit;
}
if (!(args = PyTuple_New(1))) { goto exit; }
Py_INCREF(self->gtrid);
PyTuple_SET_ITEM(args, 0, self->gtrid);
}
else {
if (!(format = Text_FromUTF8("<Xid: (%r, %r, %r)>"))) {
goto exit;
}
if (!(args = PyTuple_New(3))) { goto exit; }
Py_INCREF(self->format_id);
PyTuple_SET_ITEM(args, 0, self->format_id);
Py_INCREF(self->gtrid);
PyTuple_SET_ITEM(args, 1, self->gtrid);
Py_INCREF(self->bqual);
PyTuple_SET_ITEM(args, 2, self->bqual);
}
rv = Text_Format(format, args);
exit:
Py_XDECREF(args);
Py_XDECREF(format);
return rv;
}
static const char xid_from_string_doc[] =
"Create a `!Xid` object from a string representation. Static method.\n\n"
"If *s* is a PostgreSQL transaction ID produced by a XA transaction,\n"
"the returned object will have `format_id`, `gtrid`, `bqual` set to\n"
"the values of the preparing XA id.\n"
"Otherwise only the `!gtrid` is populated with the unparsed string.\n"
"The operation is the inverse of the one performed by `!str(xid)`.";
static PyObject *
xid_from_string_method(PyObject *cls, PyObject *args)
{
PyObject *s = NULL;
if (!PyArg_ParseTuple(args, "O", &s)) { return NULL; }
return (PyObject *)xid_from_string(s);
}
static PySequenceMethods xid_sequence = {
(lenfunc)xid_len, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
(ssizeargfunc)xid_getitem, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
0, /* sq_contains */
0, /* sq_inplace_concat */
0, /* sq_inplace_repeat */
};
static struct PyMethodDef xid_methods[] = {
{"from_string", (PyCFunction)xid_from_string_method,
METH_VARARGS|METH_STATIC, xid_from_string_doc},
{NULL}
};
PyTypeObject xidType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Xid",
sizeof(xidObject), 0,
(destructor)xid_dealloc, /* tp_dealloc */
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)xid_repr, /*tp_repr*/
0, /*tp_as_number*/
&xid_sequence, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
(reprfunc)xid_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
/* Notify is not GC as it only has string attributes */
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
xid_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
xid_methods, /*tp_methods*/
xid_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)xid_init, /*tp_init*/
0, /*tp_alloc*/
xid_new, /*tp_new*/
};
/* Convert a Python object into a proper xid.
*
* Return a new reference to the object or set an exception.
*
* The idea is that people can either create a xid from connection.xid
* or use a regular string they have found in PostgreSQL's pg_prepared_xacts
* in order to recover a transaction not generated by psycopg.
*/
xidObject *xid_ensure(PyObject *oxid)
{
xidObject *rv = NULL;
if (PyObject_TypeCheck(oxid, &xidType)) {
Py_INCREF(oxid);
rv = (xidObject *)oxid;
}
else {
rv = xid_from_string(oxid);
}
return rv;
}
/* Encode or decode a string in base64. */
static PyObject *
_xid_base64_enc_dec(const char *funcname, PyObject *s)
{
PyObject *base64 = NULL;
PyObject *func = NULL;
PyObject *rv = NULL;
if (!(base64 = PyImport_ImportModule("base64"))) { goto exit; }
if (!(func = PyObject_GetAttrString(base64, funcname))) { goto exit; }
Py_INCREF(s);
if (!(s = psyco_ensure_bytes(s))) { goto exit; }
rv = psyco_ensure_text(PyObject_CallFunctionObjArgs(func, s, NULL));
Py_DECREF(s);
exit:
Py_XDECREF(func);
Py_XDECREF(base64);
return rv;
}
/* Return a base64-encoded string. */
static PyObject *
_xid_encode64(PyObject *s)
{
return _xid_base64_enc_dec("b64encode", s);
}
/* Decode a base64-encoded string */
static PyObject *
_xid_decode64(PyObject *s)
{
return _xid_base64_enc_dec("b64decode", s);
}
/* Return the PostgreSQL transaction_id for this XA xid.
*
* PostgreSQL wants just a string, while the DBAPI supports the XA standard
* and thus a triple. We use the same conversion algorithm implemented by JDBC
* in order to allow some form of interoperation.
*
* The function must be called while holding the GIL.
*
* see also: the pgjdbc implementation
* http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/jdbc/pgjdbc/org/postgresql/xa/RecoveredXid.java?rev=1.2
*/
PyObject *
xid_get_tid(xidObject *self)
{
PyObject *rv = NULL;
PyObject *egtrid = NULL;
PyObject *ebqual = NULL;
PyObject *format = NULL;
PyObject *args = NULL;
if (Py_None == self->format_id) {
/* Unparsed xid: return the gtrid. */
Py_INCREF(self->gtrid);
rv = self->gtrid;
}
else {
/* XA xid: mash together the components. */
if (!(egtrid = _xid_encode64(self->gtrid))) { goto exit; }
if (!(ebqual = _xid_encode64(self->bqual))) { goto exit; }
/* rv = "%d_%s_%s" % (format_id, egtrid, ebqual) */
if (!(format = Text_FromUTF8("%d_%s_%s"))) { goto exit; }
if (!(args = PyTuple_New(3))) { goto exit; }
Py_INCREF(self->format_id);
PyTuple_SET_ITEM(args, 0, self->format_id);
PyTuple_SET_ITEM(args, 1, egtrid); egtrid = NULL;
PyTuple_SET_ITEM(args, 2, ebqual); ebqual = NULL;
if (!(rv = Text_Format(format, args))) { goto exit; }
}
exit:
Py_XDECREF(args);
Py_XDECREF(format);
Py_XDECREF(egtrid);
Py_XDECREF(ebqual);
return rv;
}
/* Return the regex object to parse a Xid string.
*
* Return a borrowed reference. */
BORROWED static PyObject *
_xid_get_parse_regex(void) {
static PyObject *rv;
if (!rv) {
PyObject *re_mod = NULL;
PyObject *comp = NULL;
PyObject *regex = NULL;
Dprintf("compiling regexp to parse transaction id");
if (!(re_mod = PyImport_ImportModule("re"))) { goto exit; }
if (!(comp = PyObject_GetAttrString(re_mod, "compile"))) { goto exit; }
if (!(regex = PyObject_CallFunction(comp, "s",
"^(\\d+)_([^_]*)_([^_]*)$"))) {
goto exit;
}
/* Good, compiled. */
rv = regex;
regex = NULL;
exit:
Py_XDECREF(regex);
Py_XDECREF(comp);
Py_XDECREF(re_mod);
}
return rv;
}
/* Try to parse a Xid string representation in a Xid object.
*
*
* Return NULL + exception if parsing failed. Else a new Xid object. */
static xidObject *
_xid_parse_string(PyObject *str) {
PyObject *regex;
PyObject *m = NULL;
PyObject *group = NULL;
PyObject *item = NULL;
PyObject *format_id = NULL;
PyObject *egtrid = NULL;
PyObject *ebqual = NULL;
PyObject *gtrid = NULL;
PyObject *bqual = NULL;
xidObject *rv = NULL;
/* check if the string is a possible XA triple with a regexp */
if (!(regex = _xid_get_parse_regex())) { goto exit; }
if (!(m = PyObject_CallMethod(regex, "match", "O", str))) { goto exit; }
if (m == Py_None) {
PyErr_SetString(PyExc_ValueError, "bad xid format");
goto exit;
}
/* Extract the components from the regexp */
if (!(group = PyObject_GetAttrString(m, "group"))) { goto exit; }
if (!(item = PyObject_CallFunction(group, "i", 1))) { goto exit; }
if (!(format_id = PyObject_CallFunctionObjArgs(
(PyObject *)&PyInt_Type, item, NULL))) {
goto exit;
}
if (!(egtrid = PyObject_CallFunction(group, "i", 2))) { goto exit; }
if (!(gtrid = _xid_decode64(egtrid))) { goto exit; }
if (!(ebqual = PyObject_CallFunction(group, "i", 3))) { goto exit; }
if (!(bqual = _xid_decode64(ebqual))) { goto exit; }
/* Try to build the xid with the parsed material */
rv = (xidObject *)PyObject_CallFunctionObjArgs((PyObject *)&xidType,
format_id, gtrid, bqual, NULL);
exit:
Py_XDECREF(bqual);
Py_XDECREF(ebqual);
Py_XDECREF(gtrid);
Py_XDECREF(egtrid);
Py_XDECREF(format_id);
Py_XDECREF(item);
Py_XDECREF(group);
Py_XDECREF(m);
return rv;
}
/* Return a new Xid object representing a transaction ID not conform to
* the XA specifications. */
static xidObject *
_xid_unparsed_from_string(PyObject *str) {
xidObject *xid = NULL;
xidObject *rv = NULL;
/* fake args to work around the checks performed by the xid init */
if (!(xid = (xidObject *)PyObject_CallFunction((PyObject *)&xidType,
"iss", 0, "", ""))) {
goto exit;
}
/* set xid.gtrid = str */
Py_CLEAR(xid->gtrid);
Py_INCREF(str);
xid->gtrid = str;
/* set xid.format_id = None */
Py_CLEAR(xid->format_id);
Py_INCREF(Py_None);
xid->format_id = Py_None;
/* set xid.bqual = None */
Py_CLEAR(xid->bqual);
Py_INCREF(Py_None);
xid->bqual = Py_None;
/* return the finished object */
rv = xid;
xid = NULL;
exit:
Py_XDECREF(xid);
return rv;
}
/* Build a Xid from a string representation.
*
* If the xid is in the format generated by Psycopg, unpack the tuple into
* the struct members. Otherwise generate an "unparsed" xid.
*/
xidObject *
xid_from_string(PyObject *str) {
xidObject *rv;
if (!(Bytes_Check(str) || PyUnicode_Check(str))) {
PyErr_SetString(PyExc_TypeError, "not a valid transaction id");
return NULL;
}
/* Try to parse an XA triple from the string. This may fail for several
* reasons, such as the rules stated in Xid.__init__. */
rv = _xid_parse_string(str);
if (!rv) {
/* If parsing failed, treat the string as an unparsed id */
PyErr_Clear();
rv = _xid_unparsed_from_string(str);
}
return rv;
}
/* conn_tpc_recover -- return a list of pending TPC Xid */
PyObject *
xid_recover(PyObject *conn)
{
PyObject *rv = NULL;
PyObject *curs = NULL;
PyObject *xids = NULL;
xidObject *xid = NULL;
PyObject *recs = NULL;
PyObject *rec = NULL;
PyObject *item = NULL;
PyObject *tmp;
Py_ssize_t len, i;
/* curs = conn.cursor()
* (sort of. Use the real cursor in case the connection returns
* something non-dbapi -- see ticket #114) */
if (!(curs = PyObject_CallFunctionObjArgs(
(PyObject *)&cursorType, conn, NULL))) { goto exit; }
/* curs.execute(...) */
if (!(tmp = PyObject_CallMethod(curs, "execute", "s",
"SELECT gid, prepared, owner, database FROM pg_prepared_xacts")))
{
goto exit;
}
Py_DECREF(tmp);
/* recs = curs.fetchall() */
if (!(recs = PyObject_CallMethod(curs, "fetchall", NULL))) { goto exit; }
/* curs.close() */
if (!(tmp = PyObject_CallMethod(curs, "close", NULL))) { goto exit; }
Py_DECREF(tmp);
/* Build the list with return values. */
if (0 > (len = PySequence_Size(recs))) { goto exit; }
if (!(xids = PyList_New(len))) { goto exit; }
/* populate the xids list */
for (i = 0; i < len; ++i) {
if (!(rec = PySequence_GetItem(recs, i))) { goto exit; }
/* Get the xid with the XA triple set */
if (!(item = PySequence_GetItem(rec, 0))) { goto exit; }
if (!(xid = xid_from_string(item))) { goto exit; }
Py_CLEAR(item);
/* set xid.prepared */
Py_CLEAR(xid->prepared);
if (!(xid->prepared = PySequence_GetItem(rec, 1))) { goto exit; }
/* set xid.owner */
Py_CLEAR(xid->owner);
if (!(xid->owner = PySequence_GetItem(rec, 2))) { goto exit; }
/* set xid.database */
Py_CLEAR(xid->database);
if (!(xid->database = PySequence_GetItem(rec, 3))) { goto exit; }
/* xid finished: add it to the returned list */
PyList_SET_ITEM(xids, i, (PyObject *)xid);
xid = NULL; /* ref stolen */
Py_CLEAR(rec);
}
/* set the return value. */
rv = xids;
xids = NULL;
exit:
Py_XDECREF(xids);
Py_XDECREF(xid);
Py_XDECREF(curs);
Py_XDECREF(recs);
Py_XDECREF(rec);
Py_XDECREF(item);
return rv;
}