2082 lines
47 KiB
C
2082 lines
47 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pgtclCmds.c
|
|
* C functions which implement pg_* tcl commands
|
|
*
|
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclCmds.c,v 1.75 2003/11/08 20:34:36 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres_fe.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "pgtclCmds.h"
|
|
#include "pgtclId.h"
|
|
#include "libpq/libpq-fs.h" /* large-object interface */
|
|
|
|
/*
|
|
* Local function forward declarations
|
|
*/
|
|
static int execute_put_values(Tcl_Interp *interp, CONST84 char *array_varname,
|
|
PGresult *result, int tupno);
|
|
|
|
|
|
#ifdef TCL_ARRAYS
|
|
|
|
#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
|
|
#define DIGIT(c) ((c) - '0')
|
|
|
|
|
|
/*
|
|
* translate_escape()
|
|
*
|
|
* This function performs in-place translation of a single C-style
|
|
* escape sequence pointed by p. Curly braces { } and double-quote
|
|
* are left escaped if they appear inside an array.
|
|
* The value returned is the pointer to the last character (the one
|
|
* just before the rest of the buffer).
|
|
*/
|
|
|
|
static inline char *
|
|
translate_escape(char *p, int isArray)
|
|
{
|
|
char c,
|
|
*q,
|
|
*s;
|
|
|
|
#ifdef TCL_ARRAYS_DEBUG_ESCAPE
|
|
printf(" escape = '%s'\n", p);
|
|
#endif
|
|
/* Address of the first character after the escape sequence */
|
|
s = p + 2;
|
|
switch (c = *(p + 1))
|
|
{
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
c = DIGIT(c);
|
|
if (ISOCTAL(*s))
|
|
c = (c << 3) + DIGIT(*s++);
|
|
if (ISOCTAL(*s))
|
|
c = (c << 3) + DIGIT(*s++);
|
|
*p = c;
|
|
break;
|
|
case 'b':
|
|
*p = '\b';
|
|
break;
|
|
case 'f':
|
|
*p = '\f';
|
|
break;
|
|
case 'n':
|
|
*p = '\n';
|
|
break;
|
|
case 'r':
|
|
*p = '\r';
|
|
break;
|
|
case 't':
|
|
*p = '\t';
|
|
break;
|
|
case 'v':
|
|
*p = '\v';
|
|
break;
|
|
case '\\':
|
|
case '{':
|
|
case '}':
|
|
case '"':
|
|
|
|
/*
|
|
* Backslahes, curly braces and double-quotes are left escaped
|
|
* if they appear inside an array. They will be unescaped by
|
|
* Tcl in Tcl_AppendElement. The buffer position is advanced
|
|
* by 1 so that the this character is not processed again by
|
|
* the caller.
|
|
*/
|
|
if (isArray)
|
|
return p + 1;
|
|
else
|
|
*p = c;
|
|
break;
|
|
case '\0':
|
|
|
|
/*
|
|
* This means a backslash at the end of the string. It should
|
|
* never happen but in that case replace the \ with a \0 but
|
|
* don't shift the rest of the buffer so that the caller can
|
|
* see the end of the string and terminate.
|
|
*/
|
|
*p = c;
|
|
return p;
|
|
break;
|
|
default:
|
|
|
|
/*
|
|
* Default case, store the escaped character over the
|
|
* backslash and shift the buffer over itself.
|
|
*/
|
|
*p = c;
|
|
}
|
|
/* Shift the rest of the buffer over itself after the current char */
|
|
q = p + 1;
|
|
for (; *s;)
|
|
*q++ = *s++;
|
|
*q = '\0';
|
|
#ifdef TCL_ARRAYS_DEBUG_ESCAPE
|
|
printf(" after = '%s'\n", p);
|
|
#endif
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* tcl_value()
|
|
*
|
|
* This function does in-line conversion of a value returned by libpq
|
|
* into a tcl string or into a tcl list if the value looks like the
|
|
* representation of a postgres array.
|
|
*/
|
|
|
|
static char *
|
|
tcl_value(char *value)
|
|
{
|
|
int literal,
|
|
last;
|
|
char *p;
|
|
|
|
if (!value)
|
|
return (char *) NULL;
|
|
|
|
#ifdef TCL_ARRAYS_DEBUG
|
|
printf("pq_value = '%s'\n", value);
|
|
#endif
|
|
last = strlen(value) - 1;
|
|
if ((last >= 1) && (value[0] == '{') && (value[last] == '}'))
|
|
{
|
|
/* Looks like an array, replace ',' with spaces */
|
|
/* Remove the outer pair of { }, the last first! */
|
|
value[last] = '\0';
|
|
value++;
|
|
literal = 0;
|
|
for (p = value; *p; p++)
|
|
{
|
|
if (!literal)
|
|
{
|
|
/* We are at the list level, look for ',' and '"' */
|
|
switch (*p)
|
|
{
|
|
case '"': /* beginning of literal */
|
|
literal = 1;
|
|
break;
|
|
case ',': /* replace the ',' with space */
|
|
*p = ' ';
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We are inside a C string */
|
|
switch (*p)
|
|
{
|
|
case '"': /* end of literal */
|
|
literal = 0;
|
|
break;
|
|
case '\\':
|
|
|
|
/*
|
|
* escape sequence, translate it
|
|
*/
|
|
p = translate_escape(p, 1);
|
|
break;
|
|
}
|
|
}
|
|
if (!*p)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Looks like a normal scalar value */
|
|
for (p = value; *p; p++)
|
|
{
|
|
if (*p == '\\')
|
|
{
|
|
/*
|
|
* escape sequence, translate it
|
|
*/
|
|
p = translate_escape(p, 0);
|
|
}
|
|
if (!*p)
|
|
break;
|
|
}
|
|
}
|
|
#ifdef TCL_ARRAYS_DEBUG
|
|
printf("tcl_value = '%s'\n\n", value);
|
|
#endif
|
|
return value;
|
|
}
|
|
#endif /* TCL_ARRAYS */
|
|
|
|
|
|
/**********************************
|
|
* pg_conndefaults
|
|
|
|
syntax:
|
|
pg_conndefaults
|
|
|
|
the return result is a list describing the possible options and their
|
|
current default values for a call to pg_connect with the new -conninfo
|
|
syntax. Each entry in the list is a sublist of the format:
|
|
|
|
{optname label dispchar dispsize value}
|
|
|
|
**********************************/
|
|
|
|
int
|
|
Pg_conndefaults(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PQconninfoOption *options = PQconndefaults();
|
|
PQconninfoOption *option;
|
|
Tcl_DString result;
|
|
char ibuf[32];
|
|
|
|
if (options)
|
|
{
|
|
Tcl_DStringInit(&result);
|
|
|
|
for (option = options; option->keyword != NULL; option++)
|
|
{
|
|
char *val = option->val ? option->val : "";
|
|
|
|
sprintf(ibuf, "%d", option->dispsize);
|
|
Tcl_DStringStartSublist(&result);
|
|
Tcl_DStringAppendElement(&result, option->keyword);
|
|
Tcl_DStringAppendElement(&result, option->label);
|
|
Tcl_DStringAppendElement(&result, option->dispchar);
|
|
Tcl_DStringAppendElement(&result, ibuf);
|
|
Tcl_DStringAppendElement(&result, val);
|
|
Tcl_DStringEndSublist(&result);
|
|
}
|
|
Tcl_DStringResult(interp, &result);
|
|
|
|
PQconninfoFree(options);
|
|
}
|
|
|
|
return TCL_OK;
|
|
}
|
|
|
|
|
|
/**********************************
|
|
* pg_connect
|
|
make a connection to a backend.
|
|
|
|
syntax:
|
|
pg_connect dbName [-host hostName] [-port portNumber] [-tty pqtty]]
|
|
|
|
the return result is either an error message or a handle for a database
|
|
connection. Handles start with the prefix "pgp"
|
|
|
|
**********************************/
|
|
|
|
int
|
|
Pg_connect(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
const char *pghost = NULL;
|
|
const char *pgtty = NULL;
|
|
const char *pgport = NULL;
|
|
const char *pgoptions = NULL;
|
|
const char *dbName;
|
|
int i;
|
|
PGconn *conn;
|
|
|
|
if (argc == 1)
|
|
{
|
|
Tcl_AppendResult(interp, "pg_connect: database name missing\n", 0);
|
|
Tcl_AppendResult(interp, "pg_connect databaseName [-host hostName] [-port portNumber] [-tty pgtty]\n", 0);
|
|
Tcl_AppendResult(interp, "pg_connect -conninfo conninfoString", 0);
|
|
return TCL_ERROR;
|
|
|
|
}
|
|
|
|
if (!strcmp("-conninfo", argv[1]))
|
|
{
|
|
/*
|
|
* Establish a connection using the new PQconnectdb() interface
|
|
*/
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "pg_connect: syntax error\n", 0);
|
|
Tcl_AppendResult(interp, "pg_connect -conninfo conninfoString", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
conn = PQconnectdb(argv[2]);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Establish a connection using the old PQsetdb() interface
|
|
*/
|
|
if (argc > 2)
|
|
{
|
|
/* parse for pg environment settings */
|
|
i = 2;
|
|
while (i + 1 < argc)
|
|
{
|
|
if (strcmp(argv[i], "-host") == 0)
|
|
{
|
|
pghost = argv[i + 1];
|
|
i += 2;
|
|
}
|
|
else if (strcmp(argv[i], "-port") == 0)
|
|
{
|
|
pgport = argv[i + 1];
|
|
i += 2;
|
|
}
|
|
else if (strcmp(argv[i], "-tty") == 0)
|
|
{
|
|
pgtty = argv[i + 1];
|
|
i += 2;
|
|
}
|
|
else if (strcmp(argv[i], "-options") == 0)
|
|
{
|
|
pgoptions = argv[i + 1];
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
Tcl_AppendResult(interp, "Bad option to pg_connect: ",
|
|
argv[i], 0);
|
|
Tcl_AppendResult(interp, "\npg_connect databaseName [-host hostName] [-port portNumber] [-tty pgtty]", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
} /* while */
|
|
if ((i % 2 != 0) || i != argc)
|
|
{
|
|
Tcl_AppendResult(interp, "wrong # of arguments to pg_connect: ",
|
|
argv[i], 0);
|
|
Tcl_AppendResult(interp, "\npg_connect databaseName [-host hostName] [-port portNumber] [-tty pgtty]", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
dbName = argv[1];
|
|
conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName);
|
|
}
|
|
|
|
if (PQstatus(conn) == CONNECTION_OK)
|
|
{
|
|
PgSetConnectionId(interp, conn);
|
|
return TCL_OK;
|
|
}
|
|
else
|
|
{
|
|
Tcl_AppendResult(interp, "Connection to database failed\n",
|
|
PQerrorMessage(conn), 0);
|
|
PQfinish(conn);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
/**********************************
|
|
* pg_disconnect
|
|
close a backend connection
|
|
|
|
syntax:
|
|
pg_disconnect connection
|
|
|
|
The argument passed in must be a connection pointer.
|
|
|
|
**********************************/
|
|
|
|
int
|
|
Pg_disconnect(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
Tcl_Channel conn_chan;
|
|
|
|
if (argc != 2)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n", "pg_disconnect connection", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn_chan = Tcl_GetChannel(interp, argv[1], 0);
|
|
if (conn_chan == NULL)
|
|
{
|
|
Tcl_ResetResult(interp);
|
|
Tcl_AppendResult(interp, argv[1], " is not a valid connection", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/* Check that it is a PG connection and not something else */
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
return Tcl_UnregisterChannel(interp, conn_chan);
|
|
}
|
|
|
|
/**********************************
|
|
* pg_exec
|
|
send a query string to the backend connection
|
|
|
|
syntax:
|
|
pg_exec connection query
|
|
|
|
the return result is either an error message or a handle for a query
|
|
result. Handles start with the prefix "pgp"
|
|
**********************************/
|
|
|
|
int
|
|
Pg_exec(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
PGresult *result;
|
|
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_exec connection queryString", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], &connid);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
if (connid->res_copyStatus != RES_COPY_NONE)
|
|
{
|
|
Tcl_SetResult(interp, "Attempt to query while COPY in progress", TCL_STATIC);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
result = PQexec(conn, argv[2]);
|
|
|
|
/* Transfer any notify events from libpq to Tcl event queue. */
|
|
PgNotifyTransferEvents(connid);
|
|
|
|
if (result)
|
|
{
|
|
int rId = PgSetResultId(interp, argv[1], result);
|
|
|
|
ExecStatusType rStat = PQresultStatus(result);
|
|
|
|
if (rStat == PGRES_COPY_IN || rStat == PGRES_COPY_OUT)
|
|
{
|
|
connid->res_copyStatus = RES_COPY_INPROGRESS;
|
|
connid->res_copy = rId;
|
|
}
|
|
return TCL_OK;
|
|
}
|
|
else
|
|
{
|
|
/* error occurred during the query */
|
|
Tcl_SetResult(interp, PQerrorMessage(conn), TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
|
|
/**********************************
|
|
* pg_result
|
|
get information about the results of a query
|
|
|
|
syntax:
|
|
|
|
pg_result result ?option?
|
|
|
|
the options are:
|
|
|
|
-status the status of the result
|
|
|
|
-error the error message, if the status indicates error; otherwise
|
|
an empty string
|
|
|
|
-conn the connection that produced the result
|
|
|
|
-oid if command was an INSERT, the OID of the inserted tuple
|
|
|
|
-numTuples the number of tuples in the query
|
|
|
|
-cmdTuples the number of tuples affected by the query
|
|
|
|
-numAttrs returns the number of attributes returned by the query
|
|
|
|
-assign arrayName
|
|
assign the results to an array, using subscripts of the form
|
|
(tupno,attributeName)
|
|
|
|
-assignbyidx arrayName ?appendstr?
|
|
assign the results to an array using the first field's value
|
|
as a key.
|
|
All but the first field of each tuple are stored, using
|
|
subscripts of the form (field0value,attributeNameappendstr)
|
|
|
|
-getTuple tupleNumber
|
|
returns the values of the tuple in a list
|
|
|
|
-tupleArray tupleNumber arrayName
|
|
stores the values of the tuple in array arrayName, indexed
|
|
by the attributes returned
|
|
|
|
-attributes
|
|
returns a list of the name/type pairs of the tuple attributes
|
|
|
|
-lAttributes
|
|
returns a list of the {name type len} entries of the tuple
|
|
attributes
|
|
|
|
-clear clear the result buffer. Do not reuse after this
|
|
|
|
**********************************/
|
|
int
|
|
Pg_result(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGresult *result;
|
|
const char *opt;
|
|
int i;
|
|
int tupno;
|
|
CONST84 char *arrVar;
|
|
char nameBuffer[256];
|
|
const char *appendstr;
|
|
|
|
if (argc < 3 || argc > 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n", 0);
|
|
goto Pg_result_errReturn; /* append help info */
|
|
}
|
|
|
|
result = PgGetResultId(interp, argv[1]);
|
|
if (result == (PGresult *) NULL)
|
|
{
|
|
Tcl_AppendResult(interp, "\n",
|
|
argv[1], " is not a valid query result", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
opt = argv[2];
|
|
|
|
if (strcmp(opt, "-status") == 0)
|
|
{
|
|
Tcl_AppendResult(interp, PQresStatus(PQresultStatus(result)), 0);
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-error") == 0)
|
|
{
|
|
Tcl_SetResult(interp, (char *) PQresultErrorMessage(result),
|
|
TCL_STATIC);
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-conn") == 0)
|
|
return PgGetConnByResultId(interp, argv[1]);
|
|
else if (strcmp(opt, "-oid") == 0)
|
|
{
|
|
sprintf(interp->result, "%u", PQoidValue(result));
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-clear") == 0)
|
|
{
|
|
PgDelResultId(interp, argv[1]);
|
|
PQclear(result);
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-numTuples") == 0)
|
|
{
|
|
sprintf(interp->result, "%d", PQntuples(result));
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-cmdTuples") == 0)
|
|
{
|
|
sprintf(interp->result, "%s", PQcmdTuples(result));
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-numAttrs") == 0)
|
|
{
|
|
sprintf(interp->result, "%d", PQnfields(result));
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-assign") == 0)
|
|
{
|
|
if (argc != 4)
|
|
{
|
|
Tcl_AppendResult(interp, "-assign option must be followed by a variable name", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
arrVar = argv[3];
|
|
|
|
/*
|
|
* this assignment assigns the table of result tuples into a giant
|
|
* array with the name given in the argument. The indices of the
|
|
* array are of the form (tupno,attrName). Note we expect field
|
|
* names not to exceed a few dozen characters, so truncating to
|
|
* prevent buffer overflow shouldn't be a problem.
|
|
*/
|
|
for (tupno = 0; tupno < PQntuples(result); tupno++)
|
|
{
|
|
for (i = 0; i < PQnfields(result); i++)
|
|
{
|
|
sprintf(nameBuffer, "%d,%.200s", tupno, PQfname(result, i));
|
|
if (Tcl_SetVar2(interp, arrVar, nameBuffer,
|
|
#ifdef TCL_ARRAYS
|
|
tcl_value(PQgetvalue(result, tupno, i)),
|
|
#else
|
|
PQgetvalue(result, tupno, i),
|
|
#endif
|
|
TCL_LEAVE_ERR_MSG) == NULL)
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
Tcl_AppendResult(interp, arrVar, 0);
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-assignbyidx") == 0)
|
|
{
|
|
if (argc != 4 && argc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "-assignbyidx option requires an array name and optionally an append string", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
arrVar = argv[3];
|
|
appendstr = (argc == 5) ? (const char *) argv[4] : "";
|
|
|
|
/*
|
|
* this assignment assigns the table of result tuples into a giant
|
|
* array with the name given in the argument. The indices of the
|
|
* array are of the form (field0Value,attrNameappendstr). Here, we
|
|
* still assume PQfname won't exceed 200 characters, but we dare
|
|
* not make the same assumption about the data in field 0 nor the
|
|
* append string.
|
|
*/
|
|
for (tupno = 0; tupno < PQntuples(result); tupno++)
|
|
{
|
|
const char *field0 =
|
|
#ifdef TCL_ARRAYS
|
|
tcl_value(PQgetvalue(result, tupno, 0));
|
|
|
|
#else
|
|
PQgetvalue(result, tupno, 0);
|
|
#endif
|
|
char *workspace = malloc(strlen(field0) + strlen(appendstr) + 210);
|
|
|
|
for (i = 1; i < PQnfields(result); i++)
|
|
{
|
|
sprintf(workspace, "%s,%.200s%s", field0, PQfname(result, i),
|
|
appendstr);
|
|
if (Tcl_SetVar2(interp, arrVar, workspace,
|
|
#ifdef TCL_ARRAYS
|
|
tcl_value(PQgetvalue(result, tupno, i)),
|
|
#else
|
|
PQgetvalue(result, tupno, i),
|
|
#endif
|
|
TCL_LEAVE_ERR_MSG) == NULL)
|
|
{
|
|
free(workspace);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
free(workspace);
|
|
}
|
|
Tcl_AppendResult(interp, arrVar, 0);
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-getTuple") == 0)
|
|
{
|
|
if (argc != 4)
|
|
{
|
|
Tcl_AppendResult(interp, "-getTuple option must be followed by a tuple number", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
tupno = atoi(argv[3]);
|
|
if (tupno < 0 || tupno >= PQntuples(result))
|
|
{
|
|
Tcl_AppendResult(interp, "argument to getTuple cannot exceed number of tuples - 1", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
#ifdef TCL_ARRAYS
|
|
for (i = 0; i < PQnfields(result); i++)
|
|
Tcl_AppendElement(interp, tcl_value(PQgetvalue(result, tupno, i)));
|
|
#else
|
|
for (i = 0; i < PQnfields(result); i++)
|
|
Tcl_AppendElement(interp, PQgetvalue(result, tupno, i));
|
|
#endif
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-tupleArray") == 0)
|
|
{
|
|
if (argc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "-tupleArray option must be followed by a tuple number and array name", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
tupno = atoi(argv[3]);
|
|
if (tupno < 0 || tupno >= PQntuples(result))
|
|
{
|
|
Tcl_AppendResult(interp, "argument to tupleArray cannot exceed number of tuples - 1", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
for (i = 0; i < PQnfields(result); i++)
|
|
{
|
|
if (Tcl_SetVar2(interp, argv[4], PQfname(result, i),
|
|
#ifdef TCL_ARRAYS
|
|
tcl_value(PQgetvalue(result, tupno, i)),
|
|
#else
|
|
PQgetvalue(result, tupno, i),
|
|
#endif
|
|
TCL_LEAVE_ERR_MSG) == NULL)
|
|
return TCL_ERROR;
|
|
}
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-attributes") == 0)
|
|
{
|
|
for (i = 0; i < PQnfields(result); i++)
|
|
Tcl_AppendElement(interp, PQfname(result, i));
|
|
return TCL_OK;
|
|
}
|
|
else if (strcmp(opt, "-lAttributes") == 0)
|
|
{
|
|
for (i = 0; i < PQnfields(result); i++)
|
|
{
|
|
/* start a sublist */
|
|
if (i > 0)
|
|
Tcl_AppendResult(interp, " {", 0);
|
|
else
|
|
Tcl_AppendResult(interp, "{", 0);
|
|
Tcl_AppendElement(interp, PQfname(result, i));
|
|
sprintf(nameBuffer, "%ld", (long) PQftype(result, i));
|
|
Tcl_AppendElement(interp, nameBuffer);
|
|
sprintf(nameBuffer, "%ld", (long) PQfsize(result, i));
|
|
Tcl_AppendElement(interp, nameBuffer);
|
|
/* end the sublist */
|
|
Tcl_AppendResult(interp, "}", 0);
|
|
}
|
|
return TCL_OK;
|
|
}
|
|
else
|
|
{
|
|
Tcl_AppendResult(interp, "Invalid option\n", 0);
|
|
goto Pg_result_errReturn; /* append help info */
|
|
}
|
|
|
|
|
|
Pg_result_errReturn:
|
|
Tcl_AppendResult(interp,
|
|
"pg_result result ?option? where option is\n",
|
|
"\t-status\n",
|
|
"\t-error\n",
|
|
"\t-conn\n",
|
|
"\t-oid\n",
|
|
"\t-numTuples\n",
|
|
"\t-cmdTuples\n",
|
|
"\t-numAttrs\n"
|
|
"\t-assign arrayVarName\n",
|
|
"\t-assignbyidx arrayVarName ?appendstr?\n",
|
|
"\t-getTuple tupleNumber\n",
|
|
"\t-tupleArray tupleNumber arrayVarName\n",
|
|
"\t-attributes\n"
|
|
"\t-lAttributes\n"
|
|
"\t-clear\n",
|
|
(char *) 0);
|
|
return TCL_ERROR;
|
|
|
|
|
|
}
|
|
|
|
|
|
/**********************************
|
|
* pg_execute
|
|
send a query string to the backend connection and process the result
|
|
|
|
syntax:
|
|
pg_execute ?-array name? ?-oid varname? connection query ?loop_body?
|
|
|
|
the return result is the number of tuples processed. If the query
|
|
returns tuples (i.e. a SELECT statement), the result is placed into
|
|
variables
|
|
**********************************/
|
|
|
|
int
|
|
Pg_execute(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
PGresult *result;
|
|
int i;
|
|
int tupno;
|
|
int ntup;
|
|
int loop_rc;
|
|
CONST84 char *oid_varname = NULL;
|
|
CONST84 char *array_varname = NULL;
|
|
char buf[64];
|
|
|
|
char *usage = "Wrong # of arguments\n"
|
|
"pg_execute ?-array arrayname? ?-oid varname? "
|
|
"connection queryString ?loop_body?";
|
|
|
|
/*
|
|
* First we parse the options
|
|
*/
|
|
i = 1;
|
|
while (i < argc)
|
|
{
|
|
if (argv[i][0] != '-')
|
|
break;
|
|
|
|
if (strcmp(argv[i], "-array") == 0)
|
|
{
|
|
/*
|
|
* The rows should appear in an array vs. to single variables
|
|
*/
|
|
i++;
|
|
if (i == argc)
|
|
{
|
|
Tcl_SetResult(interp, usage, TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
array_varname = argv[i++];
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(argv[i], "-oid") == 0)
|
|
{
|
|
/*
|
|
* We should place PQoidValue() somewhere
|
|
*/
|
|
i++;
|
|
if (i == argc)
|
|
{
|
|
Tcl_SetResult(interp, usage, TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
oid_varname = argv[i++];
|
|
continue;
|
|
}
|
|
|
|
Tcl_AppendResult(interp, "Unknown option '", argv[i], "'", NULL);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Check that after option parsing at least 'connection' and 'query'
|
|
* are left
|
|
*/
|
|
if (argc - i < 2)
|
|
{
|
|
Tcl_SetResult(interp, usage, TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Get the connection and make sure no COPY command is pending
|
|
*/
|
|
conn = PgGetConnectionId(interp, argv[i++], &connid);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
if (connid->res_copyStatus != RES_COPY_NONE)
|
|
{
|
|
Tcl_SetResult(interp, "Attempt to query while COPY in progress", TCL_STATIC);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Execute the query
|
|
*/
|
|
result = PQexec(conn, argv[i++]);
|
|
|
|
/*
|
|
* Transfer any notify events from libpq to Tcl event queue.
|
|
*/
|
|
PgNotifyTransferEvents(connid);
|
|
|
|
/*
|
|
* Check for errors
|
|
*/
|
|
if (result == NULL)
|
|
{
|
|
Tcl_SetResult(interp, PQerrorMessage(conn), TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Set the oid variable to the returned oid of an INSERT statement if
|
|
* requested (or 0 if it wasn't an INSERT)
|
|
*/
|
|
if (oid_varname != NULL)
|
|
{
|
|
char oid_buf[32];
|
|
|
|
sprintf(oid_buf, "%u", PQoidValue(result));
|
|
if (Tcl_SetVar(interp, oid_varname, oid_buf,
|
|
TCL_LEAVE_ERR_MSG) == NULL)
|
|
{
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Decide how to go on based on the result status
|
|
*/
|
|
switch (PQresultStatus(result))
|
|
{
|
|
case PGRES_TUPLES_OK:
|
|
/* fall through if we have tuples */
|
|
break;
|
|
|
|
case PGRES_EMPTY_QUERY:
|
|
case PGRES_COMMAND_OK:
|
|
case PGRES_COPY_IN:
|
|
case PGRES_COPY_OUT:
|
|
/* tell the number of affected tuples for non-SELECT queries */
|
|
Tcl_SetResult(interp, PQcmdTuples(result), TCL_VOLATILE);
|
|
PQclear(result);
|
|
return TCL_OK;
|
|
|
|
default:
|
|
/* anything else must be an error */
|
|
Tcl_ResetResult(interp);
|
|
Tcl_AppendElement(interp, PQresStatus(PQresultStatus(result)));
|
|
Tcl_AppendElement(interp, PQresultErrorMessage(result));
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* We reach here only for queries that returned tuples
|
|
*/
|
|
if (i == argc)
|
|
{
|
|
/*
|
|
* We don't have a loop body. If we have at least one result row,
|
|
* we set all the variables to the first one and return.
|
|
*/
|
|
if (PQntuples(result) > 0)
|
|
{
|
|
if (execute_put_values(interp, array_varname, result, 0) != TCL_OK)
|
|
{
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
|
|
sprintf(buf, "%d", PQntuples(result));
|
|
Tcl_SetResult(interp, buf, TCL_VOLATILE);
|
|
PQclear(result);
|
|
return TCL_OK;
|
|
}
|
|
|
|
/*
|
|
* We have a loop body. For each row in the result set put the values
|
|
* into the Tcl variables and execute the body.
|
|
*/
|
|
ntup = PQntuples(result);
|
|
for (tupno = 0; tupno < ntup; tupno++)
|
|
{
|
|
if (execute_put_values(interp, array_varname, result, tupno) != TCL_OK)
|
|
{
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
loop_rc = Tcl_Eval(interp, argv[i]);
|
|
|
|
/* The returncode of the loop body controls the loop execution */
|
|
if (loop_rc == TCL_OK || loop_rc == TCL_CONTINUE)
|
|
/* OK or CONTINUE means start next loop invocation */
|
|
continue;
|
|
if (loop_rc == TCL_RETURN)
|
|
{
|
|
/* RETURN means hand up the given interpreter result */
|
|
PQclear(result);
|
|
return TCL_RETURN;
|
|
}
|
|
if (loop_rc == TCL_BREAK)
|
|
/* BREAK means leave the loop */
|
|
break;
|
|
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* At the end of the loop we put the number of rows we got into the
|
|
* interpreter result and clear the result set.
|
|
*/
|
|
sprintf(buf, "%d", ntup);
|
|
Tcl_SetResult(interp, buf, TCL_VOLATILE);
|
|
PQclear(result);
|
|
return TCL_OK;
|
|
}
|
|
|
|
|
|
/**********************************
|
|
* execute_put_values
|
|
|
|
Put the values of one tuple into Tcl variables named like the
|
|
column names, or into an array indexed by the column names.
|
|
**********************************/
|
|
static int
|
|
execute_put_values(Tcl_Interp *interp, CONST84 char *array_varname,
|
|
PGresult *result, int tupno)
|
|
{
|
|
int i;
|
|
int n;
|
|
char *fname;
|
|
char *value;
|
|
|
|
/*
|
|
* For each column get the column name and value and put it into a Tcl
|
|
* variable (either scalar or array item)
|
|
*/
|
|
n = PQnfields(result);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
fname = PQfname(result, i);
|
|
value = PQgetvalue(result, tupno, i);
|
|
|
|
if (array_varname != NULL)
|
|
{
|
|
if (Tcl_SetVar2(interp, array_varname, fname, value,
|
|
TCL_LEAVE_ERR_MSG) == NULL)
|
|
return TCL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
if (Tcl_SetVar(interp, fname, value, TCL_LEAVE_ERR_MSG) == NULL)
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
|
|
return TCL_OK;
|
|
}
|
|
|
|
|
|
/**********************************
|
|
* pg_lo_open
|
|
open a large object
|
|
|
|
syntax:
|
|
pg_lo_open conn objOid mode
|
|
|
|
where mode can be either 'r', 'w', or 'rw'
|
|
**********************/
|
|
|
|
int
|
|
Pg_lo_open(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
int lobjId;
|
|
int mode;
|
|
int fd;
|
|
|
|
if (argc != 4)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_open connection lobjOid mode", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
lobjId = atoi(argv[2]);
|
|
if (strlen(argv[3]) < 1 ||
|
|
strlen(argv[3]) > 2)
|
|
{
|
|
Tcl_AppendResult(interp, "mode argument must be 'r', 'w', or 'rw'", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
switch (argv[3][0])
|
|
{
|
|
case 'r':
|
|
case 'R':
|
|
mode = INV_READ;
|
|
break;
|
|
case 'w':
|
|
case 'W':
|
|
mode = INV_WRITE;
|
|
break;
|
|
default:
|
|
Tcl_AppendResult(interp, "mode argument must be 'r', 'w', or 'rw'", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
switch (argv[3][1])
|
|
{
|
|
case '\0':
|
|
break;
|
|
case 'r':
|
|
case 'R':
|
|
mode |= INV_READ;
|
|
break;
|
|
case 'w':
|
|
case 'W':
|
|
mode |= INV_WRITE;
|
|
break;
|
|
default:
|
|
Tcl_AppendResult(interp, "mode argument must be 'r', 'w', or 'rw'", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
fd = lo_open(conn, lobjId, mode);
|
|
sprintf(interp->result, "%d", fd);
|
|
return TCL_OK;
|
|
}
|
|
|
|
/**********************************
|
|
* pg_lo_close
|
|
close a large object
|
|
|
|
syntax:
|
|
pg_lo_close conn fd
|
|
|
|
**********************/
|
|
int
|
|
Pg_lo_close(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
int fd;
|
|
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_close connection fd", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
fd = atoi(argv[2]);
|
|
sprintf(interp->result, "%d", lo_close(conn, fd));
|
|
return TCL_OK;
|
|
}
|
|
|
|
/**********************************
|
|
* pg_lo_read
|
|
reads at most len bytes from a large object into a variable named
|
|
bufVar
|
|
|
|
syntax:
|
|
pg_lo_read conn fd bufVar len
|
|
|
|
bufVar is the name of a variable in which to store the contents of the read
|
|
|
|
**********************/
|
|
#ifdef PGTCL_USE_TCLOBJ
|
|
int
|
|
Pg_lo_read(ClientData cData, Tcl_Interp *interp, int objc,
|
|
Tcl_Obj *CONST objv[])
|
|
{
|
|
PGconn *conn;
|
|
int fd;
|
|
int nbytes = 0;
|
|
char *buf;
|
|
Tcl_Obj *bufVar;
|
|
Tcl_Obj *bufObj;
|
|
int len;
|
|
int rc = TCL_OK;
|
|
|
|
if (objc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
" pg_lo_read conn fd bufVar len", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, Tcl_GetStringFromObj(objv[1], NULL),
|
|
(Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
if (Tcl_GetIntFromObj(interp, objv[2], &fd) != TCL_OK)
|
|
return TCL_ERROR;
|
|
|
|
bufVar = objv[3];
|
|
|
|
if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK)
|
|
return TCL_ERROR;
|
|
|
|
if (len <= 0)
|
|
{
|
|
Tcl_SetObjResult(interp, Tcl_NewIntObj(nbytes));
|
|
return TCL_OK;
|
|
}
|
|
buf = ckalloc(len + 1);
|
|
|
|
nbytes = lo_read(conn, fd, buf, len);
|
|
|
|
if (nbytes >= 0)
|
|
{
|
|
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 1 || TCL_MAJOR_VERSION > 8
|
|
bufObj = Tcl_NewByteArrayObj(buf, nbytes);
|
|
#else
|
|
bufObj = Tcl_NewStringObj(buf, nbytes);
|
|
#endif
|
|
|
|
if (Tcl_ObjSetVar2(interp, bufVar, NULL, bufObj,
|
|
TCL_LEAVE_ERR_MSG | TCL_PARSE_PART1) == NULL)
|
|
rc = TCL_ERROR;
|
|
}
|
|
|
|
if (rc == TCL_OK)
|
|
Tcl_SetObjResult(interp, Tcl_NewIntObj(nbytes));
|
|
|
|
ckfree(buf);
|
|
return rc;
|
|
}
|
|
|
|
#else
|
|
int
|
|
Pg_lo_read(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
int fd;
|
|
int nbytes = 0;
|
|
char *buf;
|
|
char *bufVar;
|
|
int len;
|
|
|
|
if (argc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
" pg_lo_read conn fd bufVar len", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
fd = atoi(argv[2]);
|
|
|
|
bufVar = argv[3];
|
|
|
|
len = atoi(argv[4]);
|
|
|
|
if (len <= 0)
|
|
{
|
|
sprintf(interp->result, "%d", nbytes);
|
|
return TCL_OK;
|
|
}
|
|
buf = ckalloc(len + 1);
|
|
|
|
nbytes = lo_read(conn, fd, buf, len);
|
|
|
|
if (nbytes >= 0)
|
|
Tcl_SetVar(interp, bufVar, buf, TCL_LEAVE_ERR_MSG);
|
|
|
|
sprintf(interp->result, "%d", nbytes);
|
|
ckfree(buf);
|
|
return TCL_OK;
|
|
|
|
}
|
|
#endif
|
|
|
|
/***********************************
|
|
Pg_lo_write
|
|
write at most len bytes to a large object
|
|
|
|
syntax:
|
|
pg_lo_write conn fd buf len
|
|
|
|
***********************************/
|
|
#ifdef PGTCL_USE_TCLOBJ
|
|
int
|
|
Pg_lo_write(ClientData cData, Tcl_Interp *interp, int objc,
|
|
Tcl_Obj *CONST objv[])
|
|
{
|
|
PGconn *conn;
|
|
char *buf;
|
|
int fd;
|
|
int nbytes = 0;
|
|
int len;
|
|
|
|
if (objc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_write conn fd buf len", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, Tcl_GetStringFromObj(objv[1], NULL),
|
|
(Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
if (Tcl_GetIntFromObj(interp, objv[2], &fd) != TCL_OK)
|
|
return TCL_ERROR;
|
|
|
|
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 1 || TCL_MAJOR_VERSION > 8
|
|
buf = Tcl_GetByteArrayFromObj(objv[3], &nbytes);
|
|
#else
|
|
buf = Tcl_GetStringFromObj(objv[3], &nbytes);
|
|
#endif
|
|
|
|
if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK)
|
|
return TCL_ERROR;
|
|
|
|
if (len > nbytes)
|
|
len = nbytes;
|
|
|
|
if (len <= 0)
|
|
{
|
|
Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
|
|
return TCL_OK;
|
|
}
|
|
|
|
nbytes = lo_write(conn, fd, buf, len);
|
|
Tcl_SetObjResult(interp, Tcl_NewIntObj(nbytes));
|
|
return TCL_OK;
|
|
}
|
|
|
|
#else
|
|
int
|
|
Pg_lo_write(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
char *buf;
|
|
int fd;
|
|
int nbytes = 0;
|
|
int len;
|
|
|
|
if (argc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_write conn fd buf len", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
fd = atoi(argv[2]);
|
|
|
|
buf = argv[3];
|
|
|
|
len = atoi(argv[4]);
|
|
|
|
if (len <= 0)
|
|
{
|
|
sprintf(interp->result, "%d", nbytes);
|
|
return TCL_OK;
|
|
}
|
|
|
|
nbytes = lo_write(conn, fd, buf, len);
|
|
sprintf(interp->result, "%d", nbytes);
|
|
return TCL_OK;
|
|
}
|
|
#endif
|
|
|
|
/***********************************
|
|
Pg_lo_lseek
|
|
seek to a certain position in a large object
|
|
|
|
syntax
|
|
pg_lo_lseek conn fd offset whence
|
|
|
|
whence can be either
|
|
"SEEK_CUR", "SEEK_END", or "SEEK_SET"
|
|
***********************************/
|
|
int
|
|
Pg_lo_lseek(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
int fd;
|
|
const char *whenceStr;
|
|
int offset,
|
|
whence;
|
|
|
|
if (argc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_lseek conn fd offset whence", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
fd = atoi(argv[2]);
|
|
|
|
offset = atoi(argv[3]);
|
|
|
|
whenceStr = argv[4];
|
|
if (strcmp(whenceStr, "SEEK_SET") == 0)
|
|
whence = SEEK_SET;
|
|
else if (strcmp(whenceStr, "SEEK_CUR") == 0)
|
|
whence = SEEK_CUR;
|
|
else if (strcmp(whenceStr, "SEEK_END") == 0)
|
|
whence = SEEK_END;
|
|
else
|
|
{
|
|
Tcl_AppendResult(interp, "the whence argument to Pg_lo_lseek must be SEEK_SET, SEEK_CUR or SEEK_END", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
sprintf(interp->result, "%d", lo_lseek(conn, fd, offset, whence));
|
|
return TCL_OK;
|
|
}
|
|
|
|
|
|
/***********************************
|
|
Pg_lo_creat
|
|
create a new large object with mode
|
|
|
|
syntax:
|
|
pg_lo_creat conn mode
|
|
|
|
mode can be any OR'ing together of INV_READ, INV_WRITE,
|
|
for now, we don't support any additional storage managers.
|
|
|
|
***********************************/
|
|
int
|
|
Pg_lo_creat(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
char *modeStr;
|
|
char *modeWord;
|
|
int mode;
|
|
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_creat conn mode", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
modeStr = strdup(argv[2]);
|
|
|
|
modeWord = strtok(modeStr, "|");
|
|
if (strcmp(modeWord, "INV_READ") == 0)
|
|
mode = INV_READ;
|
|
else if (strcmp(modeWord, "INV_WRITE") == 0)
|
|
mode = INV_WRITE;
|
|
else
|
|
{
|
|
Tcl_AppendResult(interp,
|
|
"invalid mode argument to Pg_lo_creat\nmode argument must be some OR'd combination of INV_READ, and INV_WRITE",
|
|
0);
|
|
free(modeStr);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
while ((modeWord = strtok((char *) NULL, "|")) != NULL)
|
|
{
|
|
if (strcmp(modeWord, "INV_READ") == 0)
|
|
mode |= INV_READ;
|
|
else if (strcmp(modeWord, "INV_WRITE") == 0)
|
|
mode |= INV_WRITE;
|
|
else
|
|
{
|
|
Tcl_AppendResult(interp,
|
|
"invalid mode argument to Pg_lo_creat\nmode argument must be some OR'd combination of INV_READ, INV_WRITE",
|
|
0);
|
|
free(modeStr);
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
sprintf(interp->result, "%d", lo_creat(conn, mode));
|
|
free(modeStr);
|
|
return TCL_OK;
|
|
}
|
|
|
|
/***********************************
|
|
Pg_lo_tell
|
|
returns the current seek location of the large object
|
|
|
|
syntax:
|
|
pg_lo_tell conn fd
|
|
|
|
***********************************/
|
|
int
|
|
Pg_lo_tell(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
int fd;
|
|
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_tell conn fd", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
fd = atoi(argv[2]);
|
|
|
|
sprintf(interp->result, "%d", lo_tell(conn, fd));
|
|
return TCL_OK;
|
|
|
|
}
|
|
|
|
/***********************************
|
|
Pg_lo_unlink
|
|
unlink a file based on lobject id
|
|
|
|
syntax:
|
|
pg_lo_unlink conn lobjId
|
|
|
|
|
|
***********************************/
|
|
int
|
|
Pg_lo_unlink(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
int lobjId;
|
|
int retval;
|
|
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_tell conn fd", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
lobjId = atoi(argv[2]);
|
|
|
|
retval = lo_unlink(conn, lobjId);
|
|
if (retval == -1)
|
|
{
|
|
sprintf(interp->result, "Pg_lo_unlink of '%d' failed", lobjId);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
sprintf(interp->result, "%d", retval);
|
|
return TCL_OK;
|
|
}
|
|
|
|
/***********************************
|
|
Pg_lo_import
|
|
import a Unix file into an (inversion) large objct
|
|
returns the oid of that object upon success
|
|
returns InvalidOid upon failure
|
|
|
|
syntax:
|
|
pg_lo_import conn filename
|
|
|
|
***********************************/
|
|
|
|
int
|
|
Pg_lo_import(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
const char *filename;
|
|
Oid lobjId;
|
|
|
|
if (argc != 3)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_import conn filename", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
filename = argv[2];
|
|
|
|
lobjId = lo_import(conn, filename);
|
|
if (lobjId == InvalidOid)
|
|
{
|
|
/*
|
|
* What is the maximum size of this? FIXME if this is not a good
|
|
* quess
|
|
*/
|
|
snprintf(interp->result, 128, "Pg_lo_import of '%s' failed", filename);
|
|
return TCL_ERROR;
|
|
}
|
|
sprintf(interp->result, "%u", lobjId);
|
|
return TCL_OK;
|
|
}
|
|
|
|
/***********************************
|
|
Pg_lo_export
|
|
export an Inversion large object to a Unix file
|
|
|
|
syntax:
|
|
pg_lo_export conn lobjId filename
|
|
|
|
***********************************/
|
|
|
|
int
|
|
Pg_lo_export(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
PGconn *conn;
|
|
const char *filename;
|
|
Oid lobjId;
|
|
int retval;
|
|
|
|
if (argc != 4)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_lo_export conn lobjId filename", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], (Pg_ConnectionId **) NULL);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
lobjId = atoi(argv[2]);
|
|
filename = argv[3];
|
|
|
|
retval = lo_export(conn, lobjId, filename);
|
|
if (retval == -1)
|
|
{
|
|
sprintf(interp->result, "Pg_lo_export %u %s failed", lobjId, filename);
|
|
return TCL_ERROR;
|
|
}
|
|
return TCL_OK;
|
|
}
|
|
|
|
/**********************************
|
|
* pg_select
|
|
send a select query string to the backend connection
|
|
|
|
syntax:
|
|
pg_select connection query var proc
|
|
|
|
The query must be a select statement
|
|
The var is used in the proc as an array
|
|
The proc is run once for each row found
|
|
|
|
Originally I was also going to update changes but that has turned out
|
|
to be not so simple. Instead, the caller should get the OID of any
|
|
table they want to update and update it themself in the loop. I may
|
|
try to write a simplified table lookup and update function to make
|
|
that task a little easier.
|
|
|
|
The return is either TCL_OK, TCL_ERROR or TCL_RETURN and interp->result
|
|
may contain more information.
|
|
**********************************/
|
|
|
|
int
|
|
Pg_select(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
PGresult *result;
|
|
int r,
|
|
retval;
|
|
int tupno,
|
|
column,
|
|
ncols;
|
|
Tcl_DString headers;
|
|
char buffer[2048];
|
|
struct info_s
|
|
{
|
|
char *cname;
|
|
int change;
|
|
} *info;
|
|
|
|
if (argc != 5)
|
|
{
|
|
Tcl_AppendResult(interp, "Wrong # of arguments\n",
|
|
"pg_select connection queryString var proc", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
conn = PgGetConnectionId(interp, argv[1], &connid);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
if ((result = PQexec(conn, argv[2])) == 0)
|
|
{
|
|
/* error occurred sending the query */
|
|
Tcl_SetResult(interp, PQerrorMessage(conn), TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/* Transfer any notify events from libpq to Tcl event queue. */
|
|
PgNotifyTransferEvents(connid);
|
|
|
|
if (PQresultStatus(result) != PGRES_TUPLES_OK)
|
|
{
|
|
/* query failed, or it wasn't SELECT */
|
|
Tcl_SetResult(interp, (char *) PQresultErrorMessage(result),
|
|
TCL_VOLATILE);
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
if ((info = (struct info_s *) ckalloc(sizeof(*info) * (ncols = PQnfields(result)))) == NULL)
|
|
{
|
|
Tcl_AppendResult(interp, "Not enough memory", 0);
|
|
PQclear(result);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
Tcl_DStringInit(&headers);
|
|
|
|
for (column = 0; column < ncols; column++)
|
|
{
|
|
info[column].cname = PQfname(result, column);
|
|
info[column].change = 0;
|
|
Tcl_DStringAppendElement(&headers, info[column].cname);
|
|
}
|
|
|
|
Tcl_SetVar2(interp, argv[3], ".headers", Tcl_DStringValue(&headers), 0);
|
|
Tcl_DStringFree(&headers);
|
|
sprintf(buffer, "%d", ncols);
|
|
Tcl_SetVar2(interp, argv[3], ".numcols", buffer, 0);
|
|
|
|
retval = TCL_OK;
|
|
|
|
for (tupno = 0; tupno < PQntuples(result); tupno++)
|
|
{
|
|
sprintf(buffer, "%d", tupno);
|
|
Tcl_SetVar2(interp, argv[3], ".tupno", buffer, 0);
|
|
|
|
for (column = 0; column < ncols; column++)
|
|
Tcl_SetVar2(interp, argv[3], info[column].cname,
|
|
#ifdef TCL_ARRAYS
|
|
tcl_value(PQgetvalue(result, tupno, column)),
|
|
#else
|
|
PQgetvalue(result, tupno, column),
|
|
#endif
|
|
0);
|
|
|
|
Tcl_SetVar2(interp, argv[3], ".command", "update", 0);
|
|
|
|
if ((r = Tcl_Eval(interp, argv[4])) != TCL_OK && r != TCL_CONTINUE)
|
|
{
|
|
if (r == TCL_BREAK)
|
|
break; /* exit loop, but return TCL_OK */
|
|
|
|
if (r == TCL_ERROR)
|
|
{
|
|
char msg[60];
|
|
|
|
sprintf(msg, "\n (\"pg_select\" body line %d)",
|
|
interp->errorLine);
|
|
Tcl_AddErrorInfo(interp, msg);
|
|
}
|
|
|
|
retval = r;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ckfree((void *) info);
|
|
Tcl_UnsetVar(interp, argv[3], 0);
|
|
PQclear(result);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Test whether any callbacks are registered on this connection for
|
|
* the given relation name. NB: supplied name must be case-folded already.
|
|
*/
|
|
|
|
static int
|
|
Pg_have_listener(Pg_ConnectionId * connid, const char *relname)
|
|
{
|
|
Pg_TclNotifies *notifies;
|
|
Tcl_HashEntry *entry;
|
|
|
|
for (notifies = connid->notify_list;
|
|
notifies != NULL;
|
|
notifies = notifies->next)
|
|
{
|
|
Tcl_Interp *interp = notifies->interp;
|
|
|
|
if (interp == NULL)
|
|
continue; /* ignore deleted interpreter */
|
|
|
|
entry = Tcl_FindHashEntry(¬ifies->notify_hash, (char *) relname);
|
|
if (entry == NULL)
|
|
continue; /* no pg_listen in this interpreter */
|
|
|
|
return TRUE; /* OK, there is a listener */
|
|
}
|
|
|
|
return FALSE; /* Found no listener */
|
|
}
|
|
|
|
/***********************************
|
|
Pg_listen
|
|
create or remove a callback request for notifies on a given name
|
|
|
|
syntax:
|
|
pg_listen conn notifyname ?callbackcommand?
|
|
|
|
With a fourth arg, creates or changes the callback command for
|
|
notifies on the given name; without, cancels the callback request.
|
|
|
|
Callbacks can occur whenever Tcl is executing its event loop.
|
|
This is the normal idle loop in Tk; in plain tclsh applications,
|
|
vwait or update can be used to enter the Tcl event loop.
|
|
***********************************/
|
|
int
|
|
Pg_listen(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
const char *origrelname;
|
|
char *caserelname;
|
|
char *callback = NULL;
|
|
Pg_TclNotifies *notifies;
|
|
Tcl_HashEntry *entry;
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
PGresult *result;
|
|
int new;
|
|
|
|
if (argc < 3 || argc > 4)
|
|
{
|
|
Tcl_AppendResult(interp, "wrong # args, should be \"",
|
|
argv[0], " connection relname ?callback?\"", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Get the command arguments. Note that the relation name will be
|
|
* copied by Tcl_CreateHashEntry while the callback string must be
|
|
* allocated by us.
|
|
*/
|
|
conn = PgGetConnectionId(interp, argv[1], &connid);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
/*
|
|
* LISTEN/NOTIFY do not preserve case unless the relation name is
|
|
* quoted. We have to do the same thing to ensure that we will find
|
|
* the desired pg_listen item.
|
|
*/
|
|
origrelname = argv[2];
|
|
caserelname = (char *) ckalloc((unsigned) (strlen(origrelname) + 1));
|
|
if (*origrelname == '"')
|
|
{
|
|
/* Copy a quoted string without downcasing */
|
|
strcpy(caserelname, origrelname + 1);
|
|
caserelname[strlen(caserelname) - 1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
/* Downcase it */
|
|
const char *rels = origrelname;
|
|
char *reld = caserelname;
|
|
|
|
while (*rels)
|
|
*reld++ = tolower((unsigned char) *rels++);
|
|
*reld = '\0';
|
|
}
|
|
|
|
if ((argc > 3) && *argv[3])
|
|
{
|
|
callback = (char *) ckalloc((unsigned) (strlen(argv[3]) + 1));
|
|
strcpy(callback, argv[3]);
|
|
}
|
|
|
|
/* Find or make a Pg_TclNotifies struct for this interp and connection */
|
|
|
|
for (notifies = connid->notify_list; notifies; notifies = notifies->next)
|
|
{
|
|
if (notifies->interp == interp)
|
|
break;
|
|
}
|
|
if (notifies == NULL)
|
|
{
|
|
notifies = (Pg_TclNotifies *) ckalloc(sizeof(Pg_TclNotifies));
|
|
notifies->interp = interp;
|
|
Tcl_InitHashTable(¬ifies->notify_hash, TCL_STRING_KEYS);
|
|
notifies->conn_loss_cmd = NULL;
|
|
notifies->next = connid->notify_list;
|
|
connid->notify_list = notifies;
|
|
Tcl_CallWhenDeleted(interp, PgNotifyInterpDelete,
|
|
(ClientData) notifies);
|
|
}
|
|
|
|
if (callback)
|
|
{
|
|
/*
|
|
* Create or update a callback for a relation
|
|
*/
|
|
int alreadyHadListener = Pg_have_listener(connid, caserelname);
|
|
|
|
entry = Tcl_CreateHashEntry(¬ifies->notify_hash, caserelname, &new);
|
|
/* If update, free the old callback string */
|
|
if (!new)
|
|
ckfree((char *) Tcl_GetHashValue(entry));
|
|
/* Store the new callback string */
|
|
Tcl_SetHashValue(entry, callback);
|
|
|
|
/* Start the notify event source if it isn't already running */
|
|
PgStartNotifyEventSource(connid);
|
|
|
|
/*
|
|
* Send a LISTEN command if this is the first listener.
|
|
*/
|
|
if (!alreadyHadListener)
|
|
{
|
|
char *cmd = (char *)
|
|
ckalloc((unsigned) (strlen(origrelname) + 8));
|
|
|
|
sprintf(cmd, "LISTEN %s", origrelname);
|
|
result = PQexec(conn, cmd);
|
|
ckfree(cmd);
|
|
/* Transfer any notify events from libpq to Tcl event queue. */
|
|
PgNotifyTransferEvents(connid);
|
|
if (PQresultStatus(result) != PGRES_COMMAND_OK)
|
|
{
|
|
/* Error occurred during the execution of command */
|
|
PQclear(result);
|
|
Tcl_DeleteHashEntry(entry);
|
|
ckfree(callback);
|
|
ckfree(caserelname);
|
|
Tcl_SetResult(interp, PQerrorMessage(conn), TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
PQclear(result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Remove a callback for a relation
|
|
*/
|
|
entry = Tcl_FindHashEntry(¬ifies->notify_hash, caserelname);
|
|
if (entry == NULL)
|
|
{
|
|
Tcl_AppendResult(interp, "not listening on ", origrelname, 0);
|
|
ckfree(caserelname);
|
|
return TCL_ERROR;
|
|
}
|
|
ckfree((char *) Tcl_GetHashValue(entry));
|
|
Tcl_DeleteHashEntry(entry);
|
|
|
|
/*
|
|
* Send an UNLISTEN command if that was the last listener. Note:
|
|
* we don't attempt to turn off the notify mechanism if no LISTENs
|
|
* remain active; not worth the trouble.
|
|
*/
|
|
if (!Pg_have_listener(connid, caserelname))
|
|
{
|
|
char *cmd = (char *)
|
|
ckalloc((unsigned) (strlen(origrelname) + 10));
|
|
|
|
sprintf(cmd, "UNLISTEN %s", origrelname);
|
|
result = PQexec(conn, cmd);
|
|
ckfree(cmd);
|
|
/* Transfer any notify events from libpq to Tcl event queue. */
|
|
PgNotifyTransferEvents(connid);
|
|
if (PQresultStatus(result) != PGRES_COMMAND_OK)
|
|
{
|
|
/* Error occurred during the execution of command */
|
|
PQclear(result);
|
|
ckfree(caserelname);
|
|
Tcl_SetResult(interp, PQerrorMessage(conn), TCL_VOLATILE);
|
|
return TCL_ERROR;
|
|
}
|
|
PQclear(result);
|
|
}
|
|
}
|
|
|
|
ckfree(caserelname);
|
|
return TCL_OK;
|
|
}
|
|
|
|
/***********************************
|
|
Pg_on_connection_loss
|
|
create or remove a callback request for unexpected connection loss
|
|
|
|
syntax:
|
|
pg_on_connection_loss conn ?callbackcommand?
|
|
|
|
With a third arg, creates or changes the callback command for
|
|
connection loss; without, cancels the callback request.
|
|
|
|
Callbacks can occur whenever Tcl is executing its event loop.
|
|
This is the normal idle loop in Tk; in plain tclsh applications,
|
|
vwait or update can be used to enter the Tcl event loop.
|
|
***********************************/
|
|
int
|
|
Pg_on_connection_loss(ClientData cData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
|
|
{
|
|
char *callback = NULL;
|
|
Pg_TclNotifies *notifies;
|
|
Pg_ConnectionId *connid;
|
|
PGconn *conn;
|
|
|
|
if (argc < 2 || argc > 3)
|
|
{
|
|
Tcl_AppendResult(interp, "wrong # args, should be \"",
|
|
argv[0], " connection ?callback?\"", 0);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Get the command arguments.
|
|
*/
|
|
conn = PgGetConnectionId(interp, argv[1], &connid);
|
|
if (conn == (PGconn *) NULL)
|
|
return TCL_ERROR;
|
|
|
|
if ((argc > 2) && *argv[2])
|
|
{
|
|
callback = (char *) ckalloc((unsigned) (strlen(argv[2]) + 1));
|
|
strcpy(callback, argv[2]);
|
|
}
|
|
|
|
/* Find or make a Pg_TclNotifies struct for this interp and connection */
|
|
|
|
for (notifies = connid->notify_list; notifies; notifies = notifies->next)
|
|
{
|
|
if (notifies->interp == interp)
|
|
break;
|
|
}
|
|
if (notifies == NULL)
|
|
{
|
|
notifies = (Pg_TclNotifies *) ckalloc(sizeof(Pg_TclNotifies));
|
|
notifies->interp = interp;
|
|
Tcl_InitHashTable(¬ifies->notify_hash, TCL_STRING_KEYS);
|
|
notifies->conn_loss_cmd = NULL;
|
|
notifies->next = connid->notify_list;
|
|
connid->notify_list = notifies;
|
|
Tcl_CallWhenDeleted(interp, PgNotifyInterpDelete,
|
|
(ClientData) notifies);
|
|
}
|
|
|
|
/* Store new callback setting */
|
|
|
|
if (notifies->conn_loss_cmd)
|
|
ckfree((void *) notifies->conn_loss_cmd);
|
|
notifies->conn_loss_cmd = callback;
|
|
|
|
if (callback)
|
|
{
|
|
/*
|
|
* Start the notify event source if it isn't already running. The
|
|
* notify source will cause Tcl to watch read-ready on the
|
|
* connection socket, so that we find out quickly if the
|
|
* connection drops.
|
|
*/
|
|
PgStartNotifyEventSource(connid);
|
|
}
|
|
|
|
return TCL_OK;
|
|
}
|