*
* 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.
*/
except for pg_execute and pq_fetch (that are somehow high-level). This means
that all the other functions should be called while holding a lock to the
connection.
*/
#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"
#include "psycopg/pqpath.h"
#include "psycopg/connection.h"
#include "psycopg/cursor.h"
#include "psycopg/replication_cursor.h"
#include "psycopg/replication_message.h"
#include "psycopg/green.h"
#include "psycopg/typecast.h"
#include "psycopg/pgtypes.h"
#include "psycopg/error.h"
#include "psycopg/column.h"
#include <stdlib.h>
#include "psycopg/libpq_support.h"
#include "libpq-fe.h"
#include <stdlib.h>
#ifdef _WIN32
#include <winsock2.h>
#include "win32_support.h"
#elif defined(__sun) && defined(__SVR4)
#include "solaris_support.h"
#elif defined(_AIX)
#include "aix_support.h"
#else
#include <sys/time.h>
#endif
extern HIDDEN PyObject *psyco_DescriptionType;
extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[];
extern HIDDEN const char *srv_deferrable[];
static const char *
strip_severity(const char *msg)
{
if (!msg)
return NULL;
if (strlen(msg) > 8 && (!strncmp(msg, "ERROR: ", 8) ||
!strncmp(msg, "FATAL: ", 8) ||
!strncmp(msg, "PANIC: ", 8)))
return &msg[8];
else
return msg;
}
This function should be called while holding the GIL.
The function passes the ownership of the pgres to the returned exception,
where the pgres was the explicit argument or taken from the cursor.
So, after calling it curs->pgres will be set to null */
RAISES static void
pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
{
PyObject *exc = NULL;
const char *err = NULL;
const char *err2 = NULL;
const char *code = NULL;
PyObject *pyerr = NULL;
PyObject *pgerror = NULL, *pgcode = NULL;
if (conn == NULL) {
PyErr_SetString(DatabaseError,
"psycopg went psychotic and raised a null error");
return;
}
object as closed but requiring cleanup */
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) {
conn->closed = 2;
exc = OperationalError;
}
if (pgres == NULL && curs != NULL)
pgres = &curs->pgres;
if (pgres && *pgres) {
err = PQresultErrorMessage(*pgres);
if (err != NULL) {
Dprintf("pq_raise: PQresultErrorMessage: err=%s", err);
code = PQresultErrorField(*pgres, PG_DIAG_SQLSTATE);
}
}
if (err == NULL) {
err = PQerrorMessage(conn->pgconn);
Dprintf("pq_raise: PQerrorMessage: err=%s", err);
}
we need to set an exception anyway because the caller will probably
raise and a meaningful message is better than an empty one.
Note: it can happen without it being our error: see ticket #82 */
if (err == NULL || err[0] == '\0') {
PyErr_Format(DatabaseError,
"error with status %s and no message from the libpq",
PQresStatus(pgres == NULL ?
PQstatus(conn->pgconn) : PQresultStatus(*pgres)));
return;
}
(only if we got the SQLSTATE from the pgres, obviously) */
if (code != NULL) {
exc = exception_from_sqlstate(code);
}
else if (exc == NULL) {
determined that the connection was closed). */
exc = DatabaseError;
}
err2 = strip_severity(err);
Dprintf("pq_raise: err2=%s", err2);
* decoding will fail.
*/
if (!(pgerror = conn_text_from_chars(conn, err))) {
* so just print it. */
PyErr_Print();
PyErr_Clear();
}
if (!(pgcode = conn_text_from_chars(conn, code))) {
PyErr_Print();
PyErr_Clear();
}
pyerr = psyco_set_error(exc, curs, err2);
if (pyerr && PyObject_TypeCheck(pyerr, &errorType)) {
errorObject *perr = (errorObject *)pyerr;
Py_CLEAR(perr->pydecoder);
Py_XINCREF(conn->pydecoder);
perr->pydecoder = conn->pydecoder;
Py_CLEAR(perr->pgerror);
perr->pgerror = pgerror;
pgerror = NULL;
Py_CLEAR(perr->pgcode);
perr->pgcode = pgcode;
pgcode = NULL;
CLEARPGRES(perr->pgres);
if (pgres && *pgres) {
perr->pgres = *pgres;
*pgres = NULL;
}
}
Py_XDECREF(pgerror);
Py_XDECREF(pgcode);
}
note that this function does block because it needs to wait for the full
result sets of the previous query to clear them.
this function does not call any Py_*_ALLOW_THREADS macros */
void
pq_clear_async(connectionObject *conn)
{
PGresult *pgres;
many parts, i.e. "select 1; select 2", there will be many) and also
finalize asynchronous processing so the connection will be ready to
accept another query */
while ((pgres = PQgetResult(conn->pgconn))) {
Dprintf("pq_clear_async: clearing PGresult at %p", pgres);
PQclear(pgres);
}
Py_CLEAR(conn->async_cursor);
}
Accepted arg values are 1 (nonblocking) and 0 (blocking).
Return 0 if everything ok, else < 0 and set an exception.
*/
RAISES_NEG int
pq_set_non_blocking(connectionObject *conn, int arg)
{
int ret = PQsetnonblocking(conn->pgconn, arg);
if (0 != ret) {
Dprintf("PQsetnonblocking(%d) FAILED", arg);
PyErr_SetString(OperationalError, "PQsetnonblocking() failed");
ret = -1;
}
return ret;
}
This function should only be called on a locked connection without
holding the global interpreter lock.
On error, -1 is returned, and the conn->pgres will hold the
relevant result structure.
The tstate parameter should be the pointer of the _save variable created by
Py_BEGIN_ALLOW_THREADS: this enables the function to acquire and release
again the GIL if needed, i.e. if a Python wait callback must be invoked.
*/
int
pq_execute_command_locked(
connectionObject *conn, const char *query, PyThreadState **tstate)
{
int pgstatus, retvalue = -1;
Dprintf("pq_execute_command_locked: pgconn = %p, query = %s",
conn->pgconn, query);
if (!psyco_green()) {
conn_set_result(conn, PQexec(conn->pgconn, query));
} else {
PyEval_RestoreThread(*tstate);
conn_set_result(conn, psyco_exec_green(conn, query));
*tstate = PyEval_SaveThread();
}
if (conn->pgres == NULL) {
Dprintf("pq_execute_command_locked: PQexec returned NULL");
PyEval_RestoreThread(*tstate);
if (!PyErr_Occurred()) {
conn_set_error(conn, PQerrorMessage(conn->pgconn));
}
*tstate = PyEval_SaveThread();
goto cleanup;
}
pgstatus = PQresultStatus(conn->pgres);
if (pgstatus != PGRES_COMMAND_OK ) {
Dprintf("pq_execute_command_locked: result was not COMMAND_OK (%d)",
pgstatus);
goto cleanup;
}
retvalue = 0;
CLEARPGRES(conn->pgres);
cleanup:
return retvalue;
}
If pq_execute_command_locked() returns -1, this function should be
called to convert the result to a Python exception.
This function should be called while holding the global interpreter
lock.
*/
RAISES void
pq_complete_error(connectionObject *conn)
{
Dprintf("pq_complete_error: pgconn = %p, error = %s",
conn->pgconn, conn->error);
if (conn->pgres) {
pq_raise(conn, NULL, &conn->pgres);
}
else {
if (conn->error) {
PyErr_SetString(OperationalError, conn->error);
} else if (PyErr_Occurred()) {
* it with an unknown exception. (see #410) */
Dprintf("pq_complete_error: forwarding Python exception");
} else {
PyErr_SetString(OperationalError, "unknown error");
}
* end up here. With a TCP connection we get a pgres with an error
* instead, and the connection gets closed in the pq_raise call above
* (see ticket #196)
*/
if (CONNECTION_BAD == PQstatus(conn->pgconn)) {
conn->closed = 2;
}
}
conn_set_error(conn, NULL);
}
This function should only be called on a locked connection without
holding the global interpreter lock.
On error, -1 is returned, and the conn->pgres argument will hold the
relevant result structure.
*/
int
pq_begin_locked(connectionObject *conn, PyThreadState **tstate)
{
const size_t bufsize = 256;
char buf[256];
int result;
Dprintf("pq_begin_locked: pgconn = %p, %d, status = %d",
conn->pgconn, conn->autocommit, conn->status);
if (conn->status != CONN_STATUS_READY) {
Dprintf("pq_begin_locked: transaction in progress");
return 0;
}
if (conn->autocommit && !conn->entered) {
Dprintf("pq_begin_locked: autocommit and no with block");
return 0;
}
if (conn->isolevel == ISOLATION_LEVEL_DEFAULT
&& conn->readonly == STATE_DEFAULT
&& conn->deferrable == STATE_DEFAULT) {
strcpy(buf, "BEGIN");
}
else {
snprintf(buf, bufsize,
conn->server_version >= 80000 ?
"BEGIN%s%s%s%s" : "BEGIN;SET TRANSACTION%s%s%s%s",
(conn->isolevel >= 1 && conn->isolevel <= 4)
? " ISOLATION LEVEL " : "",
(conn->isolevel >= 1 && conn->isolevel <= 4)
? srv_isolevels[conn->isolevel] : "",
srv_readonly[conn->readonly],
srv_deferrable[conn->deferrable]);
}
result = pq_execute_command_locked(conn, buf, tstate);
if (result == 0)
conn->status = CONN_STATUS_BEGIN;
return result;
}
This function should be called while holding the global interpreter
lock.
*/
int
pq_commit(connectionObject *conn)
{
int retvalue = -1;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock);
Dprintf("pq_commit: pgconn = %p, status = %d",
conn->pgconn, conn->status);
if (conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_commit: no transaction to commit");
retvalue = 0;
}
else {
conn->mark += 1;
retvalue = pq_execute_command_locked(conn, "COMMIT", &_save);
}
Py_BLOCK_THREADS;
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
so we unconditionally set the connection status here. */
conn->status = CONN_STATUS_READY;
pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(conn);
return retvalue;
}
RAISES_NEG int
pq_abort_locked(connectionObject *conn, PyThreadState **tstate)
{
int retvalue = -1;
Dprintf("pq_abort_locked: pgconn = %p, status = %d",
conn->pgconn, conn->status);
if (conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_abort_locked: no transaction to abort");
return 0;
}
conn->mark += 1;
retvalue = pq_execute_command_locked(conn, "ROLLBACK", tstate);
if (retvalue == 0)
conn->status = CONN_STATUS_READY;
return retvalue;
}
This function should be called while holding the global interpreter
lock. */
RAISES_NEG int
pq_abort(connectionObject *conn)
{
int retvalue = -1;
Dprintf("pq_abort: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->autocommit, conn->status);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock);
retvalue = pq_abort_locked(conn, &_save);
Py_BLOCK_THREADS;
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS;
if (retvalue < 0)
pq_complete_error(conn);
return retvalue;
}
This function should be called while holding the global interpreter
lock.
The _locked version of this function should be called on a locked
connection without holding the global interpreter lock.
*/
RAISES_NEG int
pq_reset_locked(connectionObject *conn, PyThreadState **tstate)
{
int retvalue = -1;
Dprintf("pq_reset_locked: pgconn = %p, status = %d",
conn->pgconn, conn->status);
conn->mark += 1;
if (conn->status == CONN_STATUS_BEGIN) {
retvalue = pq_execute_command_locked(conn, "ABORT", tstate);
if (retvalue != 0) return retvalue;
}
if (conn->server_version >= 80300) {
retvalue = pq_execute_command_locked(conn, "DISCARD ALL", tstate);
if (retvalue != 0) return retvalue;
}
else {
retvalue = pq_execute_command_locked(conn, "RESET ALL", tstate);
if (retvalue != 0) return retvalue;
retvalue = pq_execute_command_locked(conn,
"SET SESSION AUTHORIZATION DEFAULT", tstate);
if (retvalue != 0) return retvalue;
}
conn->status = CONN_STATUS_READY;
return retvalue;
}
int
pq_reset(connectionObject *conn)
{
int retvalue = -1;
Dprintf("pq_reset: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->autocommit, conn->status);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock);
retvalue = pq_reset_locked(conn, &_save);
Py_BLOCK_THREADS;
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS;
if (retvalue < 0) {
pq_complete_error(conn);
}
else {
Py_CLEAR(conn->tpc_xid);
}
return retvalue;
}
*
* The function should be called on a locked connection without
* holding the GIL.
*
* The result is a new string allocated with malloc.
*/
char *
pq_get_guc_locked(connectionObject *conn, const char *param, PyThreadState **tstate)
{
char query[256];
int size;
char *rv = NULL;
Dprintf("pq_get_guc_locked: reading %s", param);
size = PyOS_snprintf(query, sizeof(query), "SHOW %s", param);
if (size < 0 || (size_t)size >= sizeof(query)) {
conn_set_error(conn, "SHOW: query too large");
goto cleanup;
}
Dprintf("pq_get_guc_locked: pgconn = %p, query = %s", conn->pgconn, query);
if (!psyco_green()) {
conn_set_result(conn, PQexec(conn->pgconn, query));
} else {
PyEval_RestoreThread(*tstate);
conn_set_result(conn, psyco_exec_green(conn, query));
*tstate = PyEval_SaveThread();
}
if (!conn->pgres) {
Dprintf("pq_get_guc_locked: PQexec returned NULL");
PyEval_RestoreThread(*tstate);
if (!PyErr_Occurred()) {
conn_set_error(conn, PQerrorMessage(conn->pgconn));
}
*tstate = PyEval_SaveThread();
goto cleanup;
}
if (PQresultStatus(conn->pgres) != PGRES_TUPLES_OK) {
Dprintf("pq_get_guc_locked: result was not TUPLES_OK (%s)",
PQresStatus(PQresultStatus(conn->pgres)));
goto cleanup;
}
rv = strdup(PQgetvalue(conn->pgres, 0, 0));
CLEARPGRES(conn->pgres);
cleanup:
return rv;
}
unsigned int
pq_get_pg_catalog_custom_type_oid(connectionObject *conn, const char *param, PyThreadState **tstate)
{
char query[256];
int size;
unsigned int rv = 0;
char *temp_oid = NULL;
size = PyOS_snprintf(query, sizeof(query), "select oid from pg_type where typnamespace = 11 and typname= %s", param);
if (size < 0 || (size_t)size >= sizeof(query)) {
conn_set_error(conn, "query too large");
goto cleanup;
}
if (!psyco_green()) {
conn_set_result(conn, PQexec(conn->pgconn, query));
} else {
PyEval_RestoreThread(*tstate);
conn_set_result(conn, psyco_exec_green(conn, query));
*tstate = PyEval_SaveThread();
}
if (!conn->pgres) {
Dprintf("pq_get_pg_catalog_custom_type_oid: PQexec returned NULL");
PyEval_RestoreThread(*tstate);
if (!PyErr_Occurred()) {
conn_set_error(conn, PQerrorMessage(conn->pgconn));
}
*tstate = PyEval_SaveThread();
goto cleanup;
}
if (PQresultStatus(conn->pgres) != PGRES_TUPLES_OK) {
Dprintf("pq_get_pg_catalog_custom_type_oid: result was not TUPLES_OK (%s)",
PQresStatus(PQresultStatus(conn->pgres)));
goto cleanup;
}
temp_oid = PQgetvalue(conn->pgres, 0, 0);
if(temp_oid)
{
rv = (unsigned int)atoi(temp_oid);
}
CLEARPGRES(conn->pgres);
cleanup:
return rv;
}
*
* The function should be called on a locked connection without
* holding the GIL
*/
int
pq_set_guc_locked(
connectionObject *conn, const char *param, const char *value,
PyThreadState **tstate)
{
char query[256];
int size;
int rv = -1;
Dprintf("pq_set_guc_locked: setting %s to %s", param, value);
if (0 == strcmp(value, "default")) {
size = PyOS_snprintf(query, sizeof(query),
"SET %s TO DEFAULT", param);
}
else {
size = PyOS_snprintf(query, sizeof(query),
"SET %s TO '%s'", param, value);
}
if (size < 0 || (size_t)size >= sizeof(query)) {
conn_set_error(conn, "SET: query too large");
goto exit;
}
rv = pq_execute_command_locked(conn, query, tstate);
exit:
return rv;
}
*
* This function should only be called on a locked connection without
* holding the global interpreter lock. */
int
pq_tpc_command_locked(
connectionObject *conn, const char *cmd, const char *tid,
PyThreadState **tstate)
{
int rv = -1;
char *etid = NULL, *buf = NULL;
Py_ssize_t buflen;
Dprintf("_pq_tpc_command: pgconn = %p, command = %s",
conn->pgconn, cmd);
conn->mark += 1;
PyEval_RestoreThread(*tstate);
if (!(etid = psyco_escape_string(conn, tid, -1, NULL, NULL)))
{ goto exit; }
buflen = 2 + strlen(cmd) + strlen(etid);
if (!(buf = PyMem_Malloc(buflen))) {
PyErr_NoMemory();
goto exit;
}
if (0 > PyOS_snprintf(buf, buflen, "%s %s", cmd, etid)) { goto exit; }
*tstate = PyEval_SaveThread();
rv = pq_execute_command_locked(conn, buf, tstate);
PyEval_RestoreThread(*tstate);
exit:
PyMem_Free(buf);
PyMem_Free(etid);
*tstate = PyEval_SaveThread();
return rv;
}
*
* Return 0 if the result is ready, 1 if it will block, -1 on error.
* The last result will be returned in conn->pgres.
*
* The function should be called with the lock and holding the GIL.
*/
RAISES_NEG int
pq_get_result_async(connectionObject *conn)
{
int rv = -1;
Dprintf("pq_get_result_async: calling PQconsumeInput()");
if (PQconsumeInput(conn->pgconn) == 0) {
Dprintf("pq_get_result_async: PQconsumeInput() failed");
if (CONNECTION_BAD == PQstatus(conn->pgconn)) {
conn->closed = 2;
}
PyErr_SetString(OperationalError, PQerrorMessage(conn->pgconn));
goto exit;
}
conn_notifies_process(conn);
conn_notice_process(conn);
for (;;) {
int busy;
PGresult *res;
ExecStatusType status;
Dprintf("pq_get_result_async: calling PQisBusy()");
busy = PQisBusy(conn->pgconn);
if (busy) {
Dprintf("pq_get_result_async: PQisBusy() = 1");
rv = 1;
goto exit;
}
if (!(res = PQgetResult(conn->pgconn))) {
Dprintf("pq_get_result_async: got no result");
rv = 0;
goto exit;
}
status = PQresultStatus(res);
Dprintf("pq_get_result_async: got result %s", PQresStatus(status));
* one and we may have to do it across poll calls. However if there is
* an error in the stream of results we want to handle the *first*
* error. So don't clobber it with the following ones. */
if (conn->pgres && PQresultStatus(conn->pgres) == PGRES_FATAL_ERROR) {
Dprintf("previous pgres is error: discarding");
PQclear(res);
}
else {
conn_set_result(conn, res);
}
switch (status) {
case PGRES_COPY_OUT:
case PGRES_COPY_IN:
case PGRES_COPY_BOTH:
* PGresult for us every time we query for it, so we need to
* break out of this endless loop. */
rv = 0;
goto exit;
default:
* we have finished. */
continue;
}
}
exit:
return rv;
}
a status of 1 means that a some data is still pending to be flushed, while a
status of 0 means that there is no data waiting to be sent. -1 means an
error and an exception will be set accordingly.
this function locks the connection object
this function call Py_*_ALLOW_THREADS macros */
int
pq_flush(connectionObject *conn)
{
int res;
Dprintf("pq_flush: flushing output");
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(conn->lock));
res = PQflush(conn->pgconn);
pthread_mutex_unlock(&(conn->lock));
Py_END_ALLOW_THREADS;
return res;
}
*
* With no_result an eventual query result is discarded.
* Currently only used to implement cursor.executemany().
*
* This function locks the connection object
* This function call Py_*_ALLOW_THREADS macros
*/
RAISES_NEG int
_pq_execute_sync(cursorObject *curs, const char *query, int no_result, int no_begin)
{
connectionObject *conn = curs->conn;
CLEARPGRES(curs->pgres);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(conn->lock));
if (!no_begin && pq_begin_locked(conn, &_save) < 0) {
pthread_mutex_unlock(&(conn->lock));
Py_BLOCK_THREADS;
pq_complete_error(conn);
return -1;
}
Dprintf("pq_execute: executing SYNC query: pgconn = %p", conn->pgconn);
Dprintf(" %-.200s", query);
if (!psyco_green()) {
conn_set_result(conn, PQexec(conn->pgconn, query));
}
else {
Py_BLOCK_THREADS;
conn_set_result(conn, psyco_exec_green(conn, query));
Py_UNBLOCK_THREADS;
}
if (!conn->pgres) {
if (CONNECTION_BAD == PQstatus(conn->pgconn)) {
conn->closed = 2;
}
pthread_mutex_unlock(&(conn->lock));
Py_BLOCK_THREADS;
if (!PyErr_Occurred()) {
PyErr_SetString(OperationalError,
PQerrorMessage(conn->pgconn));
}
return -1;
}
Py_BLOCK_THREADS;
curs_set_result(curs, conn->pgres);
conn->pgres = NULL;
* into the same critical section that received the data. Without this
* care, reading notifies may disrupt other thread communications.
* (as in ticket #55). */
conn_notifies_process(conn);
conn_notice_process(conn);
Py_UNBLOCK_THREADS;
pthread_mutex_unlock(&(conn->lock));
Py_END_ALLOW_THREADS;
to respect the old DBAPI-2.0 compatible behaviour */
Dprintf("pq_execute: entering synchronous DBAPI compatibility mode");
if (pq_fetch(curs, no_result) < 0) return -1;
return 1;
}
RAISES_NEG int
_pq_execute_async(cursorObject *curs, const char *query, int no_result)
{
int async_status = ASYNC_WRITE;
connectionObject *conn = curs->conn;
int ret;
CLEARPGRES(curs->pgres);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(conn->lock));
Dprintf("pq_execute: executing ASYNC query: pgconn = %p", conn->pgconn);
Dprintf(" %-.200s", query);
if (PQsendQuery(conn->pgconn, query) == 0) {
if (CONNECTION_BAD == PQstatus(conn->pgconn)) {
conn->closed = 2;
}
pthread_mutex_unlock(&(conn->lock));
Py_BLOCK_THREADS;
PyErr_SetString(OperationalError,
PQerrorMessage(conn->pgconn));
return -1;
}
Dprintf("pq_execute: async query sent to backend");
ret = PQflush(conn->pgconn);
if (ret == 0) {
Dprintf("pq_execute: query got flushed immediately");
async_status = ASYNC_READ;
}
else if (ret == 1) {
async_status = ASYNC_WRITE;
}
else {
pthread_mutex_unlock(&(conn->lock));
Py_BLOCK_THREADS;
PyErr_SetString(OperationalError,
PQerrorMessage(conn->pgconn));
return -1;
}
pthread_mutex_unlock(&(conn->lock));
Py_END_ALLOW_THREADS;
conn->async_status = async_status;
if (!(conn->async_cursor
= PyWeakref_NewRef((PyObject *)curs, NULL))) {
return -1;
}
return 0;
}
RAISES_NEG int
pq_execute(cursorObject *curs, const char *query, int async, int no_result, int no_begin)
{
if (PQstatus(curs->conn->pgconn) != CONNECTION_OK) {
Dprintf("pq_execute: connection NOT OK");
PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn));
return -1;
}
Dprintf("pq_execute: pg connection at %p OK", curs->conn->pgconn);
if (!async) {
return _pq_execute_sync(curs, query, no_result, no_begin);
} else {
return _pq_execute_async(curs, query, no_result);
}
}
*
* Return 1 if command succeeded, else 0.
*
* The function should be called helding the connection lock and the GIL.
*/
int
pq_send_query(connectionObject *conn, const char *query)
{
int rv;
Dprintf("pq_send_query: sending ASYNC query:");
Dprintf(" %-.200s", query);
CLEARPGRES(conn->pgres);
if (0 == (rv = PQsendQuery(conn->pgconn, query))) {
Dprintf("pq_send_query: error: %s", PQerrorMessage(conn->pgconn));
}
return rv;
}
this function locks the connection object
this function call Py_*_ALLOW_THREADS macros
return value:
-1 - some error occurred while calling libpq
0 - no result from the backend but no libpq errors
1 - result from backend (possibly data is ready)
*/
static PyObject *
_get_cast(cursorObject *curs, PGresult *pgres, int i)
{
- the per-cursor dictionary, if available (can be NULL or None)
- the per-connection dictionary (always exists but can be null)
- the global dictionary (at module level)
if we get no defined cast use the default one */
PyObject *type = NULL;
PyObject *cast = NULL;
PyObject *rv = NULL;
Oid ftype = PQftype(pgres, i);
if (!(type = PyLong_FromOid(ftype))) { goto exit; }
Dprintf("_pq_fetch_tuples: looking for cast %u:", ftype);
if (!(cast = curs_get_cast(curs, type))) { goto exit; }
is binary use the default cast
FIXME: what the hell am I trying to do here? This just can't work..
*/
if (cast == psyco_default_binary_cast && PQbinaryTuples(pgres)) {
Dprintf("_pq_fetch_tuples: Binary cursor and "
"binary field: %u using default cast", ftype);
cast = psyco_default_cast;
}
Dprintf("_pq_fetch_tuples: using cast at %p for type %u", cast, ftype);
Py_INCREF(cast);
rv = cast;
exit:
Py_XDECREF(type);
return rv;
}
static PyObject *
_make_column(connectionObject *conn, PGresult *pgres, int i)
{
Oid ftype = PQftype(pgres, i);
int fsize = PQfsize(pgres, i);
int fmod = PQfmod(pgres, i);
Oid ftable = PQftable(pgres, i);
int ftablecol = PQftablecol(pgres, i);
columnObject *column = NULL;
PyObject *rv = NULL;
if (!(column = (columnObject *)PyObject_CallObject(
(PyObject *)&columnType, NULL))) {
goto exit;
}
{
PyObject *tmp;
if (!(tmp = PyLong_FromOid(ftype))) {
goto exit;
}
column->type_code = tmp;
}
{
PyObject *tmp;
if (!(tmp = conn_text_from_chars(conn, PQfname(pgres, i)))) {
goto exit;
}
column->name = tmp;
}
Py_INCREF(Py_None);
column->display_size = Py_None;
if (fmod > 0) {
fmod = fmod - sizeof(int);
}
if (fsize == -1) {
if (ftype == NUMERICOID) {
PyObject *tmp;
if (!(tmp = PyInt_FromLong((fmod >> 16)))) { goto exit; }
column->internal_size = tmp;
}
else {
PyObject *tmp;
if (!(tmp = PyInt_FromLong(fmod))) { goto exit; }
column->internal_size = tmp;
}
}
else {
PyObject *tmp;
if (!(tmp = PyInt_FromLong(fsize))) { goto exit; }
column->internal_size = tmp;
}
if (ftype == NUMERICOID) {
PyObject *tmp;
if (!(tmp = PyInt_FromLong((fmod >> 16) & 0xFFFF))) {
goto exit;
}
column->precision = tmp;
if (!(tmp = PyInt_FromLong(fmod & 0xFFFF))) {
goto exit;
}
column->scale = tmp;
}
if (ftable != InvalidOid) {
PyObject *tmp;
if (!(tmp = PyLong_FromOid(ftable))) { goto exit; }
column->table_oid = tmp;
}
if (ftablecol > 0) {
PyObject *tmp;
if (!(tmp = PyInt_FromLong((long)ftablecol))) { goto exit; }
column->table_column = tmp;
}
rv = (PyObject *)column;
column = NULL;
exit:
Py_XDECREF(column);
return rv;
}
RAISES_NEG static int
_pq_fetch_tuples(cursorObject *curs)
{
int i;
int pgnfields;
int rv = -1;
PyObject *description = NULL;
PyObject *casts = NULL;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(curs->conn->lock));
Py_END_ALLOW_THREADS;
pgnfields = PQnfields(curs->pgres);
curs->notuples = 0;
Py_CLEAR(curs->description);
Py_CLEAR(curs->casts);
if (!(description = PyTuple_New(pgnfields))) { goto exit; }
if (!(casts = PyTuple_New(pgnfields))) { goto exit; }
curs->columns = pgnfields;
for (i = 0; i < pgnfields; i++) {
PyObject *column = NULL;
PyObject *cast = NULL;
if (!(column = _make_column(curs->conn, curs->pgres, i))) {
goto exit;
}
PyTuple_SET_ITEM(description, i, (PyObject *)column);
if (!(cast = _get_cast(curs, curs->pgres, i))) {
goto exit;
}
PyTuple_SET_ITEM(casts, i, cast);
}
curs->description = description;
description = NULL;
curs->casts = casts;
casts = NULL;
rv = 0;
exit:
Py_XDECREF(description);
Py_XDECREF(casts);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_unlock(&(curs->conn->lock));
Py_END_ALLOW_THREADS;
return rv;
}
void
_read_rowcount(cursorObject *curs)
{
const char *rowcount;
rowcount = PQcmdTuples(curs->pgres);
Dprintf("_read_rowcount: PQcmdTuples = %s", rowcount);
if (!rowcount || !rowcount[0]) {
curs->rowcount = -1;
} else {
curs->rowcount = atol(rowcount);
}
}
static int
_pq_copy_in_v3(cursorObject *curs)
{
uses the new PQputCopyData() and can detect errors and set the correct
exception */
PyObject *o, *func = NULL, *size = NULL;
Py_ssize_t length = 0;
int res, error = 0;
if (!curs->copyfile) {
PyErr_SetString(ProgrammingError,
"can't execute COPY FROM: use the copy_from() method instead");
error = 1;
goto exit;
}
if (!(func = PyObject_GetAttrString(curs->copyfile, "read"))) {
Dprintf("_pq_copy_in_v3: can't get o.read");
error = 1;
goto exit;
}
if (!(size = PyInt_FromSsize_t(curs->copysize))) {
Dprintf("_pq_copy_in_v3: can't get int from copysize");
error = 1;
goto exit;
}
while (1) {
if (!(o = PyObject_CallFunctionObjArgs(func, size, NULL))) {
Dprintf("_pq_copy_in_v3: read() failed");
error = 1;
break;
}
if (PyUnicode_Check(o)) {
PyObject *tmp;
if (!(tmp = conn_encode(curs->conn, o))) {
Dprintf("_pq_copy_in_v3: encoding() failed");
error = 1;
break;
}
Py_DECREF(o);
o = tmp;
}
if (!Bytes_Check(o)) {
Dprintf("_pq_copy_in_v3: got %s instead of bytes",
Py_TYPE(o)->tp_name);
error = 1;
break;
}
if (0 == (length = Bytes_GET_SIZE(o))) {
break;
}
if (length > INT_MAX) {
Dprintf("_pq_copy_in_v3: bad length: " FORMAT_CODE_PY_SSIZE_T,
length);
error = 1;
break;
}
Py_BEGIN_ALLOW_THREADS;
res = PQputCopyData(curs->conn->pgconn, Bytes_AS_STRING(o),
(int) length);
Dprintf("_pq_copy_in_v3: sent " FORMAT_CODE_PY_SSIZE_T " bytes of data; res = %d",
length, res);
if (res == 0) {
here would be a nice idea */
}
else if (res == -1) {
Dprintf("_pq_copy_in_v3: PQerrorMessage = %s",
PQerrorMessage(curs->conn->pgconn));
error = 2;
}
Py_END_ALLOW_THREADS;
if (error == 2) break;
Py_DECREF(o);
}
Py_XDECREF(o);
Dprintf("_pq_copy_in_v3: error = %d", error);
backend: in both cases we'll get the error message from the PQresult */
if (error == 0)
res = PQputCopyEnd(curs->conn->pgconn, NULL);
else if (error == 2)
res = PQputCopyEnd(curs->conn->pgconn, "error in PQputCopyData() call");
else {
char buf[1024];
strcpy(buf, "error in .read() call");
if (PyErr_Occurred()) {
PyObject *t, *ex, *tb;
PyErr_Fetch(&t, &ex, &tb);
if (ex) {
PyObject *str;
str = PyObject_Str(ex);
str = psyco_ensure_bytes(str);
if (str) {
PyOS_snprintf(buf, sizeof(buf),
"error in .read() call: %s %s",
((PyTypeObject *)t)->tp_name, Bytes_AsString(str));
Py_DECREF(str);
}
}
Py_XDECREF(t);
Py_XDECREF(ex);
Py_XDECREF(tb);
PyErr_Clear();
}
res = PQputCopyEnd(curs->conn->pgconn, buf);
}
CLEARPGRES(curs->pgres);
Dprintf("_pq_copy_in_v3: copy ended; res = %d", res);
because that will lock the current thread forever */
if (res == -1) {
pq_raise(curs->conn, curs, NULL);
if the error message says "server closed the connection unexpectedly"
the status returned by PQstatus is CONNECTION_OK! */
curs->conn->closed = 2;
}
else {
for (;;) {
Py_BEGIN_ALLOW_THREADS;
curs_set_result(curs, PQgetResult(curs->conn->pgconn));
Py_END_ALLOW_THREADS;
if (NULL == curs->pgres)
break;
_read_rowcount(curs);
if (PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR)
pq_raise(curs->conn, curs, NULL);
CLEARPGRES(curs->pgres);
}
}
exit:
Py_XDECREF(func);
Py_XDECREF(size);
return (error == 0 ? 1 : -1);
}
static int
_pq_copy_out_v3(cursorObject *curs)
{
PyObject *tmp = NULL;
PyObject *func = NULL;
PyObject *obj = NULL;
int ret = -1;
int is_text;
char *buffer;
Py_ssize_t len;
if (!curs->copyfile) {
PyErr_SetString(ProgrammingError,
"can't execute COPY TO: use the copy_to() method instead");
goto exit;
}
if (!(func = PyObject_GetAttrString(curs->copyfile, "write"))) {
Dprintf("_pq_copy_out_v3: can't get o.write");
goto exit;
}
if (-1 == (is_text = psyco_is_text_file(curs->copyfile))) {
goto exit;
}
while (1) {
Py_BEGIN_ALLOW_THREADS;
len = PQgetCopyData(curs->conn->pgconn, &buffer, 0);
Py_END_ALLOW_THREADS;
if (len > 0 && buffer) {
if (is_text) {
obj = conn_decode(curs->conn, buffer, len);
} else {
obj = Bytes_FromStringAndSize(buffer, len);
}
PQfreemem(buffer);
if (!obj) { goto exit; }
tmp = PyObject_CallFunctionObjArgs(func, obj, NULL);
Py_DECREF(obj);
if (tmp == NULL) {
goto exit;
} else {
Py_DECREF(tmp);
}
}
because we are not doing an async call (if it happens blame
postgresql authors :/) */
else if (len <= 0) break;
}
if (len == -2) {
pq_raise(curs->conn, curs, NULL);
goto exit;
}
for (;;) {
Py_BEGIN_ALLOW_THREADS;
curs_set_result(curs, PQgetResult(curs->conn->pgconn));
Py_END_ALLOW_THREADS;
if (NULL == curs->pgres)
break;
_read_rowcount(curs);
if (PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR)
pq_raise(curs->conn, curs, NULL);
CLEARPGRES(curs->pgres);
}
ret = 1;
exit:
Py_XDECREF(func);
return ret;
}
blocking, in both sync and async connection modes. If no message
is ready in the CopyData buffer, tries to read from the server,
again without blocking. If that doesn't help, returns Py_None.
The caller is then supposed to block on the socket(s) and call this
function again.
Any keepalive messages from the server are silently consumed and
are never returned to the caller.
*/
int
pq_read_replication_message(replicationCursorObject *repl, replicationMessageObject **msg)
{
cursorObject *curs = &repl->cur;
connectionObject *conn = curs->conn;
PGconn *pgconn = conn->pgconn;
char *buffer = NULL;
int len, data_size, consumed, hdr, reply;
XLogRecPtr data_start, wal_end;
int64_t send_time;
PyObject *str = NULL, *result = NULL;
int ret = -1;
struct timeval curr_time, feedback_time;
Dprintf("pq_read_replication_message");
*msg = NULL;
consumed = 0;
gettimeofday(&curr_time, NULL);
timeradd(&repl->last_feedback, &repl->status_interval, &feedback_time);
if (timercmp(&curr_time, &feedback_time, >=) && pq_send_replication_feedback(repl, 0) < 0) {
goto exit;
}
retry:
len = PQgetCopyData(pgconn, &buffer, 1 );
if (len == 0) {
if (consumed) {
ret = 0;
goto exit;
}
available at the moment. Otherwise, with a really highly loaded
server we might be reading a number of messages for every single
one we process, thus overgrowing the internal buffer until the
client system runs out of memory. */
if (!PQconsumeInput(pgconn)) {
pq_raise(conn, curs, NULL);
goto exit;
}
anything into the internal buffer and there is no (supported) way
to ask libpq about this directly. The way we check is setting the
flag and re-trying PQgetCopyData(): if that returns 0 again,
there's no more data available in the buffer, so we return None. */
consumed = 1;
goto retry;
}
if (len == -2) {
pq_raise(conn, curs, NULL);
goto exit;
}
if (len == -1) {
curs_set_result(curs, PQgetResult(pgconn));
if (curs->pgres && PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR) {
pq_raise(conn, curs, NULL);
goto exit;
}
CLEARPGRES(curs->pgres);
ret = 0;
goto exit;
}
case of retry due to keepalive message. Any pending data on the socket
will trigger read condition in select() in the calling code anyway. */
consumed = 1;
gettimeofday(&repl->last_io, NULL);
Dprintf("pq_read_replication_message: msg=%c, len=%d", buffer[0], len);
if (buffer[0] == 'w') {
hdr = 1 + 8 + 8 + 8;
if (len < hdr + 1) {
psyco_set_error(OperationalError, curs, "data message header too small");
goto exit;
}
data_size = len - hdr;
data_start = fe_recvint64(buffer + 1);
wal_end = fe_recvint64(buffer + 1 + 8);
send_time = fe_recvint64(buffer + 1 + 8 + 8);
Dprintf("pq_read_replication_message: data_start="XLOGFMTSTR", wal_end="XLOGFMTSTR,
XLOGFMTARGS(data_start), XLOGFMTARGS(wal_end));
Dprintf("pq_read_replication_message: >>%.*s<<", data_size, buffer + hdr);
if (repl->decode) {
str = conn_decode(conn, buffer + hdr, data_size);
} else {
str = Bytes_FromStringAndSize(buffer + hdr, data_size);
}
if (!str) { goto exit; }
result = PyObject_CallFunctionObjArgs((PyObject *)&replicationMessageType,
curs, str, NULL);
Py_DECREF(str);
if (!result) { goto exit; }
*msg = (replicationMessageObject *)result;
(*msg)->data_size = data_size;
(*msg)->data_start = data_start;
(*msg)->wal_end = wal_end;
(*msg)->send_time = send_time;
repl->wal_end = wal_end;
repl->last_msg_data_start = data_start;
}
else if (buffer[0] == 'k') {
hdr = 1 + 8 + 8;
if (len < hdr + 1) {
psyco_set_error(OperationalError, curs, "keepalive message header too small");
goto exit;
}
wal_end = fe_recvint64(buffer + 1);
Dprintf("pq_read_replication_message: wal_end="XLOGFMTSTR, XLOGFMTARGS(wal_end));
repl->wal_end = wal_end;
* if we know that the client already processed (confirmed) the last XLogData message */
if (repl->explicitly_flushed_lsn >= repl->last_msg_data_start
&& wal_end > repl->explicitly_flushed_lsn
&& wal_end > repl->flush_lsn) {
repl->flush_lsn = wal_end;
}
reply = buffer[hdr];
if (reply && pq_send_replication_feedback(repl, 0) < 0) {
goto exit;
}
PQfreemem(buffer);
buffer = NULL;
goto retry;
}
else {
psyco_set_error(OperationalError, curs, "unrecognized replication message type");
goto exit;
}
ret = 0;
exit:
if (buffer) {
PQfreemem(buffer);
}
return ret;
}
int
pq_send_replication_feedback(replicationCursorObject *repl, int reply_requested)
{
cursorObject *curs = &repl->cur;
connectionObject *conn = curs->conn;
PGconn *pgconn = conn->pgconn;
char replybuf[1 + 8 + 8 + 8 + 8 + 1];
int len = 0;
Dprintf("pq_send_replication_feedback: write="XLOGFMTSTR", flush="XLOGFMTSTR", apply="XLOGFMTSTR,
XLOGFMTARGS(repl->write_lsn),
XLOGFMTARGS(repl->flush_lsn),
XLOGFMTARGS(repl->apply_lsn));
replybuf[len] = 'r'; len += 1;
fe_sendint64(repl->write_lsn, &replybuf[len]); len += 8;
fe_sendint64(repl->flush_lsn, &replybuf[len]); len += 8;
fe_sendint64(repl->apply_lsn, &replybuf[len]); len += 8;
fe_sendint64(feGetCurrentTimestamp(), &replybuf[len]); len += 8;
replybuf[len] = reply_requested ? 1 : 0; len += 1;
if (PQputCopyData(pgconn, replybuf, len) <= 0 || PQflush(pgconn) != 0) {
pq_raise(conn, curs, NULL);
return -1;
}
gettimeofday(&repl->last_feedback, NULL);
repl->last_io = repl->last_feedback;
return 0;
}
stop_replication is called or a fatal error occurs. The messages
are passed to the consumer object.
When no message is available, blocks on the connection socket, but
manages to send keepalive messages to the server as needed.
*/
int
pq_copy_both(replicationCursorObject *repl, PyObject *consume)
{
cursorObject *curs = &repl->cur;
connectionObject *conn = curs->conn;
PGconn *pgconn = conn->pgconn;
replicationMessageObject *msg = NULL;
PyObject *tmp = NULL;
int fd, sel, ret = -1;
fd_set fds;
struct timeval curr_time, feedback_time, timeout;
if (!PyCallable_Check(consume)) {
Dprintf("pq_copy_both: expected callable consume object");
goto exit;
}
CLEARPGRES(curs->pgres);
while (1) {
if (pq_read_replication_message(repl, &msg) < 0) {
goto exit;
}
else if (msg == NULL) {
fd = PQsocket(pgconn);
if (fd < 0) {
pq_raise(conn, curs, NULL);
goto exit;
}
FD_ZERO(&fds);
FD_SET(fd, &fds);
gettimeofday(&curr_time, NULL);
timeradd(&repl->last_feedback, &repl->status_interval, &feedback_time);
timersub(&feedback_time, &curr_time, &timeout);
if (timeout.tv_sec >= 0) {
Py_BEGIN_ALLOW_THREADS;
sel = select(fd + 1, &fds, NULL, NULL, &timeout);
Py_END_ALLOW_THREADS;
if (sel < 0) {
if (errno != EINTR) {
PyErr_SetFromErrno(PyExc_OSError);
goto exit;
}
if (PyErr_CheckSignals()) {
goto exit;
}
}
}
}
else {
tmp = PyObject_CallFunctionObjArgs(consume, msg, NULL);
Py_DECREF(msg);
if (tmp == NULL) {
Dprintf("pq_copy_both: consume returned NULL");
goto exit;
}
Py_DECREF(tmp);
}
}
ret = 1;
exit:
return ret;
}
int
pq_fetch(cursorObject *curs, int no_result)
{
int pgstatus, ex = -1;
curs_reset(curs);
if (curs->pgres == NULL) return 0;
pgstatus = PQresultStatus(curs->pgres);
Dprintf("pq_fetch: pgstatus = %s", PQresStatus(pgstatus));
Py_CLEAR(curs->pgstatus);
if (!(curs->pgstatus = conn_text_from_chars(
curs->conn, PQcmdStatus(curs->pgres)))) {
ex = -1;
return ex;
}
switch(pgstatus) {
case PGRES_COMMAND_OK:
Dprintf("pq_fetch: command returned OK (no tuples)");
_read_rowcount(curs);
curs->lastoid = PQoidValue(curs->pgres);
CLEARPGRES(curs->pgres);
ex = 1;
break;
case PGRES_COPY_OUT:
Dprintf("pq_fetch: data from a COPY TO (no tuples)");
curs->rowcount = -1;
ex = _pq_copy_out_v3(curs);
if (PyErr_Occurred()) ex = -1;
CLEARPGRES(curs->pgres);
break;
case PGRES_COPY_IN:
Dprintf("pq_fetch: data from a COPY FROM (no tuples)");
curs->rowcount = -1;
ex = _pq_copy_in_v3(curs);
if (PyErr_Occurred()) ex = -1;
CLEARPGRES(curs->pgres);
break;
case PGRES_COPY_BOTH:
Dprintf("pq_fetch: data from a streaming replication slot (no tuples)");
curs->rowcount = -1;
ex = 0;
Also don't clear the result status: it's checked in
consume_stream. */
break;
case PGRES_TUPLES_OK:
if (!no_result) {
Dprintf("pq_fetch: got tuples");
curs->rowcount = PQntuples(curs->pgres);
if (0 == _pq_fetch_tuples(curs)) { ex = 0; }
}
else {
Dprintf("pq_fetch: got tuples, discarding them");
_read_rowcount(curs);
CLEARPGRES(curs->pgres);
ex = 0;
}
break;
case PGRES_EMPTY_QUERY:
PyErr_SetString(ProgrammingError,
"can't execute an empty query");
CLEARPGRES(curs->pgres);
ex = -1;
break;
case PGRES_BAD_RESPONSE:
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR:
Dprintf("pq_fetch: uh-oh, something FAILED: status = %d pgconn = %p",
pgstatus, curs->conn);
pq_raise(curs->conn, curs, NULL);
ex = -1;
break;
default:
Dprintf("pq_fetch: got unsupported result: status = %d pgconn = %p",
pgstatus, curs->conn);
PyErr_Format(NotSupportedError,
"got server response with unsupported status %s",
PQresStatus(curs->pgres == NULL ?
PQstatus(curs->conn->pgconn) : PQresultStatus(curs->pgres)));
CLEARPGRES(curs->pgres);
ex = -1;
break;
}
return ex;
}