diff options
author | Marc G. Fournier | 1998-03-15 08:03:00 +0000 |
---|---|---|
committer | Marc G. Fournier | 1998-03-15 08:03:00 +0000 |
commit | 6ac2528616697eb2d155ff693874e37c7273b797 (patch) | |
tree | 80f1258f558ba290b44341828f90a7a7a087abda /src/interfaces/libpgtcl/pgtclId.c | |
parent | 609026bb6b9cc05f9aa0e5a4ad7e06b5a352e969 (diff) |
From: Randy Kunkee <kunkee@pluto.ops.NeoSoft.com>
It is my hope that the following "patches" to libpgtcl get included
in the next release.
See the update to the README file to get a full description of the changes.
This version of libpgtcl is completely interpreter-safe, implements the
database connection handle as a channel (no events yet, but will make it
a lot easier to do fileevents on it in the future), and supports the SQL
"copy table to stdout" and "copy table from stdin" commands, with the
I/O being from and to the connection handle. The connection and result
handles are formatted in a way to make access to the tables more efficient.
Diffstat (limited to 'src/interfaces/libpgtcl/pgtclId.c')
-rw-r--r-- | src/interfaces/libpgtcl/pgtclId.c | 441 |
1 files changed, 300 insertions, 141 deletions
diff --git a/src/interfaces/libpgtcl/pgtclId.c b/src/interfaces/libpgtcl/pgtclId.c index 2473b6c3181..971b04039b4 100644 --- a/src/interfaces/libpgtcl/pgtclId.c +++ b/src/interfaces/libpgtcl/pgtclId.c @@ -1,48 +1,173 @@ /*------------------------------------------------------------------------- * * pgtclId.c-- - * useful routines to convert between strings and pointers - * Needed because everything in tcl is a string, but we want pointers - * to data structures + * useful routines to convert between strings and pointers + * Needed because everything in tcl is a string, but we want pointers + * to data structures * - * ASSUMPTION: sizeof(long) >= sizeof(void*) + * ASSUMPTION: sizeof(long) >= sizeof(void*) * * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclId.c,v 1.7 1998/02/26 04:44:53 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclId.c,v 1.8 1998/03/15 08:03:00 scrappy Exp $ * *------------------------------------------------------------------------- */ #include <stdlib.h> #include <string.h> +#include <errno.h> #include <tcl.h> #include "postgres.h" #include "pgtclCmds.h" #include "pgtclId.h" +int PgEndCopy(Pg_ConnectionId *connid, int *errorCodePtr) +{ + connid->res_copyStatus = RES_COPY_NONE; + if (PQendcopy(connid->conn)) { + connid->results[connid->res_copy]->resultStatus = PGRES_BAD_RESPONSE; + connid->res_copy = -1; + *errorCodePtr = EIO; + return -1; + } else { + connid->results[connid->res_copy]->resultStatus = 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 c; + int avail; + + connid = (Pg_ConnectionId *)cData; + conn = connid->conn; + + if (connid->res_copy < 0 || + connid->results[connid->res_copy]->resultStatus != PGRES_COPY_OUT) { + *errorCodePtr = EBUSY; + return -1; + } + + if (connid->res_copyStatus == RES_COPY_FIN) { + return PgEndCopy(connid, errorCodePtr); + } + + avail = bufSize; + while (avail > 0 && + (c = pqGetc(conn->Pfin, conn->Pfdebug)) != EOF) { + /* fprintf(stderr, "%d: got char %c\n", bufSize-avail, c); */ + *buf++ = c; + --avail; + if (c == '\n' && bufSize-avail > 3) { + if ((bufSize-avail == 3 || buf[-4] == '\n') && + buf[-3] == '\\' && buf[-2] == '.') { + avail += 3; + connid->res_copyStatus = RES_COPY_FIN; + break; + } + } + } + /* fprintf(stderr, "returning %d chars\n", bufSize - avail); */ + return bufSize - 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 || + connid->results[connid->res_copy]->resultStatus != PGRES_COPY_IN) { + *errorCodePtr = EBUSY; + return -1; + } + + /* + fprintf(stderr, "PgOutputProc called: bufSize=%d: atend:%d <", bufSize, + strncmp(buf, "\\.\n", 3)); + fwrite(buf, 1, bufSize, stderr); + fputs(">\n", stderr); + */ + fwrite(buf, 1, bufSize, conn->Pfout); + if (bufSize > 2 && strncmp(&buf[bufSize-3], "\\.\n", 3) == 0) { + /* fprintf(stderr,"checking closure\n"); */ + fflush(conn->Pfout); + if (PgEndCopy(connid, errorCodePtr) == -1) + return -1; + } + return bufSize; +} + +#if (TCL_MAJOR_VERSION == 7 && TCL_MINOR_VERSION == 6) +Tcl_File +PgGetFileProc(ClientData cData, int direction) +{ + return (Tcl_File)NULL; +} +#endif + +Tcl_ChannelType Pg_ConnType = { + "pgsql", /* channel type */ + NULL, /* blockmodeproc */ + PgDelConnectionId, /* closeproc */ + PgInputProc, /* inputproc */ + PgOutputProc, /* outputproc */ + /* Note the additional stuff can be left NULL, + or is initialized during a PgSetConnectionId */ +}; + /* - * Create the Id for a new connection and hash it + * Create and register a new channel for the connection */ void -PgSetConnectionId(Pg_clientData * cd, char *id, PGconn *conn) +PgSetConnectionId(Tcl_Interp *interp, PGconn *conn) { - Tcl_HashEntry *hent; - Pg_ConnectionId *connid; - int hnew; - - connid = (Pg_ConnectionId *) ckalloc(sizeof(Pg_ConnectionId)); - connid->conn = conn; - Tcl_InitHashTable(&(connid->res_hash), TCL_STRING_KEYS); - sprintf(connid->id, "pgc%ld", cd->dbh_count++); - strcpy(id, connid->id); - - hent = Tcl_CreateHashEntry(&(cd->dbh_hash), connid->id, &hnew); - Tcl_SetHashValue(hent, (ClientData) connid); + 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; + Tcl_InitHashTable(&connid->notify_hash, TCL_STRING_KEYS); + + sprintf(connid->id, "pgsql%d", fileno(conn->Pfout)); + +#if TCL_MAJOR_VERSION == 7 && TCL_MINOR_VERSION == 5 + conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, conn->Pfin, conn->Pfout, (ClientData)connid); +#else + 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); } @@ -50,19 +175,22 @@ PgSetConnectionId(Pg_clientData * cd, char *id, PGconn *conn) * Get back the connection from the Id */ PGconn * -PgGetConnectionId(Pg_clientData * cd, char *id) +PgGetConnectionId(Tcl_Interp *interp, char *id, Pg_ConnectionId **connid_p) { - Tcl_HashEntry *hent; - Pg_ConnectionId *connid; - - hent = Tcl_FindHashEntry(&(cd->dbh_hash), id); - if (hent == NULL) - { - return (PGconn *) NULL; - } - - connid = (Pg_ConnectionId *) Tcl_GetHashValue(hent); - return connid->conn; + 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\n", 0); + return (PGconn *)NULL; + } + + connid = (Pg_ConnectionId *)Tcl_GetChannelInstanceData(conn_chan); + if (connid_p) + *connid_p = connid; + return connid->conn; } @@ -70,98 +198,139 @@ PgGetConnectionId(Pg_clientData * cd, char *id) * Remove a connection Id from the hash table and * close all portals the user forgot. */ -void -PgDelConnectionId(Pg_clientData * cd, char *id) +int PgDelConnectionId(DRIVER_DEL_PROTO) { - Tcl_HashEntry *hent; - Tcl_HashEntry *hent2; - Tcl_HashEntry *hent3; - Tcl_HashSearch hsearch; - Pg_ConnectionId *connid; - Pg_ResultId *resid; - - hent = Tcl_FindHashEntry(&(cd->dbh_hash), id); - if (hent == NULL) - { - return; - } - - connid = (Pg_ConnectionId *) Tcl_GetHashValue(hent); - - hent2 = Tcl_FirstHashEntry(&(connid->res_hash), &hsearch); - while (hent2 != NULL) - { - resid = (Pg_ResultId *) Tcl_GetHashValue(hent2); - PQclear(resid->result); - hent3 = Tcl_FindHashEntry(&(cd->res_hash), resid->id); - if (hent3 != NULL) - { - Tcl_DeleteHashEntry(hent3); - } - ckfree(resid); - hent2 = Tcl_NextHashEntry(&hsearch); - } - Tcl_DeleteHashTable(&(connid->res_hash)); - Tcl_DeleteHashEntry(hent); - ckfree(connid); + Tcl_HashEntry *entry; + char *hval; + Tcl_HashSearch hsearch; + Pg_ConnectionId *connid; + 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); + + for (entry = Tcl_FirstHashEntry(&(connid->notify_hash), &hsearch); + entry != NULL; + entry = Tcl_NextHashEntry(&hsearch)) + { + hval = (char*)Tcl_GetHashValue(entry); + ckfree(hval); + } + + Tcl_DeleteHashTable(&connid->notify_hash); + PQfinish(connid->conn); + ckfree((void*)connid); + return 0; } /* - * Create a new result Id and hash it + * 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. */ -void -PgSetResultId(Pg_clientData * cd, char *id, char *connid_c, PGresult *res) +int +PgSetResultId(Tcl_Interp *interp, char *connid_c, PGresult *res) { - Tcl_HashEntry *hent; - Pg_ConnectionId *connid; - Pg_ResultId *resid; - int hnew; + Tcl_Channel conn_chan; + Pg_ConnectionId *connid; + int resid, i; + char buf[32]; - hent = Tcl_FindHashEntry(&(cd->dbh_hash), connid_c); - if (hent == NULL) - { - connid = NULL; - } - else - { - connid = (Pg_ConnectionId *) Tcl_GetHashValue(hent); - } - - resid = (Pg_ResultId *) ckalloc(sizeof(Pg_ResultId)); - resid->result = res; - resid->connection = connid; - sprintf(resid->id, "pgr%ld", cd->res_count++); - strcpy(id, resid->id); - hent = Tcl_CreateHashEntry(&(cd->res_hash), resid->id, &hnew); - Tcl_SetHashValue(hent, (ClientData) resid); + conn_chan = Tcl_GetChannel(interp, connid_c, 0); + if(conn_chan == NULL) + return TCL_ERROR; + connid = (Pg_ConnectionId *)Tcl_GetChannelInstanceData(conn_chan); - if (connid != NULL) + for (resid = connid->res_last+1; resid != connid->res_last; resid++) { + if (resid == connid->res_max) + resid = 0; + if (!connid->results[resid]) { - hent = Tcl_CreateHashEntry(&(connid->res_hash), resid->id, &hnew); - Tcl_SetHashValue(hent, (ClientData) 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(Pg_clientData * cd, char *id) +PGresult * +PgGetResultId(Tcl_Interp *interp, char *id) { - Tcl_HashEntry *hent; - Pg_ResultId *resid; - - hent = Tcl_FindHashEntry(&(cd->res_hash), id); - if (hent == NULL) - { - return (PGresult *) NULL; - } - - resid = (Pg_ResultId *) Tcl_GetHashValue(hent); - return resid->result; + Pg_ConnectionId *connid; + int resid; + + if (!id) + return NULL; + resid = getresid(interp, id, &connid); + if (resid == -1) + return NULL; + return connid->results[resid]; } @@ -169,51 +338,41 @@ PgGetResultId(Pg_clientData * cd, char *id) * Remove a result Id from the hash tables */ void -PgDelResultId(Pg_clientData * cd, char *id) +PgDelResultId(Tcl_Interp *interp, char *id) { - Tcl_HashEntry *hent; - Tcl_HashEntry *hent2; - Pg_ResultId *resid; - - hent = Tcl_FindHashEntry(&(cd->res_hash), id); - if (hent == NULL) - { - return; - } - - resid = (Pg_ResultId *) Tcl_GetHashValue(hent); - if (resid->connection != NULL) - { - hent2 = Tcl_FindHashEntry(&(resid->connection->res_hash), id); - if (hent2 != NULL) - { - Tcl_DeleteHashEntry(hent2); - } - } + Pg_ConnectionId *connid; + int resid; - Tcl_DeleteHashEntry(hent); - ckfree(resid); + resid = getresid(interp, id, &connid); + if (resid == -1) + return; + connid->results[resid] = 0; } /* * Get the connection Id from the result Id */ -void -PgGetConnByResultId(Pg_clientData * cd, char *id, char *resid_c) +int +PgGetConnByResultId(Tcl_Interp *interp, char *resid_c) { - Tcl_HashEntry *hent; - Pg_ResultId *resid; + 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; +} - hent = Tcl_FindHashEntry(&(cd->res_hash), id); - if (hent == NULL) - { - return; - } - resid = (Pg_ResultId *) Tcl_GetHashValue(hent); - if (resid->connection != NULL) - { - strcpy(id, resid->connection->id); - } -} |