first commit based on psycopg2 2.9 version
This commit is contained in:
15
psycopg/_psycopg.vc9.amd64.manifest
Normal file
15
psycopg/_psycopg.vc9.amd64.manifest
Normal 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>
|
||||
15
psycopg/_psycopg.vc9.x86.manifest
Normal file
15
psycopg/_psycopg.vc9.x86.manifest
Normal 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
195
psycopg/adapter_asis.c
Normal 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
48
psycopg/adapter_asis.h
Normal 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
281
psycopg/adapter_binary.c
Normal 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
48
psycopg/adapter_binary.h
Normal 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
515
psycopg/adapter_datetime.c
Normal 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
107
psycopg/adapter_datetime.h
Normal 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
342
psycopg/adapter_list.c
Normal 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
47
psycopg/adapter_list.h
Normal 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
185
psycopg/adapter_pboolean.c
Normal 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*/
|
||||
};
|
||||
48
psycopg/adapter_pboolean.h
Normal file
48
psycopg/adapter_pboolean.h
Normal 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
248
psycopg/adapter_pdecimal.c
Normal 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*/
|
||||
};
|
||||
48
psycopg/adapter_pdecimal.h
Normal file
48
psycopg/adapter_pdecimal.h
Normal 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
221
psycopg/adapter_pfloat.c
Normal 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
48
psycopg/adapter_pfloat.h
Normal 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
222
psycopg/adapter_pint.c
Normal 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
48
psycopg/adapter_pint.h
Normal 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
307
psycopg/adapter_qstring.c
Normal 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
52
psycopg/adapter_qstring.h
Normal 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
58
psycopg/aix_support.c
Normal 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
48
psycopg/aix_support.h
Normal 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
309
psycopg/bytes_format.c
Normal 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
49
psycopg/column.h
Normal 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
420
psycopg/column_type.c
Normal 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
216
psycopg/config.h
Normal 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
229
psycopg/connection.h
Normal 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
1553
psycopg/connection_int.c
Normal file
File diff suppressed because it is too large
Load Diff
1517
psycopg/connection_type.c
Normal file
1517
psycopg/connection_type.c
Normal file
File diff suppressed because it is too large
Load Diff
41
psycopg/conninfo.h
Normal file
41
psycopg/conninfo.h
Normal 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
648
psycopg/conninfo_type.c
Normal 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
147
psycopg/cursor.h
Normal 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
171
psycopg/cursor_int.c
Normal 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
2126
psycopg/cursor_type.c
Normal file
File diff suppressed because it is too large
Load Diff
41
psycopg/diagnostics.h
Normal file
41
psycopg/diagnostics.h
Normal 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
208
psycopg/diagnostics_type.c
Normal 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
46
psycopg/error.h
Normal 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
376
psycopg/error_type.c
Normal 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
210
psycopg/green.c
Normal 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
76
psycopg/green.h
Normal 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
105
psycopg/libpq_support.c
Normal 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
49
psycopg/libpq_support.h
Normal 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
102
psycopg/lobject.h
Normal 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
486
psycopg/lobject_int.c
Normal 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
471
psycopg/lobject_type.c
Normal 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
277
psycopg/microprotocols.c
Normal 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
64
psycopg/microprotocols.h
Normal 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) */
|
||||
180
psycopg/microprotocols_proto.c
Normal file
180
psycopg/microprotocols_proto.c
Normal 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*/
|
||||
};
|
||||
47
psycopg/microprotocols_proto.h
Normal file
47
psycopg/microprotocols_proto.h
Normal 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
41
psycopg/notify.h
Normal 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
298
psycopg/notify_type.c
Normal 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) == ¬ifyType) {
|
||||
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*/
|
||||
¬ify_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
65
psycopg/pgtypes.h
Normal 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
1834
psycopg/pqpath.c
Normal file
File diff suppressed because it is too large
Load Diff
74
psycopg/pqpath.h
Normal file
74
psycopg/pqpath.h
Normal 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
107
psycopg/psycopg.h
Normal 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
1030
psycopg/psycopgmodule.c
Normal file
File diff suppressed because it is too large
Load Diff
99
psycopg/python.h
Normal file
99
psycopg/python.h
Normal 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) */
|
||||
53
psycopg/replication_connection.h
Normal file
53
psycopg/replication_connection.h
Normal 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) */
|
||||
193
psycopg/replication_connection_type.c
Normal file
193
psycopg/replication_connection_type.c
Normal 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*/
|
||||
};
|
||||
66
psycopg/replication_cursor.h
Normal file
66
psycopg/replication_cursor.h
Normal 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) */
|
||||
394
psycopg/replication_cursor_type.c
Normal file
394
psycopg/replication_cursor_type.c
Normal 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*/
|
||||
};
|
||||
58
psycopg/replication_message.h
Normal file
58
psycopg/replication_message.h
Normal 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) */
|
||||
195
psycopg/replication_message_type.c
Normal file
195
psycopg/replication_message_type.c
Normal 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
58
psycopg/solaris_support.c
Normal 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
48
psycopg/solaris_support.h
Normal 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
335
psycopg/sqlstate_errors.h
Normal 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
620
psycopg/typecast.c
Normal 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
91
psycopg/typecast.h
Normal 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
298
psycopg/typecast_array.c
Normal 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, "es);
|
||||
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
150
psycopg/typecast_basic.c
Normal 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
275
psycopg/typecast_binary.c
Normal 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
50
psycopg/typecast_binary.h
Normal 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) */
|
||||
71
psycopg/typecast_builtins.c
Normal file
71
psycopg/typecast_builtins.c
Normal 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
486
psycopg/typecast_datetime.c
Normal 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
456
psycopg/utils.c
Normal 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
65
psycopg/utils.h
Normal 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
90
psycopg/win32_support.c
Normal 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
56
psycopg/win32_support.h
Normal 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
52
psycopg/xid.h
Normal 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
665
psycopg/xid_type.c
Normal 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, >rid, &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;
|
||||
}
|
||||
Reference in New Issue
Block a user