mirror of
https://github.com/postgres/postgres.git
synced 2025-11-18 00:08:31 -05:00
>tcl-extension for postgreSQL.
>I'm currently using 7.0 and always getting a seg fault when I try to
>read from the database connection after issueing a "COPY table TO
>stdout;" (I'm using the connection handle, *not* the result handle).
>Maybe this is fixed in a later release.
>The README file in src/interfaces/libpgtcl tells me, that this should
>work, but unforunately it doesn't.
Yes, it seems broken. It is a bug in libpgtcl. Are you running Tcl >= 8.3.2?
That's when the Tcl team changed the data structure for channel
callbacks. The change itself was designed to be backward compatible, but I
suspect a related change made the code more sensitive to errors in the
structure (NULL pointers where functions are required). Either that, or
nobody has tried to use libpgtcl with COPY in a long time.
First, I have to say I can't think of a good reason to use PostgreSQL's
COPY command from a Tcl application. I think it should only be used with
psql for importing data from another source into PostgreSQL, or for
exporting PostgreSQL data into another database (but why would anyone do
that?) If it was me, I would stick with SELECT and INSERT and be "SQL
Compliant".
OK, editorial is over. Try applying the patch below to fix
src/interfaces/libpgtcl/pgtclId.c
and let us know if it works. I did little testing on it, but my test did
segfault before and ran fine (copy in and copy out) after the patch. This
is for PostgreSQL-7.1.2 - since you are running older 7.0, I don't know if
this will work, but I suspect it will.
PS It's the absence of PgWatchProc which kills it. I didn't upgrade it
to the "V2" channel type structure, so it should be compatible with older
Tcl's. But aside from gets and puts, I doubt any other file operations
would work on the handle during a copy.
ljb
749 lines
20 KiB
C
749 lines
20 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pgtclId.c
|
|
*
|
|
* Contains Tcl "channel" interface routines, plus useful routines
|
|
* to convert between strings and pointers. These are needed because
|
|
* everything in Tcl is a string, but in C, pointers to data structures
|
|
* are needed.
|
|
*
|
|
* ASSUMPTION: sizeof(long) >= sizeof(void*)
|
|
*
|
|
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclId.c,v 1.27 2001/09/07 21:55:00 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres_fe.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#include "pgtclCmds.h"
|
|
#include "pgtclId.h"
|
|
|
|
|
|
static int
|
|
PgEndCopy(Pg_ConnectionId * connid, int *errorCodePtr)
|
|
{
|
|
connid->res_copyStatus = RES_COPY_NONE;
|
|
if (PQendcopy(connid->conn))
|
|
{
|
|
PQclear(connid->results[connid->res_copy]);
|
|
connid->results[connid->res_copy] =
|
|
PQmakeEmptyPGresult(connid->conn, PGRES_BAD_RESPONSE);
|
|
connid->res_copy = -1;
|
|
*errorCodePtr = EIO;
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
PQclear(connid->results[connid->res_copy]);
|
|
connid->results[connid->res_copy] =
|
|
PQmakeEmptyPGresult(connid->conn, PGRES_COMMAND_OK);
|
|
connid->res_copy = -1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when reading data (via gets) for a copy <rel> to stdout.
|
|
*/
|
|
int
|
|
PgInputProc(DRIVER_INPUT_PROTO)
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
int avail;
|
|
|
|
connid = (Pg_ConnectionId *) cData;
|
|
conn = connid->conn;
|
|
|
|
if (connid->res_copy < 0 ||
|
|
PQresultStatus(connid->results[connid->res_copy]) != PGRES_COPY_OUT)
|
|
{
|
|
*errorCodePtr = EBUSY;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Read any newly arrived data into libpq's buffer, thereby clearing
|
|
* the socket's read-ready condition.
|
|
*/
|
|
if (!PQconsumeInput(conn))
|
|
{
|
|
*errorCodePtr = EIO;
|
|
return -1;
|
|
}
|
|
|
|
/* Move data from libpq's buffer to Tcl's. */
|
|
|
|
avail = PQgetlineAsync(conn, buf, bufSize);
|
|
|
|
if (avail < 0)
|
|
{
|
|
/* Endmarker detected, change state and return 0 */
|
|
return PgEndCopy(connid, errorCodePtr);
|
|
}
|
|
|
|
return avail;
|
|
}
|
|
|
|
/*
|
|
* Called when writing data (via puts) for a copy <rel> from stdin
|
|
*/
|
|
int
|
|
PgOutputProc(DRIVER_OUTPUT_PROTO)
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
|
|
connid = (Pg_ConnectionId *) cData;
|
|
conn = connid->conn;
|
|
|
|
if (connid->res_copy < 0 ||
|
|
PQresultStatus(connid->results[connid->res_copy]) != PGRES_COPY_IN)
|
|
{
|
|
*errorCodePtr = EBUSY;
|
|
return -1;
|
|
}
|
|
|
|
if (PQputnbytes(conn, buf, bufSize))
|
|
{
|
|
*errorCodePtr = EIO;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* This assumes Tcl script will write the terminator line in a single
|
|
* operation; maybe not such a good assumption?
|
|
*/
|
|
if (bufSize >= 3 && strncmp(&buf[bufSize - 3], "\\.\n", 3) == 0)
|
|
{
|
|
if (PgEndCopy(connid, errorCodePtr) == -1)
|
|
return -1;
|
|
}
|
|
return bufSize;
|
|
}
|
|
|
|
#if HAVE_TCL_GETFILEPROC
|
|
|
|
Tcl_File
|
|
PgGetFileProc(ClientData cData, int direction)
|
|
{
|
|
return (Tcl_File) NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* The WatchProc and GetHandleProc are no-ops but must be present.
|
|
*/
|
|
static void
|
|
PgWatchProc(ClientData instanceData, int mask)
|
|
{
|
|
}
|
|
static int
|
|
PgGetHandleProc(ClientData instanceData, int direction,
|
|
ClientData *handlePtr)
|
|
{
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
Tcl_ChannelType Pg_ConnType = {
|
|
"pgsql", /* channel type */
|
|
NULL, /* blockmodeproc */
|
|
PgDelConnectionId, /* closeproc */
|
|
PgInputProc, /* inputproc */
|
|
PgOutputProc, /* outputproc */
|
|
NULL, /* SeekProc, Not used */
|
|
NULL, /* SetOptionProc, Not used */
|
|
NULL, /* GetOptionProc, Not used */
|
|
PgWatchProc, /* WatchProc, must be defined */
|
|
PgGetHandleProc, /* GetHandleProc, must be defined */
|
|
NULL /* Close2Proc, Not used */
|
|
};
|
|
|
|
/*
|
|
* Create and register a new channel for the connection
|
|
*/
|
|
void
|
|
PgSetConnectionId(Tcl_Interp *interp, PGconn *conn)
|
|
{
|
|
Tcl_Channel conn_chan;
|
|
Pg_ConnectionId *connid;
|
|
int i;
|
|
|
|
connid = (Pg_ConnectionId *) ckalloc(sizeof(Pg_ConnectionId));
|
|
connid->conn = conn;
|
|
connid->res_count = 0;
|
|
connid->res_last = -1;
|
|
connid->res_max = RES_START;
|
|
connid->res_hardmax = RES_HARD_MAX;
|
|
connid->res_copy = -1;
|
|
connid->res_copyStatus = RES_COPY_NONE;
|
|
connid->results = (PGresult **) ckalloc(sizeof(PGresult *) * RES_START);
|
|
for (i = 0; i < RES_START; i++)
|
|
connid->results[i] = NULL;
|
|
connid->notify_list = NULL;
|
|
connid->notifier_running = 0;
|
|
|
|
sprintf(connid->id, "pgsql%d", PQsocket(conn));
|
|
|
|
#if TCL_MAJOR_VERSION >= 8
|
|
connid->notifier_channel = Tcl_MakeTcpClientChannel((ClientData) PQsocket(conn));
|
|
Tcl_RegisterChannel(interp, connid->notifier_channel);
|
|
#else
|
|
connid->notifier_socket = -1;
|
|
#endif
|
|
|
|
#if TCL_MAJOR_VERSION == 7 && TCL_MINOR_VERSION == 5
|
|
/* Original signature (only seen in Tcl 7.5) */
|
|
conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, NULL, NULL, (ClientData) connid);
|
|
#else
|
|
/* Tcl 7.6 and later use this */
|
|
conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, (ClientData) connid,
|
|
TCL_READABLE | TCL_WRITABLE);
|
|
#endif
|
|
|
|
Tcl_SetChannelOption(interp, conn_chan, "-buffering", "line");
|
|
Tcl_SetResult(interp, connid->id, TCL_VOLATILE);
|
|
Tcl_RegisterChannel(interp, conn_chan);
|
|
}
|
|
|
|
|
|
/*
|
|
* Get back the connection from the Id
|
|
*/
|
|
PGconn *
|
|
PgGetConnectionId(Tcl_Interp *interp, char *id, Pg_ConnectionId ** connid_p)
|
|
{
|
|
Tcl_Channel conn_chan;
|
|
Pg_ConnectionId *connid;
|
|
|
|
conn_chan = Tcl_GetChannel(interp, id, 0);
|
|
if (conn_chan == NULL || Tcl_GetChannelType(conn_chan) != &Pg_ConnType)
|
|
{
|
|
Tcl_ResetResult(interp);
|
|
Tcl_AppendResult(interp, id, " is not a valid postgresql connection", 0);
|
|
return (PGconn *) NULL;
|
|
}
|
|
|
|
connid = (Pg_ConnectionId *) Tcl_GetChannelInstanceData(conn_chan);
|
|
if (connid_p)
|
|
*connid_p = connid;
|
|
return connid->conn;
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove a connection Id from the hash table and
|
|
* close all portals the user forgot.
|
|
*/
|
|
int
|
|
PgDelConnectionId(DRIVER_DEL_PROTO)
|
|
{
|
|
Tcl_HashEntry *entry;
|
|
Tcl_HashSearch hsearch;
|
|
Pg_ConnectionId *connid;
|
|
Pg_TclNotifies *notifies;
|
|
int i;
|
|
|
|
connid = (Pg_ConnectionId *) cData;
|
|
|
|
for (i = 0; i < connid->res_max; i++)
|
|
{
|
|
if (connid->results[i])
|
|
PQclear(connid->results[i]);
|
|
}
|
|
ckfree((void *) connid->results);
|
|
|
|
/* Release associated notify info */
|
|
while ((notifies = connid->notify_list) != NULL)
|
|
{
|
|
connid->notify_list = notifies->next;
|
|
for (entry = Tcl_FirstHashEntry(¬ifies->notify_hash, &hsearch);
|
|
entry != NULL;
|
|
entry = Tcl_NextHashEntry(&hsearch))
|
|
ckfree((char *) Tcl_GetHashValue(entry));
|
|
Tcl_DeleteHashTable(¬ifies->notify_hash);
|
|
Tcl_DontCallWhenDeleted(notifies->interp, PgNotifyInterpDelete,
|
|
(ClientData) notifies);
|
|
ckfree((void *) notifies);
|
|
}
|
|
|
|
/*
|
|
* Turn off the Tcl event source for this connection, and delete any
|
|
* pending notify events.
|
|
*/
|
|
PgStopNotifyEventSource(connid);
|
|
|
|
/* Close the libpq connection too */
|
|
PQfinish(connid->conn);
|
|
connid->conn = NULL;
|
|
|
|
/*
|
|
* We must use Tcl_EventuallyFree because we don't want the connid
|
|
* struct to vanish instantly if Pg_Notify_EventProc is active for it.
|
|
* (Otherwise, closing the connection from inside a pg_listen callback
|
|
* could lead to coredump.) Pg_Notify_EventProc can detect that the
|
|
* connection has been deleted from under it by checking connid->conn.
|
|
*/
|
|
Tcl_EventuallyFree((ClientData) connid, TCL_DYNAMIC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Find a slot for a new result id. If the table is full, expand it by
|
|
* a factor of 2. However, do not expand past the hard max, as the client
|
|
* is probably just not clearing result handles like they should.
|
|
*/
|
|
int
|
|
PgSetResultId(Tcl_Interp *interp, char *connid_c, PGresult *res)
|
|
{
|
|
Tcl_Channel conn_chan;
|
|
Pg_ConnectionId *connid;
|
|
int resid,
|
|
i;
|
|
char buf[32];
|
|
|
|
|
|
conn_chan = Tcl_GetChannel(interp, connid_c, 0);
|
|
if (conn_chan == NULL)
|
|
return TCL_ERROR;
|
|
connid = (Pg_ConnectionId *) Tcl_GetChannelInstanceData(conn_chan);
|
|
|
|
for (resid = connid->res_last + 1; resid != connid->res_last; resid++)
|
|
{
|
|
if (resid == connid->res_max)
|
|
resid = 0;
|
|
if (!connid->results[resid])
|
|
{
|
|
connid->res_last = resid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (connid->results[resid])
|
|
{
|
|
if (connid->res_max == connid->res_hardmax)
|
|
{
|
|
Tcl_SetResult(interp, "hard limit on result handles reached",
|
|
TCL_STATIC);
|
|
return TCL_ERROR;
|
|
}
|
|
connid->res_last = connid->res_max;
|
|
resid = connid->res_max;
|
|
connid->res_max *= 2;
|
|
if (connid->res_max > connid->res_hardmax)
|
|
connid->res_max = connid->res_hardmax;
|
|
connid->results = (PGresult **) ckrealloc((void *) connid->results,
|
|
sizeof(PGresult *) * connid->res_max);
|
|
for (i = connid->res_last; i < connid->res_max; i++)
|
|
connid->results[i] = NULL;
|
|
}
|
|
|
|
connid->results[resid] = res;
|
|
sprintf(buf, "%s.%d", connid_c, resid);
|
|
Tcl_SetResult(interp, buf, TCL_VOLATILE);
|
|
return resid;
|
|
}
|
|
|
|
static int
|
|
getresid(Tcl_Interp *interp, char *id, Pg_ConnectionId ** connid_p)
|
|
{
|
|
Tcl_Channel conn_chan;
|
|
char *mark;
|
|
int resid;
|
|
Pg_ConnectionId *connid;
|
|
|
|
if (!(mark = strchr(id, '.')))
|
|
return -1;
|
|
*mark = '\0';
|
|
conn_chan = Tcl_GetChannel(interp, id, 0);
|
|
*mark = '.';
|
|
if (conn_chan == NULL || Tcl_GetChannelType(conn_chan) != &Pg_ConnType)
|
|
{
|
|
Tcl_SetResult(interp, "Invalid connection handle", TCL_STATIC);
|
|
return -1;
|
|
}
|
|
|
|
if (Tcl_GetInt(interp, mark + 1, &resid) == TCL_ERROR)
|
|
{
|
|
Tcl_SetResult(interp, "Poorly formated result handle", TCL_STATIC);
|
|
return -1;
|
|
}
|
|
|
|
connid = (Pg_ConnectionId *) Tcl_GetChannelInstanceData(conn_chan);
|
|
|
|
if (resid < 0 || resid >= connid->res_max || connid->results[resid] == NULL)
|
|
{
|
|
Tcl_SetResult(interp, "Invalid result handle", TCL_STATIC);
|
|
return -1;
|
|
}
|
|
|
|
*connid_p = connid;
|
|
|
|
return resid;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get back the result pointer from the Id
|
|
*/
|
|
PGresult *
|
|
PgGetResultId(Tcl_Interp *interp, char *id)
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
int resid;
|
|
|
|
if (!id)
|
|
return NULL;
|
|
resid = getresid(interp, id, &connid);
|
|
if (resid == -1)
|
|
return NULL;
|
|
return connid->results[resid];
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove a result Id from the hash tables
|
|
*/
|
|
void
|
|
PgDelResultId(Tcl_Interp *interp, char *id)
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
int resid;
|
|
|
|
resid = getresid(interp, id, &connid);
|
|
if (resid == -1)
|
|
return;
|
|
connid->results[resid] = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the connection Id from the result Id
|
|
*/
|
|
int
|
|
PgGetConnByResultId(Tcl_Interp *interp, char *resid_c)
|
|
{
|
|
char *mark;
|
|
Tcl_Channel conn_chan;
|
|
|
|
if (!(mark = strchr(resid_c, '.')))
|
|
goto error_out;
|
|
*mark = '\0';
|
|
conn_chan = Tcl_GetChannel(interp, resid_c, 0);
|
|
*mark = '.';
|
|
if (conn_chan && Tcl_GetChannelType(conn_chan) == &Pg_ConnType)
|
|
{
|
|
Tcl_SetResult(interp, Tcl_GetChannelName(conn_chan), TCL_VOLATILE);
|
|
return TCL_OK;
|
|
}
|
|
|
|
error_out:
|
|
Tcl_ResetResult(interp);
|
|
Tcl_AppendResult(interp, resid_c, " is not a valid connection\n", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------
|
|
Notify event source
|
|
|
|
These functions allow asynchronous notify messages arriving from
|
|
the SQL server to be dispatched as Tcl events. See the Tcl
|
|
Notifier(3) man page for more info.
|
|
|
|
The main trick in this code is that we have to cope with status changes
|
|
between the queueing and the execution of a Tcl event. For example,
|
|
if the user changes or cancels the pg_listen callback command, we should
|
|
use the new setting; we do that by not resolving the notify relation
|
|
name until the last possible moment.
|
|
We also have to handle closure of the channel or deletion of the interpreter
|
|
to be used for the callback (note that with multiple interpreters,
|
|
the channel can outlive the interpreter it was created by!)
|
|
Upon closure of the channel, we immediately delete the file event handler
|
|
for it, which has the effect of disabling any file-ready events that might
|
|
be hanging about in the Tcl event queue. But for interpreter deletion,
|
|
we just set any matching interp pointers in the Pg_TclNotifies list to NULL.
|
|
The list item stays around until the connection is deleted. (This avoids
|
|
trouble with walking through a list whose members may get deleted under us.)
|
|
|
|
Another headache is that Ousterhout keeps changing the Tcl I/O interfaces.
|
|
libpgtcl currently claims to work with Tcl 7.5, 7.6, and 8.0, and each of
|
|
'em is different. Worse, the Tcl_File type went away in 8.0, which means
|
|
there is no longer any platform-independent way of waiting for file ready.
|
|
So we now have to use a Unix-specific interface. Grumble.
|
|
|
|
In the current design, Pg_Notify_FileHandler is a file handler that
|
|
we establish by calling Tcl_CreateFileHandler(). It gets invoked from
|
|
the Tcl event loop whenever the underlying PGconn's socket is read-ready.
|
|
We suck up any available data (to clear the OS-level read-ready condition)
|
|
and then transfer any available PGnotify events into the Tcl event queue.
|
|
Eventually these events will be dispatched to Pg_Notify_EventProc. When
|
|
we do an ordinary PQexec, we must also transfer PGnotify events into Tcl's
|
|
event queue, since libpq might have read them when we weren't looking.
|
|
------------------------------------------*/
|
|
|
|
typedef struct
|
|
{
|
|
Tcl_Event header; /* Standard Tcl event info */
|
|
PGnotify info; /* Notify name from SQL server */
|
|
Pg_ConnectionId *connid; /* Connection for server */
|
|
} NotifyEvent;
|
|
|
|
/* Dispatch a NotifyEvent that has reached the front of the event queue */
|
|
|
|
static int
|
|
Pg_Notify_EventProc(Tcl_Event *evPtr, int flags)
|
|
{
|
|
NotifyEvent *event = (NotifyEvent *) evPtr;
|
|
Pg_TclNotifies *notifies;
|
|
Tcl_HashEntry *entry;
|
|
char *callback;
|
|
char *svcallback;
|
|
|
|
/* We classify SQL notifies as Tcl file events. */
|
|
if (!(flags & TCL_FILE_EVENTS))
|
|
return 0;
|
|
|
|
/* If connection's been closed, just forget the whole thing. */
|
|
if (event->connid == NULL)
|
|
return 1;
|
|
|
|
/*
|
|
* Preserve/Release to ensure the connection struct doesn't disappear
|
|
* underneath us.
|
|
*/
|
|
Tcl_Preserve((ClientData) event->connid);
|
|
|
|
/*
|
|
* Loop for each interpreter that has ever registered on the
|
|
* connection. Each one can get a callback.
|
|
*/
|
|
|
|
for (notifies = event->connid->notify_list;
|
|
notifies != NULL;
|
|
notifies = notifies->next)
|
|
{
|
|
Tcl_Interp *interp = notifies->interp;
|
|
|
|
if (interp == NULL)
|
|
continue; /* ignore deleted interpreter */
|
|
|
|
/*
|
|
* Find the callback to be executed for this interpreter, if any.
|
|
*/
|
|
entry = Tcl_FindHashEntry(¬ifies->notify_hash,
|
|
event->info.relname);
|
|
if (entry == NULL)
|
|
continue; /* no pg_listen in this interpreter */
|
|
callback = (char *) Tcl_GetHashValue(entry);
|
|
if (callback == NULL)
|
|
continue; /* safety check -- shouldn't happen */
|
|
|
|
/*
|
|
* We have to copy the callback string in case the user executes a
|
|
* new pg_listen during the callback.
|
|
*/
|
|
svcallback = (char *) ckalloc((unsigned) (strlen(callback) + 1));
|
|
strcpy(svcallback, callback);
|
|
|
|
/*
|
|
* Execute the callback.
|
|
*/
|
|
Tcl_Preserve((ClientData) interp);
|
|
if (Tcl_GlobalEval(interp, svcallback) != TCL_OK)
|
|
{
|
|
Tcl_AddErrorInfo(interp, "\n (\"pg_listen\" script)");
|
|
Tcl_BackgroundError(interp);
|
|
}
|
|
Tcl_Release((ClientData) interp);
|
|
ckfree(svcallback);
|
|
|
|
/*
|
|
* Check for the possibility that the callback closed the
|
|
* connection.
|
|
*/
|
|
if (event->connid->conn == NULL)
|
|
break;
|
|
}
|
|
|
|
Tcl_Release((ClientData) event->connid);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Transfer any notify events available from libpq into the Tcl event queue.
|
|
* Note that this must be called after each PQexec (to capture notifies
|
|
* that arrive during command execution) as well as in Pg_Notify_FileHandler
|
|
* (to capture notifies that arrive when we're idle).
|
|
*/
|
|
|
|
void
|
|
PgNotifyTransferEvents(Pg_ConnectionId * connid)
|
|
{
|
|
PGnotify *notify;
|
|
|
|
while ((notify = PQnotifies(connid->conn)) != NULL)
|
|
{
|
|
NotifyEvent *event = (NotifyEvent *) ckalloc(sizeof(NotifyEvent));
|
|
|
|
event->header.proc = Pg_Notify_EventProc;
|
|
event->info = *notify;
|
|
event->connid = connid;
|
|
Tcl_QueueEvent((Tcl_Event *) event, TCL_QUEUE_TAIL);
|
|
PQfreeNotify(notify);
|
|
}
|
|
|
|
/*
|
|
* This is also a good place to check for unexpected closure of the
|
|
* connection (ie, backend crash), in which case we must shut down the
|
|
* notify event source to keep Tcl from trying to select() on the now-
|
|
* closed socket descriptor.
|
|
*/
|
|
if (PQsocket(connid->conn) < 0)
|
|
PgStopNotifyEventSource(connid);
|
|
}
|
|
|
|
/*
|
|
* Cleanup code for coping when an interpreter or a channel is deleted.
|
|
*
|
|
* PgNotifyInterpDelete is registered as an interpreter deletion callback
|
|
* for each extant Pg_TclNotifies structure.
|
|
* NotifyEventDeleteProc is used by PgStopNotifyEventSource to cancel
|
|
* pending Tcl NotifyEvents that reference a dying connection.
|
|
*/
|
|
|
|
void
|
|
PgNotifyInterpDelete(ClientData clientData, Tcl_Interp *interp)
|
|
{
|
|
/* Mark the interpreter dead, but don't do anything else yet */
|
|
Pg_TclNotifies *notifies = (Pg_TclNotifies *) clientData;
|
|
|
|
notifies->interp = NULL;
|
|
}
|
|
|
|
/*
|
|
* Comparison routine for detecting events to be removed by Tcl_DeleteEvents.
|
|
* NB: In (at least) Tcl versions 7.6 through 8.0.3, there is a serious
|
|
* bug in Tcl_DeleteEvents: if there are multiple events on the queue and
|
|
* you tell it to delete the last one, the event list pointers get corrupted,
|
|
* with the result that events queued immediately thereafter get lost.
|
|
* Therefore we daren't tell Tcl_DeleteEvents to actually delete anything!
|
|
* We simply use it as a way of scanning the event queue. Events matching
|
|
* the about-to-be-deleted connid are marked dead by setting their connid
|
|
* fields to NULL. Then Pg_Notify_EventProc will do nothing when those
|
|
* events are executed.
|
|
*/
|
|
static int
|
|
NotifyEventDeleteProc(Tcl_Event *evPtr, ClientData clientData)
|
|
{
|
|
Pg_ConnectionId *connid = (Pg_ConnectionId *) clientData;
|
|
|
|
if (evPtr->proc == Pg_Notify_EventProc)
|
|
{
|
|
NotifyEvent *event = (NotifyEvent *) evPtr;
|
|
|
|
if (event->connid == connid)
|
|
event->connid = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* File handler callback: called when Tcl has detected read-ready on socket.
|
|
* The clientData is a pointer to the associated connection.
|
|
* We can ignore the condition mask since we only ever ask about read-ready.
|
|
*/
|
|
|
|
static void
|
|
Pg_Notify_FileHandler(ClientData clientData, int mask)
|
|
{
|
|
Pg_ConnectionId *connid = (Pg_ConnectionId *) clientData;
|
|
|
|
/*
|
|
* Consume any data available from the SQL server (this just buffers
|
|
* it internally to libpq; but it will clear the read-ready
|
|
* condition).
|
|
*/
|
|
PQconsumeInput(connid->conn);
|
|
|
|
/* Transfer notify events from libpq to Tcl event queue. */
|
|
PgNotifyTransferEvents(connid);
|
|
}
|
|
|
|
|
|
/*
|
|
* Start and stop the notify event source for a connection.
|
|
*
|
|
* We do not bother to run the notifier unless at least one pg_listen
|
|
* has been executed on the connection. Currently, once started the
|
|
* notifier is run until the connection is closed.
|
|
*
|
|
* FIXME: if PQreset is executed on the underlying PGconn, the active
|
|
* socket number could change. How and when should we test for this
|
|
* and update the Tcl file handler linkage? (For that matter, we'd
|
|
* also have to reissue LISTEN commands for active LISTENs, since the
|
|
* new backend won't know about 'em. I'm leaving this problem for
|
|
* another day.)
|
|
*/
|
|
|
|
void
|
|
PgStartNotifyEventSource(Pg_ConnectionId * connid)
|
|
{
|
|
/* Start the notify event source if it isn't already running */
|
|
if (!connid->notifier_running)
|
|
{
|
|
int pqsock = PQsocket(connid->conn);
|
|
|
|
if (pqsock >= 0)
|
|
{
|
|
#if TCL_MAJOR_VERSION >= 8
|
|
Tcl_CreateChannelHandler(connid->notifier_channel, TCL_READABLE,
|
|
Pg_Notify_FileHandler, (ClientData) connid);
|
|
#else
|
|
/* In Tcl 7.5 and 7.6, we need to gin up a Tcl_File. */
|
|
Tcl_File tclfile = Tcl_GetFile((ClientData) pqsock, TCL_UNIX_FD);
|
|
|
|
Tcl_CreateFileHandler(tclfile, TCL_READABLE,
|
|
Pg_Notify_FileHandler, (ClientData) connid);
|
|
connid->notifier_socket = pqsock;
|
|
#endif
|
|
connid->notifier_running = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PgStopNotifyEventSource(Pg_ConnectionId * connid)
|
|
{
|
|
/* Remove the event source */
|
|
if (connid->notifier_running)
|
|
{
|
|
#if TCL_MAJOR_VERSION >= 8
|
|
Tcl_DeleteChannelHandler(connid->notifier_channel,
|
|
Pg_Notify_FileHandler, (ClientData) connid);
|
|
#else
|
|
/* In Tcl 7.5 and 7.6, we need to gin up a Tcl_File. */
|
|
Tcl_File tclfile = Tcl_GetFile((ClientData) connid->notifier_socket,
|
|
TCL_UNIX_FD);
|
|
|
|
Tcl_DeleteFileHandler(tclfile);
|
|
#endif
|
|
connid->notifier_running = 0;
|
|
}
|
|
|
|
/* Kill any queued Tcl events that reference this channel */
|
|
Tcl_DeleteEvents(NotifyEventDeleteProc, (ClientData) connid);
|
|
}
|