diff options
Diffstat (limited to 'src/interfaces')
36 files changed, 5802 insertions, 0 deletions
diff --git a/src/interfaces/libpgtcl/Makefile b/src/interfaces/libpgtcl/Makefile new file mode 100644 index 00000000000..73e218b3842 --- /dev/null +++ b/src/interfaces/libpgtcl/Makefile @@ -0,0 +1,38 @@ +#------------------------------------------------------------------------- +# +# Makefile +# Makefile for libpgtcl library +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/Makefile,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ +# +#------------------------------------------------------------------------- + +LIB= pgtcl + +MKDIR= ../mk +include $(MKDIR)/postgres.mk + +CFLAGS+= -I$(HEADERDIR) \ + -I$(srcdir)/backend/include \ + -I$(srcdir)/backend \ + -I$(CURDIR) \ + -I$(TCL_INCDIR) + +ifdef KRBVERS +CFLAGS+= $(KRBFLAGS) +endif + +LIBSRCS= pgtcl.c pgtclCmds.c pgtclId.c + +install-headers: + $(INSTALL) $(INSTLOPTS) libpgtcl.h $(HEADERDIR)/libpgtcl.h + + +install:: install-headers + +include $(MKDIR)/postgres.lib.mk + diff --git a/src/interfaces/libpgtcl/README b/src/interfaces/libpgtcl/README new file mode 100644 index 00000000000..d2e2d59c798 --- /dev/null +++ b/src/interfaces/libpgtcl/README @@ -0,0 +1,7 @@ +libpgtcl is a library that implements Tcl commands for front-end +clients to interact with the Postgres95 backend. See libpgtcl.doc for +details. + +For an example of how to build a new tclsh to use libpgtcl, see the +directory ../bin/pgtclsh + diff --git a/src/interfaces/libpgtcl/libpgtcl.h b/src/interfaces/libpgtcl/libpgtcl.h new file mode 100644 index 00000000000..923bf594d70 --- /dev/null +++ b/src/interfaces/libpgtcl/libpgtcl.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * libpgtcl.h-- + * libpgtcl is a tcl package for front-ends to interface with pglite + * It's the tcl equivalent of the old libpq C interface. + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: libpgtcl.h,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef LIBPGTCL_H +#define LIBPGTCL_H + +#include "tcl.h" + +extern int Pg_Init (Tcl_Interp *interp); + +#endif /* LIBPGTCL_H */ diff --git a/src/interfaces/libpgtcl/pgtcl.c b/src/interfaces/libpgtcl/pgtcl.c new file mode 100644 index 00000000000..449107339fe --- /dev/null +++ b/src/interfaces/libpgtcl/pgtcl.c @@ -0,0 +1,105 @@ +/*------------------------------------------------------------------------- + * + * pgtcl.c-- + * + * libpgtcl is a tcl package for front-ends to interface with pglite + * It's the tcl equivalent of the old libpq C interface. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtcl.c,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "tcl.h" +#include "libpgtcl.h" +#include "pgtclCmds.h" + +/* + * PG_Init + * initialization package for the PGLITE Tcl package + * + */ + +int +Pg_Init (Tcl_Interp *interp) +{ + /* register all pgtcl commands */ + + Tcl_CreateCommand(interp, + "pg_connect", + Pg_connect, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_disconnect", + Pg_disconnect, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_exec", + Pg_exec, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_result", + Pg_result, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_open", + Pg_lo_open, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_close", + Pg_lo_close, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_read", + Pg_lo_read, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_write", + Pg_lo_write, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_lseek", + Pg_lo_lseek, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_creat", + Pg_lo_creat, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_tell", + Pg_lo_tell, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_unlink", + Pg_lo_unlink, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_import", + Pg_lo_import, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateCommand(interp, + "pg_lo_export", + Pg_lo_export, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + return TCL_OK; +} + + diff --git a/src/interfaces/libpgtcl/pgtclCmds.c b/src/interfaces/libpgtcl/pgtclCmds.c new file mode 100644 index 00000000000..e6c2c539118 --- /dev/null +++ b/src/interfaces/libpgtcl/pgtclCmds.c @@ -0,0 +1,812 @@ +/*------------------------------------------------------------------------- + * + * pgtclCmds.c-- + * C functions which implement pg_* tcl commands + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclCmds.c,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <tcl.h> +#include <string.h> +#include "libpq/pqcomm.h" +#include "libpq-fe.h" +#include "libpq/libpq-fs.h" +#include "pgtclCmds.h" +#include "pgtclId.h" + +/********************************** + * 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, char* argv[]) +{ + char *pghost = NULL; + char *pgtty = NULL; + char *pgport = NULL; + char *pgoptions = NULL; + 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]]", 0); + return TCL_ERROR; + + } + 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 : \n", + argv[i], 0); + Tcl_AppendResult(interp, "pg_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\n", argv[i],0); + Tcl_AppendResult(interp, "pg_connect databaseName [-host hostName] [-port portNumber] [-tty pgtty]]",0); + return TCL_ERROR; + } + } + dbName = argv[1]; + + conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName); + if (conn->status == CONNECTION_OK) { + PgSetId(interp->result, (void*)conn); + return TCL_OK; + } + else { + Tcl_AppendResult(interp, "Connection to ", dbName, " failed\n", 0); + Tcl_AppendResult(interp, conn->errorMessage, 0); + 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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + + if (argc != 2) { + Tcl_AppendResult(interp, "Wrong # of arguments\n", "pg_disconnect connection", 0); + return TCL_ERROR; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "First argument is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + PQfinish(conn); + return TCL_OK; +} + +/********************************** + * 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(AlientData cData, Tcl_Interp *interp, int argc, char* argv[]) +{ + PGconn *conn; + PGresult *result; + char* connPtrName; + + if (argc != 3) { + Tcl_AppendResult(interp, "Wrong # of arguments\n", + "pg_exec connection queryString", 0); + return TCL_ERROR; + } + connPtrName = argv[1]; + + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + result = PQexec(conn, argv[2]); + if (result) { + PgSetId(interp->result, (void*)result); + return TCL_OK; + } + else { + /* error occurred during the query */ + Tcl_SetResult(interp, conn->errorMessage, TCL_STATIC); + return TCL_ERROR; + } + /* check return status of result */ + return TCL_OK; +} + +/********************************** + * pg_result + get information about the results of a query + + syntax: + pg_result result ?option? + + the options are: + -status + the status of the result + -conn + the connection that produced the result + -assign arrayName + assign the results to an array + -numTuples + the number of tuples in the query + -attributes + returns a list of the name/type pairs of the tuple attributes + -getTuple tupleNumber + returns the values of the tuple in a list + -clear + clear the result buffer. Do not reuse after this + **********************************/ +int +Pg_result(ClientData cData, Tcl_Interp *interp, int argc, char* argv[]) +{ + char* resultPtrName; + PGresult *result; + char *opt; + int i; + int tupno; + char arrayInd[MAX_MESSAGE_LEN]; + char *arrVar; + + if (argc != 3 && argc != 4) { + Tcl_AppendResult(interp, "Wrong # of arguments\n",0); + goto Pg_result_errReturn; + } + + resultPtrName = argv[1]; + if (! PgValidId(resultPtrName)) { + Tcl_AppendResult(interp, "First argument is not a valid query result\n", 0); + return TCL_ERROR; + } + + result = (PGresult*)PgGetId(resultPtrName); + opt = argv[2]; + + if (strcmp(opt, "-status") == 0) { + Tcl_AppendResult(interp, pgresStatus[PQresultStatus(result)], 0); + return TCL_OK; + } + else if (strcmp(opt, "-oid") == 0) { + Tcl_AppendResult(interp, PQoidStatus(result), 0); + return TCL_OK; + } + else if (strcmp(opt, "-conn") == 0) { + PgSetId(interp->result, (void*)result->conn); + return TCL_OK; + } + else if (strcmp(opt, "-clear") == 0) { + PQclear(result); + return TCL_OK; + } + else if (strcmp(opt, "-numTuples") == 0) { + sprintf(interp->result, "%d", PQntuples(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 or (tupno,attrName)*/ + for (tupno = 0; tupno<PQntuples(result); tupno++) { + for (i=0;i<PQnfields(result);i++) { + sprintf(arrayInd, "%d,%s", tupno, PQfname(result,i)); + Tcl_SetVar2(interp, arrVar, arrayInd, + PQgetvalue(result,tupno,i), + TCL_LEAVE_ERR_MSG); + } + } + 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 >= PQntuples(result)) { + Tcl_AppendResult(interp, "argument to getTuple cannot exceed number of tuples - 1",0); + return TCL_ERROR; + } + +/* Tcl_AppendResult(interp, PQgetvalue(result,tupno,0),NULL); */ + Tcl_AppendElement(interp, PQgetvalue(result,tupno,0)); + for (i=1;i<PQnfields(result);i++) { +/* Tcl_AppendResult(interp, " ", PQgetvalue(result,tupno,i),NULL);*/ + Tcl_AppendElement(interp, PQgetvalue(result,tupno,i)); + } + return TCL_OK; + } + else if (strcmp(opt, "-attributes") == 0) { + Tcl_AppendResult(interp, PQfname(result,0),NULL); + for (i=1;i<PQnfields(result);i++) { + Tcl_AppendResult(interp, " ", PQfname(result,i), NULL); + } + return TCL_OK; + } + else { + Tcl_AppendResult(interp, "Invalid option",0); + goto Pg_result_errReturn; + } + + + Pg_result_errReturn: + Tcl_AppendResult(interp, + "pg_result result ?option? where ?option is\n", + "\t-status\n", + "\t-conn\n", + "\t-assign arrayVarName\n", + "\t-numTuples\n", + "\t-attributes\n" + "\t-getTuple tupleNumber\n", + "\t-clear\n", + "\t-oid\n", + 0); + return TCL_ERROR; + + +} + +/********************************** + * 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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + 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; + } + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + 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 = mode & INV_READ; + break; + case 'w': + case 'W': + mode = 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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + int fd; + + if (argc != 3) { + Tcl_AppendResult(interp, "Wrong # of arguments\n", + "pg_lo_close connection fd", 0); + return TCL_ERROR; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + 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 + +**********************/ +int +Pg_lo_read(ClientData cData, Tcl_Interp *interp, int argc, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + 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; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + fd = atoi(argv[2]); + + bufVar = argv[3]; + + len = atoi(argv[4]); + + if (len <= 0) { + sprintf(interp->result,"%d",nbytes); + return TCL_OK; + } + buf = malloc(sizeof(len+1)); + + nbytes = lo_read(conn,fd,buf,len); + + Tcl_SetVar(interp,bufVar,buf,TCL_LEAVE_ERR_MSG); + sprintf(interp->result,"%d",nbytes); + free(buf); + return TCL_OK; + +} + +/*********************************** +Pg_lo_write + write at most len bytes to a large object + + syntax: + pg_lo_write conn fd buf len + +***********************************/ +int +Pg_lo_write(ClientData cData, Tcl_Interp *interp, int argc, char* argv[]) +{ + PGconn *conn; + char *connPtrName; + 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; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + 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; +} + +/*********************************** +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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + int fd; + 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; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + 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, and INV_ARCHIVE, +for now, we don't support any additional storage managers. + +***********************************/ +int +Pg_lo_creat(ClientData cData, Tcl_Interp *interp, int argc, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + 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; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + + modeStr = 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 if (strcmp(modeWord,"INV_ARCHIVE") == 0) { + mode = INV_ARCHIVE; + } else { + Tcl_AppendResult(interp, + "invalid mode argument to Pg_lo_creat\nmode argument must be some OR'd combination of INV_READ, INV_WRITE, and INV_ARCHIVE", + 0); + 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 if (strcmp(modeWord,"INV_ARCHIVE") == 0) { + mode |= INV_ARCHIVE; + } else { + Tcl_AppendResult(interp, + "invalid mode argument to Pg_lo_creat\nmode argument must be some OR'd combination of INV_READ, INV_WRITE, and INV_ARCHIVE", + 0); + return TCL_ERROR; + } + } + sprintf(interp->result,"%d",lo_creat(conn,mode)); + 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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + int fd; + + if (argc != 3) { + Tcl_AppendResult(interp, "Wrong # of arguments\n", + "pg_lo_tell conn fd", 0); + return TCL_ERROR; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + 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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + int lobjId; + int retval; + + if (argc != 3) { + Tcl_AppendResult(interp, "Wrong # of arguments\n", + "pg_lo_tell conn fd", 0); + return TCL_ERROR; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + 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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + char* filename; + Oid lobjId; + + if (argc != 3) { + Tcl_AppendResult(interp, "Wrong # of arguments\n", + "pg_lo_import conn filename", 0); + return TCL_ERROR; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + filename = argv[2]; + + lobjId = lo_import(conn,filename); + if (lobjId == InvalidOid) { + sprintf(interp->result, "Pg_lo_import of '%s' failed",filename); + return TCL_ERROR; + } + sprintf(interp->result,"%d",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, char* argv[]) +{ + PGconn *conn; + char* connPtrName; + 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; + } + + connPtrName = argv[1]; + if (! PgValidId(connPtrName)) { + Tcl_AppendResult(interp, "Argument passed in is not a valid connection\n", 0); + return TCL_ERROR; + } + + conn = (PGconn*)PgGetId(connPtrName); + lobjId = atoi(argv[2]); + filename = argv[3]; + + retval = lo_export(conn,lobjId,filename); + if (retval == -1) { + sprintf(interp->result, "Pg_lo_export %d %s failed",lobjId, filename); + return TCL_ERROR; + } + return TCL_OK; +} + + diff --git a/src/interfaces/libpgtcl/pgtclCmds.h b/src/interfaces/libpgtcl/pgtclCmds.h new file mode 100644 index 00000000000..244471ebe1b --- /dev/null +++ b/src/interfaces/libpgtcl/pgtclCmds.h @@ -0,0 +1,52 @@ +/*------------------------------------------------------------------------- + * + * pgtclCmds.h-- + * declarations for the C functions which implement pg_* tcl commands + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pgtclCmds.h,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef PGTCLCMDS_H +#define PGTCLCMDS_H + +#include "tcl.h" + +/* **************************/ +/* registered Tcl functions */ +/* **************************/ +extern int Pg_connect( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_disconnect( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_exec( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_result( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_open( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_close( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_read( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_write( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_lseek( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_creat( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_tell( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_unlink( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_import( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); +extern int Pg_lo_export( + ClientData cData, Tcl_Interp *interp, int argc, char* argv[]); + + +#endif /*PGTCLCMDS_H*/ + diff --git a/src/interfaces/libpgtcl/pgtclId.c b/src/interfaces/libpgtcl/pgtclId.c new file mode 100644 index 00000000000..00dffe7a883 --- /dev/null +++ b/src/interfaces/libpgtcl/pgtclId.c @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * 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 + * + * 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.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <stdlib.h> +#include <string.h> +#include "tcl.h" + +#include "pgtclId.h" + +/* convert a pointer into a string */ +void +PgSetId(char *id, void *ptr) +{ + (void) sprintf(id, "pgp%lx", (long) ptr); +} + + +/* get back a pointer from a string */ +void * +PgGetId(char *id) +{ + long ptr; + ptr = strtol(id+3, NULL, 16); + return (void *) ptr; +} + +/* check to see if the string is a valid pgtcl pointer */ +int +PgValidId(char* id) +{ + if ( (strlen(id) > 3) && id[0]=='p' && id[1] == 'g' && id[2] == 'p') + return 1; + else + return 0; +} diff --git a/src/interfaces/libpgtcl/pgtclId.h b/src/interfaces/libpgtcl/pgtclId.h new file mode 100644 index 00000000000..af9839ceb1e --- /dev/null +++ b/src/interfaces/libpgtcl/pgtclId.h @@ -0,0 +1,18 @@ +/*------------------------------------------------------------------------- + * + * pgtclId.h-- + * useful routines to convert between strings and pointers + * Needed because everything in tcl is a string, but often, pointers + * to data structures are needed. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pgtclId.h,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +extern void PgSetId(char *id, void *ptr); +extern void* PgGetId(char *id); +extern int PgValidId(char* id); diff --git a/src/interfaces/libpq++/Makefile b/src/interfaces/libpq++/Makefile new file mode 100644 index 00000000000..e1d58847ee5 --- /dev/null +++ b/src/interfaces/libpq++/Makefile @@ -0,0 +1,54 @@ +#------------------------------------------------------------------------- +# +# Makefile +# Makefile for libpq++ library +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/interfaces/libpq++/Attic/Makefile,v 1.1.1.1 1996/07/09 06:22:18 scrappy Exp $ +# +#------------------------------------------------------------------------- + +CPP_LIB= true + +LIB= pq++ + +MKDIR= ../mk +include $(MKDIR)/postgres.mk + +CXXFLAGS = $(CFLAGS) + +CXXFLAGS+= -I$(srcdir)/backend/include \ + -I$(srcdir)/backend \ + -I$(srcdir)/libpq \ + -I$(CURDIR) \ + +ifdef KRBVERS +CXXFLAGS+= $(KRBFLAGS) +endif + + +LIBSRCS = pgenv.cc pgconnection.cc pglobject.cc + +.PHONY: beforeinstall-headers install-headers + +ifndef NO_BEFOREINSTL +beforeinstall-headers: + @-if [ ! -d $(HEADERDIR) ]; then mkdir $(HEADERDIR); fi +else +beforeinstall-headers: .dosomething +endif + +HEADERFILES = libpq++.H + +install-headers: beforeinstall-headers + @for i in ${HEADERFILES}; do \ + echo "Installing $(HEADERDIR)/$$i."; \ + $(INSTALL) -c -m 444 $$i $(HEADERDIR)/$$i; \ + done + +install:: install-headers + +include $(MKDIR)/postgres.lib.mk diff --git a/src/interfaces/libpq++/README b/src/interfaces/libpq++/README new file mode 100644 index 00000000000..cb5d0aeddb1 --- /dev/null +++ b/src/interfaces/libpq++/README @@ -0,0 +1,22 @@ +This directory contains libpq++, the C++ language interface to POSTGRES95. +libpq++ is implemented on of the libpq library. Users would benefit +from reading the chapter on libpq in the postgres95 users manual +before using libpq++. + +The initial version of this implementation was done by William Wanders +(wwanders@sci.kun.nl) + +This is only a preliminary attempt at providing something useful for +people who would like to use C++ to build frontend applications to +postgres95. The API provided herein is subject to change in later +versions of postgres95. + +For details on how to to use libpq++, see the man page in the man/ +subdirectory and the test programs in the examples/ subdirectory. + +libpq++ has been tested with g++, version 2.7.0 + +- Jolly Chen +jolly@cs.berkeley.edu + +Tue Sep 5 11:09:51 PDT 1995 diff --git a/src/interfaces/libpq++/examples/Makefile b/src/interfaces/libpq++/examples/Makefile new file mode 100644 index 00000000000..5e51d915876 --- /dev/null +++ b/src/interfaces/libpq++/examples/Makefile @@ -0,0 +1,70 @@ +# +# Makefile for example programs +# + +CPP_PROG = true + +MKDIR= ../../mk +include $(MKDIR)/postgres.mk + +CXXFLAGS+= -I$(HEADERDIR) -I$(srcdir)/libpq -I$(srcdir)/backend \ + -I$(srcdir)/backend/include + +LD_ADD+=-L$(LIBDIR) -lpq++ -lpq + +# +# And where libpq goes, so goes the authentication stuff... +# +ifdef KRBVERS +LD_ADD+= $(KRBLIBS) +CXXFLAGS+= $(KRBFLAGS) +endif + +P0_PROG:= testlibpq0 +P0_OBJS:= testlibpq0.o + +$(P0_PROG): $(addprefix $(objdir)/,$(P0_OBJS)) + $(CXX) $(CDEBUG) -o $(objdir)/$(@F) $< $(LD_ADD) + +P1_PROG:= testlibpq1 +P1_OBJS:= testlibpq1.o + +$(P1_PROG): $(addprefix $(objdir)/,$(P1_OBJS)) + $(CXX) $(CDEBUG) -o $(objdir)/$(@F) $< $(LD_ADD) + +P2_PROG:= testlibpq2 +P2_OBJS:= testlibpq2.o + +$(P2_PROG): $(addprefix $(objdir)/,$(P2_OBJS)) + $(CXX) $(CDEBUG) -o $(objdir)/$(@F) $< $(LD_ADD) + +P3_PROG:= testlibpq3 +P3_OBJS:= testlibpq3.o + +$(P3_PROG): $(addprefix $(objdir)/,$(P3_OBJS)) + $(CXX) $(CDEBUG) -o $(objdir)/$(@F) $< $(LD_ADD) + +P4_PROG:= testlibpq4 +P4_OBJS:= testlibpq4.o + +$(P4_PROG): $(addprefix $(objdir)/,$(P4_OBJS)) + $(CXX) $(CDEBUG) -o $(objdir)/$(@F) $< $(LD_ADD) + +P5_PROG:= testlo +P5_OBJS:= testlo.o + +$(P5_PROG): $(addprefix $(objdir)/,$(P5_OBJS)) + $(CXX) $(CDEBUG) -o $(objdir)/$(@F) $< $(LD_ADD) + +OBJS:= $(P0_OBJS) $(P1_OBJS) $(P2_OBJS) $(P3_OBJS) $(P4_OBJS) $(P5_OBJS) +PROGS:= $(P0_PROG) $(P1_PROG) $(P2_PROG) $(P3_PROG) $(P4_PROG) $(P5_PROG) + +CLEANFILES+= $(OBJS) $(PROGS) + +all:: $(PROGS) + +install:: $(PROGS) + @for i in ${PROGS}; do \ + echo "Installing $$i"; \ + $(INSTALL) $(objdir)/$$i $(DESTDIR)$(BINDIR)/$$i;\ + done diff --git a/src/interfaces/libpq++/examples/testlibpq0.cc b/src/interfaces/libpq++/examples/testlibpq0.cc new file mode 100644 index 00000000000..76f3ea80712 --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq0.cc @@ -0,0 +1,49 @@ +/*------------------------------------------------------------------------- + * + * testlibpq0.c-- + * small test program for libpq++, + * small interactive loop where queries can be entered interactively + * and sent to the backend + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq++/examples/Attic/testlibpq0.cc,v 1.1.1.1 1996/07/09 06:22:18 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <stdio.h> +#include "libpq++.H" + +int +main(int argc, char** argv) +{ + ExecStatusType status; + PGenv env; + PGdatabase* data; + + char buf[10000]; + int done = 0; + + data = new PGdatabase(&env, "template1"); + + if (data->status() == CONNECTION_BAD) + printf("connection was unsuccessful\n%s\n", data->errormessage()); + else + printf("connection successful\n"); + + while (!done) + { + printf("> ");fflush(stdout); + if (gets(buf) && buf[0]!='\0') + if((status = data->exec(buf)) == PGRES_TUPLES_OK) + data->printtuples(stdout, 1, "|", 1, 0); + else + printf("status = %s\nerrorMessage = %s\n", status, + data->errormessage()); + else + done = 1; + } +} diff --git a/src/interfaces/libpq++/examples/testlibpq1.cc b/src/interfaces/libpq++/examples/testlibpq1.cc new file mode 100644 index 00000000000..1d71f795583 --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq1.cc @@ -0,0 +1,84 @@ +/* + * testlibpq.cc + * Test the C++ version of LIBPQ, the POSTGRES frontend library. + * + * queries the template1 database for a list of database names + * + */ +#include <stdio.h> +#include "libpq++.H" + +main() +{ + char* dbName; + int nFields; + int i,j; + + /* begin, by creating the parameter environtment for a backend + connection. When no parameters are given then the system will + try to use reasonable defaults by looking up environment variables + or, failing that, using hardwired constants */ + PGenv env; + PGdatabase* data; + + /* Select a database */ + dbName = "template1"; + + /* make a connection to the database */ + data = new PGdatabase(&env, dbName); + + /* check to see that the backend connection was successfully made */ + if (data->status() == CONNECTION_BAD) { + fprintf(stderr,"Connection to database '%s' failed.\n", dbName); + fprintf(stderr,"%s",data->errormessage()); + delete data; + exit(1); + } + + /* start a transaction block */ + if(data->exec("BEGIN") != PGRES_COMMAND_OK) { + fprintf(stderr,"BEGIN command failed\n"); + delete data; + exit(1); + } + + /* fetch instances from the pg_database, the system catalog of databases*/ + if (data->exec("DECLARE myportal CURSOR FOR select * from pg_database") + != PGRES_COMMAND_OK) { + fprintf(stderr,"DECLARE CURSOR command failed\n"); + delete data; + exit(1); + } + + if(data->exec("FETCH ALL in myportal") != PGRES_TUPLES_OK) { + fprintf(stderr,"FETCH ALL command didn't return tuples properly\n"); + delete data; + exit(1); + } + + /* first, print out the attribute names */ + nFields = data->nfields(); + for (i=0; i < nFields; i++) { + printf("%-15s",data->fieldname(i)); + } + printf("\n\n"); + + /* next, print out the instances */ + for (i=0; i < data->ntuples(); i++) { + for (j=0 ; j < nFields; j++) { + printf("%-15s", data->getvalue(i,j)); + } + printf("\n"); + } + + /* close the portal */ + data->exec("CLOSE myportal"); + + /* end the transaction */ + data->exec("END"); + + /* close the connection to the database and cleanup */ + delete data; +} + + diff --git a/src/interfaces/libpq++/examples/testlibpq2.cc b/src/interfaces/libpq++/examples/testlibpq2.cc new file mode 100644 index 00000000000..8eafea00b7a --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq2.cc @@ -0,0 +1,71 @@ +/* + * testlibpq2.cc + * Test of the asynchronous notification interface + * + populate a database with the following: + +CREATE TABLE TBL1 (i int4); + +CREATE TABLE TBL2 (i int4); + +CREATE RULE r1 AS ON INSERT TO TBL1 DO [INSERT INTO TBL2 values (new.i); NOTIFY TBL2]; + + * Then start up this program + * After the program has begun, do + +INSERT INTO TBL1 values (10); + + * + * + */ +#include <stdio.h> +#include "libpq++.H" + +main() +{ + char* dbName; + int nFields; + int i,j; + + /* begin, by creating the parameter environtment for a backend + connection. When no parameters are given then the system will + try to use reasonable defaults by looking up environment variables + or, failing that, using hardwired constants */ + PGenv env; + PGdatabase* data; + PGnotify* notify; + + dbName = getenv("USER"); /* change this to the name of your test database */ + + /* make a connection to the database */ + data = new PGdatabase(&env, dbName); + + /* check to see that the backend connection was successfully made */ + if (data->status() == CONNECTION_BAD) { + fprintf(stderr,"Connection to database '%s' failed.\n", dbName); + fprintf(stderr,"%s",data->errormessage()); + delete data; + exit(1); + } + + if (data->exec("LISTEN TBL2") != PGRES_COMMAND_OK) { + fprintf(stderr,"LISTEN command failed\n"); + delete data; + exit(1); + } + + while (1) { + /* check for asynchronous returns */ + notify = data->notifies(); + if (notify) { + fprintf(stderr, + "ASYNC NOTIFY of '%s' from backend pid '%d' received\n", + notify->relname, notify->be_pid); + free(notify); + break; + } + } + + /* close the connection to the database and cleanup */ + delete data; +} diff --git a/src/interfaces/libpq++/examples/testlibpq2.sql b/src/interfaces/libpq++/examples/testlibpq2.sql new file mode 100644 index 00000000000..f9c74109328 --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq2.sql @@ -0,0 +1,5 @@ +CREATE TABLE TBL1 (i int4); + +CREATE TABLE TBL2 (i int4); + +CREATE RULE r1 AS ON INSERT TO TBL1 DO [INSERT INTO TBL2 values (new.i); NOTIFY TBL2]; diff --git a/src/interfaces/libpq++/examples/testlibpq3.cc b/src/interfaces/libpq++/examples/testlibpq3.cc new file mode 100644 index 00000000000..1146dffac98 --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq3.cc @@ -0,0 +1,131 @@ +/* + * testlibpq3.cc + * Test the C++ version of LIBPQ, the POSTGRES frontend library. + * tests the binary cursor interface + * + * + * + populate a database by doing the following: + +CREATE TABLE test1 (i int4, d float4, p polygon); + +INSERT INTO test1 values (1, 3.567, '(3.0, 4.0, 1.0, 2.0)'::polygon); + +INSERT INTO test1 values (2, 89.05, '(4.0, 3.0, 2.0, 1.0)'::polygon); + + the expected output is: + +tuple 0: got + i = (4 bytes) 1, + d = (4 bytes) 3.567000, + p = (4 bytes) 2 points boundbox = (hi=3.000000/4.000000, lo = 1.000000,2.000000) +tuple 1: got + i = (4 bytes) 2, + d = (4 bytes) 89.050003, + p = (4 bytes) 2 points boundbox = (hi=4.000000/3.000000, lo = 2.000000,1.000000) + + * + */ +#include <stdio.h> +#include "libpq++.H" +extern "C" { +#include "utils/geo-decls.h" /* for the POLYGON type */ +} + +main() +{ + char* dbName; + int nFields; + int i,j; + int i_fnum, d_fnum, p_fnum; + + /* begin, by creating the parameter environtment for a backend + connection. When no parameters are given then the system will + try to use reasonable defaults by looking up environment variables + or, failing that, using hardwired constants */ + PGenv env; + PGdatabase* data; + + dbName = getenv("USER"); /* change this to the name of your test database */ + + /* make a connection to the database */ + data = new PGdatabase(&env, dbName); + + /* check to see that the backend connection was successfully made */ + if (data->status() == CONNECTION_BAD) { + fprintf(stderr,"Connection to database '%s' failed.\n", dbName); + fprintf(stderr,"%s",data->errormessage()); + delete data; + exit(1); + } + + /* start a transaction block */ + if (data->exec("BEGIN") != PGRES_COMMAND_OK) { + fprintf(stderr,"BEGIN command failed\n"); + delete data; + exit(1); + } + + /* fetch instances from the pg_database, the system catalog of databases*/ + if (data->exec("DECLARE mycursor BINARY CURSOR FOR select * from test1") + != PGRES_COMMAND_OK) { + fprintf(stderr,"DECLARE CURSOR command failed\n"); + delete data; + exit(1); + } + + if (data->exec("FETCH ALL in mycursor") != PGRES_TUPLES_OK) { + fprintf(stderr,"FETCH ALL command didn't return tuples properly\n"); + delete data; + exit(1); + } + + i_fnum = data->fieldnum("i"); + d_fnum = data->fieldnum("d"); + p_fnum = data->fieldnum("p"); + +/* + for (i=0;i<3;i++) { + printf("type[%d] = %d, size[%d] = %d\n", + i, data->fieldtype(i), + i, data->fieldsize(i)); + } +*/ + + for (i=0; i < data->ntuples(); i++) { + int *ival; + float *dval; + int plen; + POLYGON* pval; + /* we hard-wire this to the 3 fields we know about */ + ival = (int*)data->getvalue(i,i_fnum); + dval = (float*)data->getvalue(i,d_fnum); + plen = data->getlength(i,p_fnum); + + /* plen doesn't include the length field so need to increment by VARHDSZ*/ + pval = (POLYGON*) malloc(plen + VARHDRSZ); + pval->size = plen; + memmove((char*)&pval->npts, data->getvalue(i,p_fnum), plen); + printf("tuple %d: got\n", i); + printf(" i = (%d bytes) %d,\n", + data->getlength(i,i_fnum), *ival); + printf(" d = (%d bytes) %f,\n", + data->getlength(i,d_fnum), *dval); + printf(" p = (%d bytes) %d points \tboundbox = (hi=%f/%f, lo = %f,%f)\n", + data->getlength(i,d_fnum), + pval->npts, + pval->boundbox.xh, + pval->boundbox.yh, + pval->boundbox.xl, + pval->boundbox.yl); + } + + /* close the portal */ + data->exec("CLOSE mycursor"); + + /* end the transaction */ + data->exec("END"); + + /* close the connection to the database and cleanup */ + delete data; +} diff --git a/src/interfaces/libpq++/examples/testlibpq3.sql b/src/interfaces/libpq++/examples/testlibpq3.sql new file mode 100644 index 00000000000..f024c0b071b --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq3.sql @@ -0,0 +1,6 @@ +CREATE TABLE test1 (i int4, d float4, p polygon); + +INSERT INTO test1 values (1, 3.567, '(3.0, 4.0, 1.0, 2.0)'::polygon); + +INSERT INTO test1 values (2, 89.05, '(4.0, 3.0, 2.0, 1.0)'::polygon); + diff --git a/src/interfaces/libpq++/examples/testlibpq4.cc b/src/interfaces/libpq++/examples/testlibpq4.cc new file mode 100644 index 00000000000..9d5ca3ec769 --- /dev/null +++ b/src/interfaces/libpq++/examples/testlibpq4.cc @@ -0,0 +1,69 @@ +/* + * testlibpq4.cc + * Test the C++ version of LIBPQ, the POSTGRES frontend library. + * tests the copy in features + * + */ +#include <stdio.h> +#include "libpq++.H" + +#define DEBUG printf("Got here %d\n", __LINE__); +main() +{ + char* dbName; + int nFields; + int i,j; + + /* begin, by creating the parameter environment for a backend + connection. When no parameters are given then the system will + try to use reasonable defaults by looking up environment variables + or, failing that, using hardwired constants */ + PGenv env; + PGdatabase* data; + + dbName = getenv("USER"); /* change this to the name of your test database */ + + /* make a connection to the database */ + data = new PGdatabase(&env, dbName); + + /* check to see that the backend connection was successfully made */ + if (data->status() == CONNECTION_BAD) { + fprintf(stderr,"Connection to database '%s' failed.\n", dbName); + fprintf(stderr,"%s",data->errormessage()); + delete data; + exit(1); + } + + /* start a transaction block */ + if(data->exec("BEGIN") != PGRES_COMMAND_OK) { + fprintf(stderr,"BEGIN command failed\n"); + delete data; + exit(1); + } + + if (data->exec("CREATE TABLE foo (a int4, b char16, d float8)") != + PGRES_COMMAND_OK) { + fprintf(stderr,"CREATE TABLE foo command failed\n"); + delete data; + exit(1); + } + + if (data->exec("COPY foo FROM STDIN") != PGRES_COMMAND_OK) { + fprintf(stderr,"COPY foo FROM STDIN\n"); + delete data; + exit(1); + } + + data->putline("3\thello world\t4.5\n"); + data->putline("4\tgoodbye word\t7.11\n"); + data->putline(".\n"); + data->endcopy(); + data->exec("SELECT * FROM foo"); + data->printtuples(stdout,1,"|",1,0); + data->exec("DROP TABLE foo"); + // end the transaction + data->exec("END"); + + // close the connection to the database and cleanup + delete data; +} diff --git a/src/interfaces/libpq++/examples/testlo.cc b/src/interfaces/libpq++/examples/testlo.cc new file mode 100644 index 00000000000..be3459794f9 --- /dev/null +++ b/src/interfaces/libpq++/examples/testlo.cc @@ -0,0 +1,63 @@ +/*------------------------------------------------------------------------- + * + * lotest.cc-- + * test using large objects with libpq + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq++/examples/Attic/testlo.cc,v 1.1.1.1 1996/07/09 06:22:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include "libpq++.H" +extern "C" { +#include "libpq/libpq-fs.h" +} + +int +main(int argc, char **argv) +{ + char *in_filename, *out_filename; + char *database; + Oid lobjOid; + PGenv env; + PGlobj *object; + + if (argc < 4 || argc > 5) { + fprintf(stderr, "Usage: %s database_name in_filename out_filename [oid]\n", + argv[0]); + exit(1); + } + + database = argv[1]; + in_filename = argv[2]; + out_filename = argv[3]; + + /* + * set up the connection and create a largeobject for us + */ + if (argc == 4) { + object = new PGlobj(&env, database); + } else { + object = new PGlobj(&env, database, atoi(argv[4])); + } + + /* check to see that the backend connection was successfully made */ + if (object->status() == CONNECTION_BAD) { + fprintf(stderr,"Connection to database '%s' failed.\n", database); + fprintf(stderr,"%s",object->errormessage()); + delete object; + exit(1); + } + + object->exec("BEGIN"); + printf("importing file \"%s\" ...\n", in_filename); + object->import(in_filename); + printf("exporting large object to file \"%s\" ...\n", out_filename); + object->export(out_filename); + object->exec("END"); // WHY DOES IT CORE DUMP HERE ??? + delete object; +} diff --git a/src/interfaces/libpq++/libpq++.H b/src/interfaces/libpq++/libpq++.H new file mode 100644 index 00000000000..9b3e1739007 --- /dev/null +++ b/src/interfaces/libpq++/libpq++.H @@ -0,0 +1,173 @@ +/*------------------------------------------------------------------------- + * + * libpq++.H + * + * + * DESCRIPTION + * C++ client interface to Postgres + * used for building front-end applications + * + * NOTES + * Currently under construction. + * + * Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * + * $Id: libpq++.H,v 1.1.1.1 1996/07/09 06:22:18 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef LIBPQXX_H +#define LIBPQXX_H + +#include <stdio.h> +#include <strings.h> + +extern "C" { +#include "libpq-fe.h" +#include "fe-auth.h" +} + +// **************************************************************** +// +// PGenv - the environment for setting up a connection to postgres +// +// **************************************************************** +class PGenv { + friend class PGconnection; + char* pgauth; + char* pghost; + char* pgport; + char* pgoption; + char* pgtty; +public: + PGenv(); // default ctor will use reasonable defaults + // will use environment variables PGHOST, PGPORT, + // PGOPTION, PGTTY + PGenv(char* auth, char* host, char* port, char* option, char* tty); + void setValues(char* auth, char* host, char* port, char* option, char* tty); + ~PGenv(); +}; + +// **************************************************************** +// +// PGconnection - a connection made to a postgres backend +// +// **************************************************************** +class PGconnection { + friend class PGdatabase; + friend class PGlobj; + PGenv* env; + PGconn* conn; + PGresult* result; + + char errorMessage[ERROR_MSG_LENGTH]; +public: + PGconnection(); // use reasonable defaults + PGconnection(PGenv* env, char* dbName); // connect to the database with + // given environment and database name + ConnStatusType status(); + char* errormessage() {return errorMessage;}; + + // returns the database name of the connection + char* dbName() {return PQdb(conn);}; + + ExecStatusType exec(char* query); // send a query to the backend + PGnotify* notifies() {exec(" "); return PQnotifies(conn);}; + ~PGconnection(); // close connection and clean up +protected: + ConnStatusType connect(PGenv* env, char* dbName); +}; + +// **************************************************************** +// +// PGdatabase - a class for accessing databases +// +// **************************************************************** +class PGdatabase : public PGconnection { +public: + PGdatabase() : PGconnection() {}; // use reasonable defaults + // connect to the database with + PGdatabase(PGenv* env, char* dbName) : PGconnection(env, dbName) {}; + // query result access + int ntuples() + {return PQntuples(result);}; + int nfields() + {return PQnfields(result);}; + char* fieldname(int field_num) + {return PQfname(result, field_num);}; + int fieldnum(char* field_name) + {return PQfnumber(result, field_name);}; + Oid fieldtype(int field_num) + {return PQftype(result, field_num);}; + Oid fieldtype(char* field_name) + {return PQftype(result, fieldnum(field_name));}; + int2 fieldsize(int field_num) + {return PQfsize(result, field_num);}; + int2 fieldsize(char* field_name) + {return PQfsize(result, fieldnum(field_name));}; + char* getvalue(int tup_num, int field_num) + {return PQgetvalue(result, tup_num, field_num);}; + char* getvalue(int tup_num, char* field_name) + {return PQgetvalue(result, tup_num, fieldnum(field_name));}; + int getlength(int tup_num, int field_num) + {return PQgetlength(result, tup_num, field_num);}; + int getlength(int tup_num, char* field_name) + {return PQgetlength(result, tup_num, fieldnum(field_name));}; + void printtuples(FILE *out, int fillAlign, char *fieldSep, + int printHeader, int quiet) + {PQdisplayTuples(result, out, fillAlign, fieldSep, printHeader, quiet);}; + // copy command related access + int getline(char* string, int length) + {return PQgetline(conn, string, length);}; + void putline(char* string) + {PQputline(conn, string);}; + int endcopy() + {return PQendcopy(conn);}; + ~PGdatabase() {}; // close connection and clean up +}; + +// **************************************************************** +// +// PGlobj - a class for accessing Large Object in a database +// +// **************************************************************** +class PGlobj : public PGconnection { + int fd; + Oid object; +public: + PGlobj(); // use reasonable defaults and create large object + PGlobj(Oid lobjId); // use reasonable defaults and open large object + PGlobj(PGenv* env, char* dbName); // create large object + PGlobj(PGenv* env, char* dbName, Oid lobjId); // open large object + int read(char* buf, int len) + {return lo_read(conn, fd, buf, len);}; + int write(char* buf, int len) + {return lo_write(conn, fd, buf, len);}; + int lseek(int offset, int whence) + {return lo_lseek(conn, fd, offset, whence);}; + int tell() + {return lo_tell(conn, fd);}; + int unlink(); + int import(char* filename); + int export(char* filename); + ~PGlobj(); // close connection and clean up +}; + +// +// these are the environment variables used for getting defaults +// + +#define ENV_DEFAULT_AUTH "PGAUTH" +#define ENV_DEFAULT_DBASE "PGDATABASE" +#define ENV_DEFAULT_HOST "PGHOST" +#define ENV_DEFAULT_OPTION "PGOPTION" +#define ENV_DEFAULT_PORT "PGPORT" +#define ENV_DEFAULT_TTY "PGTTY" + +// buffer size +#define BUFSIZE 1024 + +#endif /* LIBPQXX_H */ diff --git a/src/interfaces/libpq++/man/libpq++.3 b/src/interfaces/libpq++/man/libpq++.3 new file mode 100644 index 00000000000..ebca7ab7bb1 --- /dev/null +++ b/src/interfaces/libpq++/man/libpq++.3 @@ -0,0 +1,434 @@ +.\" +.\" POSTGRES95 Data Base Management System +.\" +.\" Copyright (c) 1994-5 Regents of the University of California +.\" +.\" POSTGRES Data Base Management System +.\" Copyright (c) 1988,1994 Regents of the University of California +.\" +.\" Permission to use, copy, modify, and distribute this software and its +.\" documentation for any purpose, without fee, and without a written agreement +.\" is hereby granted, provided that the above copyright notice and this +.\" paragraph and the following two paragraphs appear in all copies. +.\" +.\" IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +.\" DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING +.\" LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +.\" DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.\" THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +.\" ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO +.\" PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +.\" +.\" +.\" $Id: libpq++.3,v 1.1.1.1 1996/07/09 06:22:19 scrappy Exp $ +.\" +.\" ------------------------------------------------------------------ +.\" .(l, .)l +.\" fake "-me"-style lists +.de (l +.nf +.ie '\\$1'M' .in +0n +.el .in +5n +.. +.de )l +.fi +.in +.. +.\" .(C, .)C +.\" constant-width font blocks +.de (C +.ft C +.(b +.(l \\$1 +.sp +.. +.de )C +.sp +.)l +.)b +.ft R +.. +.\" ------------------------------------------------------------------ +.de SE +.nr si 0 +.nr so 0 +.nr $0 0 +.nr $i \\n(si*\\n($0 +.in \\n($i+\\n(po +.. +.\" ------------------------------------------------------------------ +.de SP +.he '\fB\\$1 (\\$2)'\\$3'\\$1 (\\$2)\fR' +.. +.\" ------------------------------------------------------------------ +.de SS +.PP +.B \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 +.PP +.. +.\" ------------------------------------------------------------------ +.SB +.ds II \s-1INGRES\s0 +.ds PG \s-1POSTGRES95\s0 +.ds UU \s-1UNIX\s0 +.ds PQ \s-1POSTQUEL\s0 +.ds LI \s-1LIBPQ++\s0 +.ds PV 4.2 +.SB +.TH INTRODUCTION LIBPQ++ 07/24/95 +.XA 0 "Libpq++" +.BH "LIBPQ++" +.SH DESCRIPTION +\*(LI is the C++ API to \*(PG. \*(LI is a set of classes which allow +client programs to connect to the \*(PG backend server. These connections +come in two forms: a Database Class and a Large Object class. +.PP +The Database Class is intended for manipulating a database. You can +send all sorts of SQL queries to the \*(PG backend server and retrieve +the responses of the server. +.PP +The Large Object Class is intended for manipulating a large object +in a database. Although a Large Object instance can send normal +queries to the \*(PG backend server it is only intended for simple +queries that do not return any data. A large object should be seen +as a file stream. In future it should behave much like the C++ file +streams +.IR cin , +.IR cout +and +.IR cerr . +This version of the documentation is based on the C library. Three +short programs are listed at the end of this section as examples of +\*(LI programming (though not necessarily of good programming). +.PP +There are several examples of \*(LI applications in the following +directory: +.(C +\&.../src/libpq++/examples +.)C +.XA 1 "Control and Initialization" +.SH "CONTROL AND INITIALIZATION" +.XA 2 "Environment Variables" +.SS "Environment Variables" +The following environment variables can be used to set up default +values for an environment and to avoid hard-coding database names into +an application program: +.TP 15n +.BR PGDATABASE +sets the default \*(PG database name. +.TP 15n +.BR PGHOST +sets the default server name. +.TP 15n +.BR PGOPTIONS +sets additional runtime options for the \*(PG backend. +.TP 15n +.BR PGPORT +sets the default communication port with the \*(PG backend. +.TP 15n +.BR PGTTY +sets the file or tty on which debugging messages from the backend server +are displayed. +.TP 15n +.BR PGREALM +sets the +.IR Kerberos +realm to use with \*(PG, if it is different from the local realm. If +.SM PGREALM +is set, \*(PG applications will attempt authentication with servers +for this realm and use separate ticket files to avoid conflicts with +local ticket files. This environment variable is only used if +.IR Kerberos +authentication is enabled. +.TP 15n +.BR PGAUTH +sets the type of authentication which should be used. Currently +only +.IR unauth , +.IR krb4 , +and +.IR krb5 . +are supported. Depending on whether you compiled in support for those. +.XA 1 "Database Connection Functions" +.SH "DATABASE ENVIRONMENT CLASS: PGenv" +The database environment class provides C++ objects for manipulating the +above environment variables. +.TP 15n +.BR PGenv +Create an environment for the running program. +.(C +PGenv() +PGenv(char* auth, char* host, char* port, char* option, char* tty) +.)C +The first form of this object's constructor sets up the defaults for +the program from the environment variables listed above. +The second allows the programmer to hardcode the values into the program. +The values of the second form relate directly to the environment variables +above. +.SH "DATABASE CLASS: PGdatabase" +The database class is a provides C++ objects that have a connection +to a backend server. To create such an object one first need +the apropriate environment for the backend to access. +The following constructors deal with making a connection to a backend +server from a C++ program. +.TP 15n +.BR PGdatabase +Make a new connection to a backend database server. +.(C +PGdatabase(PGenv *env, char *dbName); +.)C +After a PGdatabase has been created it should be checked to make sure +the connection to the database succeded before sending +queries to the object. This can easily be done by +retrieving the current status of the PGdatabase object with the +.IR status +command. +.BR PGdatabase::status +Returns the status of the PGdatabase object. + +.(C +ConnStatus PGdatabase::status() +.)C + +the following values are allowed + +.(C +CONNECTION_OK +CONNECTION_BAD +.)C + +.XA 1 "Query Execution Functions" +.SH "QUERY EXECUTION FUNCTIONS" +.TP 15n +.BR PGdatabase::exec +Submits a query to \*(PG and returns result status. In case of an error +.IR PGdatabase::errormessage +can be used to get more information on the error. +.(C +void +ExecStatusType PGdatabase::exec(char *query); +.)C +The following status results can be expected. +.(C +PGRES_EMPTY_QUERY, +PGRES_COMMAND_OK, /* the query was a command */ +PGRES_TUPLES_OK, /* the query successfully returned tuples */ +PGRES_COPY_OUT, +PGRES_COPY_IN, +PGRES_BAD_RESPONSE, /* an unexpected response was received */ +PGRES_NONFATAL_ERROR, +PGRES_FATAL_ERROR +.)C +.IP +If the result status is PGRES_TUPLES_OK, then the following routines can +be used to retrieve the tuples returned by the query. +.IP +.BR PGdatabase::ntuples +returns the number of tuples (instances) in the query result. +.(C +int PGdatabase::ntuples(); +.)C +.BR PGdatabase::nfields +returns the number of fields (attributes) in the query result. +.(C +int PGdatabase::nfields(); +.)C +.BR PGdatabase::fieldname +returns the field (attribute) name associated with the given field index. +Field indices start at 0. +.(C +char* PGdatabase::fieldname(int field_index); +.)C +.BR PGdatabase::fieldnum +returns the field (attribute) index associated with the given field name. +.(C +int PGdatabase::fieldnum(char* field_name); +.)C +.BR PGdatabase::fieldtype +returns the field type of associated with the given field index or name. +The integer returned is an internal coding of the type. Field indices start +at 0. +.(C +Oid PGdatabase::fieldtype(int field_index); +Oid PGdatabase::fieldtype(char* field_name); +.)C +.BR PGdatabase::fieldsize +returns the size in bytes of the field associated with the given field +index or name. If the size returned is -1, the field is a variable length +field. Field indices start at 0. +.(C +int2 PGdatabase::fieldsize(int field_index); +int2 PGdatabase::fieldsize(char* field_name); +.)C +.BR PGdatabase::getvalue +returns the field (attribute) value. For most queries, the values +returned by +.IR PGdatabase::getvalue +is a null-terminated ASCII string representation +of the attribute value. If the query was a result of a +.BR BINARY +cursor, then the values returned by +.IR PGdatabase::getvalue +is the binary representation of the type in the internal format of the +backend server. It is the programmer's responsibility to cast and +convert the data to the correct C++ type. The value return by +.IR PGdatabase::getvalue +points to storage that is part of the PGdatabase structure. One must +explicitly copy the value into other storage if it is to be used past +the next query. +.(C +char* PGdatabase::getvalue(int tup_num, int field_index); +char* PGdatabase::getvalue(int tup_num, char* field_name); +.)C +.BR PGdatabase::getlength +returns the length of a field (attribute) in bytes. If the field +is a +.IR "struct varlena" , +the length returned here does +.BR not +include the size field of the varlena, i.e., it is 4 bytes less. +.(C +int PGdatabase::getlength(int tup_num, int field_index); +int PGdatabase::getlength(int tup_num, char* field_name); +.)C +.BR PGdatabase::printtuples +prints out all the tuples and, optionally, the attribute names to the +specified output stream. +.(C +void PGdatabase::printtuples( + FILE* fout, /* output stream */ + int printAttName,/* print attribute names or not*/ + int terseOutput, /* delimiter bars or not?*/ + int width /* width of column, variable width if 0*/ + ); +.)C +.XA 1 "Asynchronous Notification" +.SH "ASYNCHRONOUS NOTIFICATION" +\*(PG supports asynchronous notification via the +.IR LISTEN +and +.IR NOTIFY +commands. A backend registers its interest in a particular relation +with the LISTEN command. All backends that are listening on a +particular relation will be notified asynchronously when a NOTIFY of +that relation name is executed by another backend. No additional +information is passed from the notifier to the listener. Thus, +typically, any actual data that needs to be communicated is transferred +through the relation. +.PP +\*(LI applications are notified whenever a connected backend has +received an asynchronous notification. However, the communication from +the backend to the frontend is not asynchronous. The \*(LI application +must poll the backend to see if there is any pending notification +information. After the execution of a query, a frontend may call +.IR PGdatabase::notifies +to see if any notification data is currently available from the backend. +.TP 15n +.BR PGdatabase::notifies +returns the notification from a list of unhandled notifications from the +backend. Returns NULL if there is no pending notifications from the +backend. +.IR PGdatabase::notifies +behaves like the popping of a stack. Once a notification is returned +from +.IR PGdatabase::notifies, +it is considered handled and will be removed from the list of +notifications. +.(C +PGnotify* PGdatabase::notifies() +.)C +.PP +The second sample program gives an example of the use of asynchronous +notification. +.XA 1 "Functions Associated with the COPY Command" +.SH "FUNCTIONS ASSOCIATED WITH THE COPY COMMAND" +The +.IR copy +command in \*(PG has options to read from or write to the network +connection used by \*(LI. Therefore, functions are necessary to +access this network connection directly so applications may take full +advantage of this capability. +.TP 15n +.BR PGdatabase::getline +Reads a newline-terminated line of characters (transmitted by the +backend server) into a buffer +.IR string +of size +.IR length . +Like +.IR fgets (3), +this routine copies up to +.IR length "-1" +characters into +.IR string . +It is like +.IR gets (3), +however, in that it converts the terminating newline into a null +character. +.IP +.IR PGdatabase::getline +returns EOF at EOF, 0 if the entire line has been read, and 1 if the +buffer is full but the terminating newline has not yet been read. +.IP +Notice that the application must check to see if a new line consists +of the single character \*(lq.\*(rq, which indicates that the backend +server has finished sending the results of the +.IR copy +command. Therefore, if the application ever expects to receive lines +that are more than +.IR length "-1" +characters long, the application must be sure to check the return +value of +.IR PGdatabase::getline +very carefully. +.IP +.(C +int PGdatabase::getline(char* string, int length) +.)C +.TP 15n +.BR PGdatabase::putline +Sends a null-terminated +.IR string +to the backend server. +.IP +The application must explicitly send the single character \*(lq.\*(rq +to indicate to the backend that it has finished sending its data. +.(C +void PGdatabase::putline(char* string) +.)C +.TP 15n +.BR PGdatabase::endcopy +Syncs with the backend. This function waits until the backend has +finished processing the copy. It should either be issued when the +last string has been sent to the backend using +.IR PGdatabase::putline +or when the last string has been received from the backend using +.IR PGdatabase::getline . +It must be issued or the backend may get \*(lqout of sync\*(rq with +the frontend. Upon return from this function, the backend is ready to +receive the next query. +.IP +The return value is 0 on successful completion, nonzero otherwise. +.(C +int PGdatabase::endcopy() +.)C +As an example: +.(C +PGdatabase data; +data.exec("create table foo (a int4, b char16, d float8)"); +data.exec("copy foo from stdin"); +data.putline("3\etHello World\et4.5\en"); +data.putline("4\etGoodbye World\et7.11\en"); +\&... +data.putline(".\en"); +data.endcopy(); +.)C +.SH BUGS +The query buffer is 8192 bytes long, and queries over that length will +be silently truncated. +.bp +The PGlobj class is largely untested. Use with caution. diff --git a/src/interfaces/libpq++/pgconnection.cc b/src/interfaces/libpq++/pgconnection.cc new file mode 100644 index 00000000000..777a12e8e7a --- /dev/null +++ b/src/interfaces/libpq++/pgconnection.cc @@ -0,0 +1,94 @@ +/*------------------------------------------------------------------------- + * + * FILE + * pgconnection.cc + * + * DESCRIPTION + * implementation of the PGconnection class. + * PGconnection encapsulates a frontend to backend connection + * + * Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq++/Attic/pgconnection.cc,v 1.1.1.1 1996/07/09 06:22:18 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "libpq++.H" + +// default constructor +// checks environment variable for database name +PGconnection::PGconnection() +{ + char* name; + PGenv* newenv; + + conn = NULL; + result = NULL; + errorMessage[0] = '\0'; + + newenv = new PGenv(); // use reasonable defaults for the environment + if (!(name = getenv(ENV_DEFAULT_DBASE))) + return; + connect(newenv, name); +} + +// constructor -- for given environment and database name +PGconnection::PGconnection(PGenv* env, char* dbName) +{ + conn = NULL; + result = NULL; + errorMessage[0] = '\0'; + connect(env, dbName); +} + +// destructor - closes down the connection and cleanup +PGconnection::~PGconnection() +{ + if (result) PQclear(result); + if (conn) PQfinish(conn); +} + +// PGconnection::connect +// establish a connection to a backend +ConnStatusType +PGconnection::connect(PGenv* newenv, char* dbName) +{ +#if 0 + FILE *debug; + debug = fopen("/tmp/trace.out","w"); + PQtrace(conn, debug); +#endif + + env = newenv; + fe_setauthsvc(env->pgauth, errorMessage); + conn = PQsetdb(env->pghost, env->pgport, env->pgoption, env->pgtty, dbName); + if(strlen(errorMessage)) + return CONNECTION_BAD; + else + return status(); +} + +// PGconnection::status -- return connection or result status +ConnStatusType +PGconnection::status() +{ + return PQstatus(conn); +} + +// PGconnection::exec -- send a query to the backend +ExecStatusType +PGconnection::exec(char* query) +{ + if (result) + PQclear(result); + + result = PQexec(conn, query); + if (result) + return PQresultStatus(result); + else { + strcpy(errorMessage, PQerrorMessage(conn)); + return PGRES_FATAL_ERROR; + } +} diff --git a/src/interfaces/libpq++/pgenv.cc b/src/interfaces/libpq++/pgenv.cc new file mode 100644 index 00000000000..aab4f213eb2 --- /dev/null +++ b/src/interfaces/libpq++/pgenv.cc @@ -0,0 +1,109 @@ +/*------------------------------------------------------------------------- + * + * FILE + * PGenv.cc + * + * DESCRIPTION + * PGenv is the environment for setting up a connection to a + * postgres backend, captures the host, port, tty, options and + * authentication type. + * + * NOTES + * Currently under construction. + * + * Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq++/Attic/pgenv.cc,v 1.1.1.1 1996/07/09 06:22:18 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <stdlib.h> +#include "libpq++.H" + +#define DefaultAuth DEFAULT_CLIENT_AUTHSVC +#define DefaultPort POSTPORT + +// default constructor for PGenv +// checks the environment variables +PGenv::PGenv() +{ + char* temp; + + pgauth = NULL; + pghost = NULL; + pgport = NULL; + pgoption = NULL; + pgtty = NULL; + + setValues(getenv(ENV_DEFAULT_AUTH), getenv(ENV_DEFAULT_HOST), + getenv(ENV_DEFAULT_PORT), getenv(ENV_DEFAULT_OPTION), + getenv(ENV_DEFAULT_TTY)); +} + +// constructor for given environment +PGenv::PGenv(char* auth, char* host, char* port, char* option, char* tty) +{ + pgauth = NULL; + pghost = NULL; + pgport = NULL; + pgoption = NULL; + pgtty = NULL; + + setValues(auth, host, port, option, tty); +} + +// allocate memory and set internal structures to match +// required environment +void +PGenv::setValues(char* auth, char* host, char* port, char* option, char* tty) +{ + char* temp; + + temp = (auth) ? auth : DefaultAuth; + + if (pgauth) + free(pgauth); + pgauth = strdup(temp); + + temp = (host) ? host : DefaultHost; + + if (pghost) + free(pghost); + pghost = strdup(temp); + + temp = (port) ? port : DefaultPort; + + if (pgport) + free(pgport); + pgport = strdup(temp); + + temp = (option) ? option : DefaultOption; + + if (pgoption) + free(pgoption); + pgoption = strdup(temp); + + temp = (tty) ? tty : DefaultTty; + + if (pgtty) + free(pgtty); + pgtty = strdup(temp); +} + +// default destrutor +// frees allocated memory for internal structures +PGenv::~PGenv() +{ + if (pgauth) + free(pgauth); + if (pghost) + free(pghost); + if (pgport) + free(pgport); + if (pgoption) + free(pgoption); + if (pgtty) + free(pgtty); +} diff --git a/src/interfaces/libpq++/pglobject.cc b/src/interfaces/libpq++/pglobject.cc new file mode 100644 index 00000000000..20c7ad5e22d --- /dev/null +++ b/src/interfaces/libpq++/pglobject.cc @@ -0,0 +1,152 @@ + +/*------------------------------------------------------------------------- + * + * FILE + * pglobject.cc + * + * DESCRIPTION + * implementation of the PGlobj class. + * PGlobj encapsulates a frontend to backend connection + * + * Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq++/Attic/pglobject.cc,v 1.1.1.1 1996/07/09 06:22:18 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "libpq++.H" + +extern "C" { +#include "libpq/libpq-fs.h" +} + +// default constructor +// creates a large object in the default database +PGlobj::PGlobj() : PGconnection::PGconnection() { + object = lo_creat(conn, INV_READ|INV_WRITE); + if (object == 0) { + sprintf(errorMessage, "PGlobj: can't create large object"); + } + fd = lo_open(conn, object, INV_READ|INV_WRITE); + if (fd < 0) { + sprintf(errorMessage, "PGlobj: can't open large object %d", object); + } else + sprintf(errorMessage, "PGlobj: created and opened large object %d", + object); + +} + +// constructor +// open an existing large object in the default database +PGlobj::PGlobj(Oid lobjId) : PGconnection::PGconnection() { + object = lobjId; + fd = lo_open(conn, object, INV_READ|INV_WRITE); + if (fd < 0) { + sprintf(errorMessage, "PGlobj: can't open large object %d", object); + } else + sprintf(errorMessage, "PGlobj: opened large object %d", + object); +} + +// constructor +// create a large object in the given database +PGlobj::PGlobj(PGenv* env, char* dbName) : PGconnection::PGconnection(env,dbName) { + object = lo_creat(conn, INV_READ|INV_WRITE); + if (object == 0) { + sprintf(errorMessage, "PGlobj: can't create large object"); + } + fd = lo_open(conn, object, INV_READ|INV_WRITE); + if (fd < 0) { + sprintf(errorMessage, "PGlobj: can't open large object %d", object); + } else + sprintf(errorMessage, "PGlobj: created and opened large object %d", + object); +} + +// constructor +// open an existing large object in the given database +PGlobj::PGlobj(PGenv* env, char* dbName, Oid lobjId) : PGconnection::PGconnection(env,dbName) { + object = lobjId; + fd = lo_open(conn, object, INV_READ|INV_WRITE); + if (fd < 0) { + sprintf(errorMessage, "PGlobj: can't open large object %d", object); + } else + sprintf(errorMessage, "PGlobj: created and opened large object %d", + object); +} + +// PGlobj::unlink +// destruct large object and delete from it from the database +int +PGlobj::unlink() { + int temp = lo_unlink(conn, object); + if (temp) { + return temp; + } else { + delete this; + return temp; + } +} + +// PGlobj::import -- import a given file into the large object +int +PGlobj::import(char* filename) { + char buf[BUFSIZE]; + int nbytes, tmp; + int in_fd; + + // open the file to be read in + in_fd = open(filename, O_RDONLY, 0666); + if (in_fd < 0) { /* error */ + sprintf(errorMessage, "PGlobj::import: can't open unix file\"%s\"", filename); + return -1; + } + + // read in from the Unix file and write to the inversion file + while ((nbytes = ::read(in_fd, buf, BUFSIZE)) > 0) { + tmp = lo_write(conn, fd, buf, nbytes); + if (tmp < nbytes) { + sprintf(errorMessage, "PGlobj::import: error while reading \"%s\"", + filename); + return -1; + } + } + + (void) close(in_fd); + return 0; +} + +// PGlobj::export -- export large object to given file +int +PGlobj::export(char* filename) { + int out_fd; + char buf[BUFSIZE]; + int nbytes, tmp; + + // open the file to be written to + out_fd = open(filename, O_CREAT|O_WRONLY, 0666); + if (out_fd < 0) { /* error */ + sprintf(errorMessage, "PGlobj::export: can't open unix file\"%s\"", + filename); + return -1; + } + + // read in from the Unix file and write to the inversion file + while ((nbytes = lo_read(conn, fd, buf, BUFSIZE)) > 0) { + tmp = ::write(out_fd, buf, nbytes); + if (tmp < nbytes) { + sprintf(errorMessage,"PGlobj::export: error while writing \"%s\"", + filename); + return -1; + } + } + (void) close(out_fd); + return 0; +} + +// default destructor -- closes large object +PGlobj::~PGlobj() { + if (fd >= 0) + lo_close(conn, fd); +} diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile new file mode 100644 index 00000000000..dc6318340ea --- /dev/null +++ b/src/interfaces/libpq/Makefile @@ -0,0 +1,98 @@ +#------------------------------------------------------------------------- +# +# Makefile +# Makefile for libpq library +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ +# +#------------------------------------------------------------------------- + +LIB= pq + +MKDIR= ../mk +include $(MKDIR)/postgres.mk + +CFLAGS+= -I$(srcdir)/backend/include \ + -I$(srcdir)/backend \ + -I$(CURDIR) \ + +ifdef KRBVERS +CFLAGS+= $(KRBFLAGS) +endif + +# dllist.c is found in backend/lib +VPATH:= $(VPATH):$(srcdir)/backend/lib + +LIBSRCS= fe-auth.c fe-connect.c fe-exec.c fe-misc.c fe-lobj.c \ + dllist.c pqsignal.c +ifeq ($(PORTNAME), next) +VPATH:=$(VPATH):$(srcdir)/backend/port/$(PORTNAME) +LIBSRCS+= getcwd.c putenv.c +endif + + +.PHONY: beforeinstall-headers install-headers + +ifndef NO_BEFOREINSTL +beforeinstall-headers: + @-if [ ! -d $(HEADERDIR) ]; then mkdir $(HEADERDIR); fi + @-if [ ! -d $(HEADERDIR)/port ]; then mkdir $(HEADERDIR)/port; fi + @-if [ ! -d $(HEADERDIR)/port/$(PORTNAME) ]; \ + then mkdir $(HEADERDIR)/port/$(PORTNAME); fi + @-if [ ! -d $(HEADERDIR)/include ]; \ + then mkdir $(HEADERDIR)/include; fi + @-if [ ! -d $(HEADERDIR)/lib ]; \ + then mkdir $(HEADERDIR)/lib; fi + @-if [ ! -d $(HEADERDIR)/libpq ]; \ + then mkdir $(HEADERDIR)/libpq; fi + @-if [ ! -d $(HEADERDIR)/utils ]; \ + then mkdir $(HEADERDIR)/utils; fi +else +beforeinstall-headers: .dosomething +endif + +HEADERFILES = include/postgres.h \ + libpq/pqcomm.h \ + libpq/libpq-fs.h \ + lib/dllist.h \ + utils/geo-decls.h + +ifeq ($(PORTNAME), hpux) +HEADERFILES += port/hpux/fixade.h +endif + + +TEMPDIR=/tmp + +install-headers: beforeinstall-headers + @for i in ${HEADERFILES}; do \ + echo "Installing $(HEADERDIR)/$$i."; \ + $(INSTALL) $(INSTLOPTS) $(srcdir)/backend/$$i $(HEADERDIR)/$$i; \ + done + $(INSTALL) $(INSTLOPTS) libpq-fe.h $(HEADERDIR)/libpq-fe.h + @mv -f $(HEADERDIR)/include/* $(HEADERDIR) + @rmdir $(HEADERDIR)/include +# XXX - installing fmgr.h depends on the backend being built + $(INSTALL) $(INSTLOPTS) $(srcdir)/backend/$(objdir)/fmgr.h $(HEADERDIR)/fmgr.h + @rm -f $(TEMPDIR)/c.h + @echo "#undef PORTNAME" > $(TEMPDIR)/c.h + @echo "#define PORTNAME $(PORTNAME)" >> $(TEMPDIR)/c.h + @echo "#undef PORTNAME_$(PORTNAME)" >> $(TEMPDIR)/c.h + @echo "#define PORTNAME_$(PORTNAME)" >> $(TEMPDIR)/c.h + @cat $(srcdir)/backend/include/c.h >> $(TEMPDIR)/c.h + $(INSTALL) $(INSTLOPTS) $(TEMPDIR)/c.h $(HEADERDIR)/c.h + @rm -f $(TEMPDIR)/postgres.h +# hardwire NAMEDATALEN and OIDNAMELEN into the postgres.h for this installation + @echo "#define NAMEDATALEN $(NAMEDATALEN)" >> $(TEMPDIR)/postgres.h + @echo "#define OIDNAMELEN $(OIDNAMELEN)" >> $(TEMPDIR)/postgres.h + @cat $(srcdir)/backend/include/postgres.h >> $(TEMPDIR)/postgres.h + $(INSTALL) $(INSTLOPTS) $(TEMPDIR)/postgres.h $(HEADERDIR)/postgres.h + +install:: install-headers + +include $(MKDIR)/postgres.lib.mk + diff --git a/src/interfaces/libpq/README b/src/interfaces/libpq/README new file mode 100644 index 00000000000..e581950bf23 --- /dev/null +++ b/src/interfaces/libpq/README @@ -0,0 +1 @@ +This directory contains the C version of Libpq, the POSTGRES frontend library. diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c new file mode 100644 index 00000000000..cce02b64fc0 --- /dev/null +++ b/src/interfaces/libpq/fe-auth.c @@ -0,0 +1,544 @@ +/*------------------------------------------------------------------------- + * + * fe-auth.c-- + * The front-end (client) authorization routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +/* + * INTERFACE ROUTINES + * frontend (client) routines: + * fe_sendauth send authentication information + * fe_getauthname get user's name according to the client side + * of the authentication system + * fe_setauthsvc set frontend authentication service + * fe_getauthsvc get current frontend authentication service + * + * + * + */ +#include <stdio.h> +#include <string.h> +#include <sys/param.h> /* for MAX{HOSTNAME,PATH}LEN, NOFILE */ +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include "libpq/pqcomm.h" + +#include "libpq-fe.h" +#include "fe-auth.h" + +/*---------------------------------------------------------------- + * common definitions for generic fe/be routines + *---------------------------------------------------------------- + */ + +struct authsvc { + char name[16]; /* service nickname (for command line) */ + MsgType msgtype; /* startup packet header type */ + int allowed; /* initially allowed (before command line + * option parsing)? + */ +}; + +/* + * Command-line parsing routines use this structure to map nicknames + * onto service types (and the startup packets to use with them). + * + * Programs receiving an authentication request use this structure to + * decide which authentication service types are currently permitted. + * By default, all authentication systems compiled into the system are + * allowed. Unauthenticated connections are disallowed unless there + * isn't any authentication system. + */ +static struct authsvc authsvcs[] = { +#ifdef KRB4 + { "krb4", STARTUP_KRB4_MSG, 1 }, + { "kerberos", STARTUP_KRB4_MSG, 1 }, +#endif /* KRB4 */ +#ifdef KRB5 + { "krb5", STARTUP_KRB5_MSG, 1 }, + { "kerberos", STARTUP_KRB5_MSG, 1 }, +#endif /* KRB5 */ + { UNAUTHNAME, STARTUP_MSG, +#if defined(KRB4) || defined(KRB5) + 0 +#else /* !(KRB4 || KRB5) */ + 1 +#endif /* !(KRB4 || KRB5) */ + } +}; + +static n_authsvcs = sizeof(authsvcs) / sizeof(struct authsvc); + +#ifdef KRB4 +/*---------------------------------------------------------------- + * MIT Kerberos authentication system - protocol version 4 + *---------------------------------------------------------------- + */ + +#include "krb.h" + +/* for some reason, this is not defined in krb.h ... */ +extern char *tkt_string(void); + +/* + * pg_krb4_init -- initialization performed before any Kerberos calls are made + * + * For v4, all we need to do is make sure the library routines get the right + * ticket file if we want them to see a special one. (They will open the file + * themselves.) + */ +static void pg_krb4_init() +{ + char *realm; + static init_done = 0; + + if (init_done) + return; + init_done = 1; + + /* + * If the user set PGREALM, then we use a ticket file with a special + * name: <usual-ticket-file-name>@<PGREALM-value> + */ + if (realm = getenv("PGREALM")) { + char tktbuf[MAXPATHLEN]; + + (void) sprintf(tktbuf, "%s@%s", tkt_string(), realm); + krb_set_tkt_string(tktbuf); + } +} + +/* + * pg_krb4_authname -- returns a pointer to static space containing whatever + * name the user has authenticated to the system + * + * We obtain this information by digging around in the ticket file. + */ +static char * +pg_krb4_authname(char* PQerrormsg) +{ + char instance[INST_SZ]; + char realm[REALM_SZ]; + int status; + static char name[SNAME_SZ+1] = ""; + + if (name[0]) + return(name); + + pg_krb4_init(); + + name[SNAME_SZ] = '\0'; + status = krb_get_tf_fullname(tkt_string(), name, instance, realm); + if (status != KSUCCESS) { + (void) sprintf(PQerrormsg, + "pg_krb4_authname: krb_get_tf_fullname: %s\n", + krb_err_txt[status]); + return((char *) NULL); + } + return(name); +} + +/* + * pg_krb4_sendauth -- client routine to send authentication information to + * the server + * + * This routine does not do mutual authentication, nor does it return enough + * information to do encrypted connections. But then, if we want to do + * encrypted connections, we'll have to redesign the whole RPC mechanism + * anyway. + * + * If the user is too lazy to feed us a hostname, we try to come up with + * something other than "localhost" since the hostname is used as an + * instance and instance names in v4 databases are usually actual hostnames + * (canonicalized to omit all domain suffixes). + */ +static int +pg_krb4_sendauth(char* PQerrormsg, int sock, + struct sockaddr_in *laddr, + struct sockaddr_in *raddr, + char *hostname) +{ + long krbopts = 0; /* one-way authentication */ + KTEXT_ST clttkt; + int status; + char hostbuf[MAXHOSTNAMELEN]; + char *realm = getenv("PGREALM"); /* NULL == current realm */ + + if (!hostname || !(*hostname)) { + if (gethostname(hostbuf, MAXHOSTNAMELEN) < 0) + strcpy(hostbuf, "localhost"); + hostname = hostbuf; + } + + pg_krb4_init(); + + status = krb_sendauth(krbopts, + sock, + &clttkt, + PG_KRB_SRVNAM, + hostname, + realm, + (u_long) 0, + (MSG_DAT *) NULL, + (CREDENTIALS *) NULL, + (Key_schedule *) NULL, + laddr, + raddr, + PG_KRB4_VERSION); + if (status != KSUCCESS) { + (void) sprintf(PQerrormsg, + "pg_krb4_sendauth: kerberos error: %s\n", + krb_err_txt[status]); + return(STATUS_ERROR); + } + return(STATUS_OK); +} + +#endif /* KRB4 */ + +#ifdef KRB5 +/*---------------------------------------------------------------- + * MIT Kerberos authentication system - protocol version 5 + *---------------------------------------------------------------- + */ + +#include "krb5/krb5.h" + +/* + * pg_an_to_ln -- return the local name corresponding to an authentication + * name + * + * XXX Assumes that the first aname component is the user name. This is NOT + * necessarily so, since an aname can actually be something out of your + * worst X.400 nightmare, like + * ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU + * Note that the MIT an_to_ln code does the same thing if you don't + * provide an aname mapping database...it may be a better idea to use + * krb5_an_to_ln, except that it punts if multiple components are found, + * and we can't afford to punt. + */ +static char * +pg_an_to_ln(char *aname) +{ + char *p; + + if ((p = strchr(aname, '/')) || (p = strchr(aname, '@'))) + *p = '\0'; + return(aname); +} + + +/* + * pg_krb5_init -- initialization performed before any Kerberos calls are made + * + * With v5, we can no longer set the ticket (credential cache) file name; + * we now have to provide a file handle for the open (well, "resolved") + * ticket file everywhere. + * + */ +static int +krb5_ccache pg_krb5_init() +{ + krb5_error_code code; + char *realm, *defname; + char tktbuf[MAXPATHLEN]; + static krb5_ccache ccache = (krb5_ccache) NULL; + + if (ccache) + return(ccache); + + /* + * If the user set PGREALM, then we use a ticket file with a special + * name: <usual-ticket-file-name>@<PGREALM-value> + */ + if (!(defname = krb5_cc_default_name())) { + (void) sprintf(PQerrormsg, + "pg_krb5_init: krb5_cc_default_name failed\n"); + return((krb5_ccache) NULL); + } + (void) strcpy(tktbuf, defname); + if (realm = getenv("PGREALM")) { + (void) strcat(tktbuf, "@"); + (void) strcat(tktbuf, realm); + } + + if (code = krb5_cc_resolve(tktbuf, &ccache)) { + (void) sprintf(PQerrormsg, + "pg_krb5_init: Kerberos error %d in krb5_cc_resolve\n", + code); + com_err("pg_krb5_init", code, "in krb5_cc_resolve"); + return((krb5_ccache) NULL); + } + return(ccache); +} + +/* + * pg_krb5_authname -- returns a pointer to static space containing whatever + * name the user has authenticated to the system + * + * We obtain this information by digging around in the ticket file. + */ +static char * +pg_krb5_authname(char* PQerrormsg) +{ + krb5_ccache ccache; + krb5_principal principal; + krb5_error_code code; + static char *authname = (char *) NULL; + + if (authname) + return(authname); + + ccache = pg_krb5_init(); /* don't free this */ + + if (code = krb5_cc_get_principal(ccache, &principal)) { + (void) sprintf(PQerrormsg, + "pg_krb5_authname: Kerberos error %d in krb5_cc_get_principal\n", + code); + com_err("pg_krb5_authname", code, "in krb5_cc_get_principal"); + return((char *) NULL); + } + if (code = krb5_unparse_name(principal, &authname)) { + (void) sprintf(PQerrormsg, + "pg_krb5_authname: Kerberos error %d in krb5_unparse_name\n", + code); + com_err("pg_krb5_authname", code, "in krb5_unparse_name"); + krb5_free_principal(principal); + return((char *) NULL); + } + krb5_free_principal(principal); + return(pg_an_to_ln(authname)); +} + +/* + * pg_krb5_sendauth -- client routine to send authentication information to + * the server + * + * This routine does not do mutual authentication, nor does it return enough + * information to do encrypted connections. But then, if we want to do + * encrypted connections, we'll have to redesign the whole RPC mechanism + * anyway. + * + * Server hostnames are canonicalized v4-style, i.e., all domain suffixes + * are simply chopped off. Hence, we are assuming that you've entered your + * server instances as + * <value-of-PG_KRB_SRVNAM>/<canonicalized-hostname> + * in the PGREALM (or local) database. This is probably a bad assumption. + */ +static int +pg_krb5_sendauth(char* PQerrormsg,int sock, + struct sockaddr_in *laddr, + struct sockaddr_in *raddr, + char *hostname) +{ + char servbuf[MAXHOSTNAMELEN + 1 + + sizeof(PG_KRB_SRVNAM)]; + char *hostp; + char *realm; + krb5_error_code code; + krb5_principal client, server; + krb5_ccache ccache; + krb5_error *error = (krb5_error *) NULL; + + ccache = pg_krb5_init(); /* don't free this */ + + /* + * set up client -- this is easy, we can get it out of the ticket + * file. + */ + if (code = krb5_cc_get_principal(ccache, &client)) { + (void) sprintf(PQerrormsg, + "pg_krb5_sendauth: Kerberos error %d in krb5_cc_get_principal\n", + code); + com_err("pg_krb5_sendauth", code, "in krb5_cc_get_principal"); + return(STATUS_ERROR); + } + + /* + * set up server -- canonicalize as described above + */ + (void) strcpy(servbuf, PG_KRB_SRVNAM); + *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/'; + if (hostname || *hostname) { + (void) strncpy(++hostp, hostname, MAXHOSTNAMELEN); + } else { + if (gethostname(++hostp, MAXHOSTNAMELEN) < 0) + (void) strcpy(hostp, "localhost"); + } + if (hostp = strchr(hostp, '.')) + *hostp = '\0'; + if (realm = getenv("PGREALM")) { + (void) strcat(servbuf, "@"); + (void) strcat(servbuf, realm); + } + if (code = krb5_parse_name(servbuf, &server)) { + (void) sprintf(PQerrormsg, + "pg_krb5_sendauth: Kerberos error %d in krb5_parse_name\n", + code); + com_err("pg_krb5_sendauth", code, "in krb5_parse_name"); + krb5_free_principal(client); + return(STATUS_ERROR); + } + + /* + * The only thing we want back from krb5_sendauth is an error status + * and any error messages. + */ + if (code = krb5_sendauth((krb5_pointer) &sock, + PG_KRB5_VERSION, + client, + server, + (krb5_flags) 0, + (krb5_checksum *) NULL, + (krb5_creds *) NULL, + ccache, + (krb5_int32 *) NULL, + (krb5_keyblock **) NULL, + &error, + (krb5_ap_rep_enc_part **) NULL)) { + if ((code == KRB5_SENDAUTH_REJECTED) && error) { + (void) sprintf(PQerrormsg, + "pg_krb5_sendauth: authentication rejected: \"%*s\"\n", + error->text.length, error->text.data); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + } else { + (void) sprintf(PQerrormsg, + "pg_krb5_sendauth: Kerberos error %d in krb5_sendauth\n", + code); + com_err("pg_krb5_sendauth", code, "in krb5_sendauth"); + } + } + krb5_free_principal(client); + krb5_free_principal(server); + return(code ? STATUS_ERROR : STATUS_OK); +} + +#endif /* KRB5 */ + + +/* + * fe_sendauth -- client demux routine for outgoing authentication information + */ +int +fe_sendauth(MsgType msgtype, Port *port, char *hostname, char* PQerrormsg) +{ + switch (msgtype) { +#ifdef KRB4 + case STARTUP_KRB4_MSG: + if (pg_krb4_sendauth(PQerrormsg, port->sock, &port->laddr, + &port->raddr, + hostname) != STATUS_OK) { + (void) sprintf(PQerrormsg, + "fe_sendauth: krb4 authentication failed\n"); +/* fputs(PQerrormsg, stderr); */ + return(STATUS_ERROR); + } + break; +#endif +#ifdef KRB5 + case STARTUP_KRB5_MSG: + if (pg_krb5_sendauth(PQerrormsg,port->sock, &port->laddr, + &port->raddr, + hostname) != STATUS_OK) { + (void) sprintf(PQerrormsg, + "fe_sendauth: krb5 authentication failed\n"); + return(STATUS_ERROR); + } + break; +#endif + case STARTUP_MSG: + break; + default: + break; + } + return(STATUS_OK); +} + +/* + * fe_setauthsvc + * fe_getauthsvc + * + * Set/return the authentication service currently selected for use by the + * frontend. (You can only use one in the frontend, obviously.) + */ +static pg_authsvc = -1; + +void +fe_setauthsvc(char *name, char* PQerrormsg) +{ + int i; + + for (i = 0; i < n_authsvcs; ++i) + if (!strcmp(name, authsvcs[i].name)) { + pg_authsvc = i; + break; + } + if (i == n_authsvcs) { + (void) sprintf(PQerrormsg, + "fe_setauthsvc: invalid name: %s, ignoring...\n", + name); + } + return; +} + +MsgType +fe_getauthsvc(char* PQerrormsg) +{ + if (pg_authsvc < 0 || pg_authsvc >= n_authsvcs) + fe_setauthsvc(DEFAULT_CLIENT_AUTHSVC,PQerrormsg); + return(authsvcs[pg_authsvc].msgtype); +} + +/* + * fe_getauthname -- returns a pointer to static space containing whatever + * name the user has authenticated to the system + * if there is an error, return the error message in PQerrormsg + */ +char* +fe_getauthname(char* PQerrormsg) +{ + char *name = (char *) NULL; + MsgType authsvc; + + authsvc = fe_getauthsvc(PQerrormsg); + switch ((int) authsvc) { +#ifdef KRB4 + case STARTUP_KRB4_MSG: + name = pg_krb4_authname(PQerrormsg); + break; +#endif +#ifdef KRB5 + case STARTUP_KRB5_MSG: + name = pg_krb5_authname(PQerrormsg); + break; +#endif + case STARTUP_MSG: + { + struct passwd *pw = getpwuid(getuid()); + if (pw && + pw->pw_name && + (name = (char *) malloc(strlen(pw->pw_name) + 1))) { + (void) strcpy(name, pw->pw_name); + } + } + break; + default: + (void) sprintf(PQerrormsg, + "fe_getauthname: invalid authentication system: %d\n", + authsvc); + break; + } + return(name); +} + + diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h new file mode 100644 index 00000000000..d5b8ecd8f76 --- /dev/null +++ b/src/interfaces/libpq/fe-auth.h @@ -0,0 +1,38 @@ +/*------------------------------------------------------------------------- + * + * fe-auth.h + * + * Definitions for network authentication routines + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: fe-auth.h,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef FE_AUTH_H +#define FE_AUTH_H + +/*---------------------------------------------------------------- + * Common routines and definitions + *---------------------------------------------------------------- + */ + +/* what we call "no authentication system" */ +#define UNAUTHNAME "unauth" + +/* what a frontend uses by default */ +#if !defined(KRB4) && !defined(KRB5) +#define DEFAULT_CLIENT_AUTHSVC UNAUTHNAME +#else /* KRB4 || KRB5 */ +#define DEFAULT_CLIENT_AUTHSVC "kerberos" +#endif /* KRB4 || KRB5 */ + +extern int fe_sendauth(MsgType msgtype, Port *port, char *hostname, char* PQerromsg); +extern void fe_setauthsvc(char *name, char* PQerrormsg); + +#define PG_KRB4_VERSION "PGVER4.1" /* at most KRB_SENDAUTH_VLEN chars */ +#define PG_KRB5_VERSION "PGVER5.1" + +#endif /* FE_AUTH_H */ + diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c new file mode 100644 index 00000000000..97a909e1055 --- /dev/null +++ b/src/interfaces/libpq/fe-connect.c @@ -0,0 +1,460 @@ +/*------------------------------------------------------------------------- + * + * fe-connect.c-- + * functions related to setting up a connection to the backend + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <netdb.h> +#include <errno.h> +#include "libpq/pqcomm.h" /* for decls of MsgType, PacketBuf, StartupInfo */ +#include "fe-auth.h" +#include "libpq-fe.h" + +#if defined(PORTNAME_ultrix4) || defined(PORTNAME_next) + /* ultrix is lame and doesn't have strdup in libc for some reason */ + /* [TRH] So doesn't NEXTSTEP. But whaddaya expect for a non-ANSI +standard function? (My, my. Touchy today, are we?) */ +static +char * +strdup(char *string) +{ + char *nstr; + + nstr = strcpy((char *)malloc(strlen(string)+1), string); + return nstr; +} +#endif + +/* use a local version instead of the one found in pqpacket.c */ +static ConnStatusType connectDB(PGconn *conn); + +static int packetSend(Port *port, PacketBuf *buf, PacketLen len, + bool nonBlocking); +static void startup2PacketBuf(StartupInfo* s, PacketBuf* res); +static void freePGconn(PGconn *conn); +static void closePGconn(PGconn *conn); + +#define NOTIFYLIST_INITIAL_SIZE 10 +#define NOTIFYLIST_GROWBY 10 + +/* ---------------- + * PQsetdb + * + * establishes a connectin to a postgres backend through the postmaster + * at the specified host and port. + * + * returns a PGconn* which is needed for all subsequent libpq calls + * if the status field of the connection returned is CONNECTION_BAD, + * then some fields may be null'ed out instead of having valid values + * ---------------- + */ +PGconn* +PQsetdb(char *pghost, char* pgport, char* pgoptions, char* pgtty, char* dbName) +{ + PGconn *conn; + char *tmp; + + conn = (PGconn*)malloc(sizeof(PGconn)); + + conn->Pfout = NULL; + conn->Pfin = NULL; + conn->Pfdebug = NULL; + conn->port = NULL; + conn->notifyList = DLNewList(); + + if (!pghost || pghost[0] == '\0') { + if (!(tmp = getenv("PGHOST"))) { + tmp = DefaultHost; + } + conn->pghost = strdup(tmp); + } else + conn->pghost = strdup(pghost); + + if (!pgport || pgport == '\0') { + if (!(tmp = getenv("PGPORT"))) { + tmp = POSTPORT; + } + conn->pgport = strdup(tmp); + } else + conn->pgport = strdup(pgport); + + if (!pgtty || pgtty == '\0') { + if (!(tmp = getenv("PGTTY"))) { + tmp = DefaultTty; + } + conn->pgtty = strdup(tmp); + } else + conn->pgtty = strdup(pgtty); + + if (!pgoptions || pgoptions == '\0') { + if (!(tmp = getenv("PGOPTIONS"))) { + tmp = DefaultOption; + } + conn->pgoptions = strdup(tmp); + } else + conn->pgoptions = strdup(pgoptions); + + if (!dbName || dbName[0] == '\0') { + char errorMessage[ERROR_MSG_LENGTH]; + if (!(tmp = getenv("PGDATABASE")) && + !(tmp = fe_getauthname(errorMessage))) { + sprintf(conn->errorMessage, + "FATAL: PQsetdb: Unable to determine a database name!\n"); +/* pqdebug("%s", conn->errorMessage); */ + conn->dbName = NULL; + return conn; + } + conn->dbName = strdup(tmp); + } else + conn->dbName = strdup(dbName); + + conn->status = connectDB(conn); + return conn; +} + +/* + * connectDB - + * make a connection to the database, returns 1 if successful or 0 if not + * + */ +static ConnStatusType +connectDB(PGconn *conn) +{ + struct hostent *hp; + + StartupInfo startup; + PacketBuf pacBuf; + int status; + MsgType msgtype; + int laddrlen = sizeof(struct sockaddr); + Port *port = conn->port; + int portno; + PGresult *res; + + char *user; + /* + // + // Initialize the startup packet. + // + // This data structure is used for the seq-packet protocol. It + // describes the frontend-backend connection. + // + // + */ + user = fe_getauthname(conn->errorMessage); + if (!user) + goto connect_errReturn; + strncpy(startup.database,conn->dbName,sizeof(startup.database)); + strncpy(startup.user,user,sizeof(startup.user)); + strncpy(startup.tty,conn->pgtty,sizeof(startup.tty)); + if (conn->pgoptions) { + strncpy(startup.options,conn->pgoptions, sizeof(startup.options)); + } + else + startup.options[0]='\0'; + startup.execFile[0]='\0'; /* not used */ + + /* + // + // Open a connection to postmaster/backend. + // + */ + port = (Port *) malloc(sizeof(Port)); + memset((char *) port, 0, sizeof(Port)); + + if (!(hp = gethostbyname(conn->pghost)) || hp->h_addrtype != AF_INET) { + (void) sprintf(conn->errorMessage, + "connectDB() -- unknown hostname: %s\n", + conn->pghost); + goto connect_errReturn; + } + memset((char *) &port->raddr, 0, sizeof(port->raddr)); + memmove((char *) &(port->raddr.sin_addr), + (char *) hp->h_addr, + hp->h_length); + port->raddr.sin_family = AF_INET; + portno = atoi(conn->pgport); + port->raddr.sin_port = htons((unsigned short)(portno)); + + /* connect to the server */ + if ((port->sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + (void) sprintf(conn->errorMessage, + "connectDB() -- socket() failed: errno=%d\n%s\n", + errno, strerror(errno)); + goto connect_errReturn; + } + if (connect(port->sock, (struct sockaddr *)&port->raddr, + sizeof(port->raddr)) < 0) { + (void) sprintf(conn->errorMessage, + "connectDB() failed: Is the postmaster running at '%s' on port '%s'?\n", + conn->pghost,conn->pgport); + goto connect_errReturn; + } + + + /* fill in the client address */ + if (getsockname(port->sock, (struct sockaddr *) &port->laddr, + &laddrlen) < 0) { + (void) sprintf(conn->errorMessage, + "connectDB() -- getsockname() failed: errno=%d\n%s\n", + errno, strerror(errno)); + goto connect_errReturn; + } + + /* by this point, connection has been opened */ + msgtype = fe_getauthsvc(conn->errorMessage); + +/* pacBuf = startup2PacketBuf(&startup);*/ + startup2PacketBuf(&startup, &pacBuf); + pacBuf.msgtype = htonl(msgtype); + status = packetSend(port, &pacBuf, sizeof(PacketBuf), BLOCKING); + + if (status == STATUS_ERROR) + { + sprintf(conn->errorMessage, + "connectDB() -- couldn't send complete packet: errno=%d\n%s\n", errno,strerror(errno)); + goto connect_errReturn; + } + + /* authenticate as required*/ + if (fe_sendauth(msgtype, port, conn->pghost, + conn->errorMessage) != STATUS_OK) { + (void) sprintf(conn->errorMessage, + "connectDB() -- authentication failed with %s\n", + conn->pghost); + goto connect_errReturn; + } + + /* set up the socket file descriptors */ + conn->Pfout = fdopen(port->sock, "w"); + conn->Pfin = fdopen(dup(port->sock), "r"); + if (!conn->Pfout || !conn->Pfin) { + (void) sprintf(conn->errorMessage, + "connectDB() -- fdopen() failed: errno=%d\n%s\n", + errno, strerror(errno)); + goto connect_errReturn; + } + + conn->port = port; + + /* we have a connection now, + send a blank query down to make sure the database exists*/ + res = PQexec(conn," "); + if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY) { + /* error will already be in conn->errorMessage */ + goto connect_errReturn; + } + free(res); + return CONNECTION_OK; + +connect_errReturn: + return CONNECTION_BAD; + +} + +/* + * freePGconn + * - free the PGconn data structure + * + */ +static void +freePGconn(PGconn *conn) +{ + if (conn->pghost) free(conn->pghost); + if (conn->pgtty) free(conn->pgtty); + if (conn->pgoptions) free(conn->pgoptions); + if (conn->pgport) free(conn->pgport); + if (conn->dbName) free(conn->dbName); + if (conn->notifyList) DLFreeList(conn->notifyList); + free(conn); +} + +/* + closePGconn + - properly close a connection to the backend +*/ +static void +closePGconn(PGconn *conn) +{ + fputs("X\0", conn->Pfout); + fflush(conn->Pfout); + if (conn->Pfout) fclose(conn->Pfout); + if (conn->Pfin) fclose(conn->Pfin); + if (conn->Pfdebug) fclose(conn->Pfdebug); +} + +/* + PQfinish: + properly close a connection to the backend + also frees the PGconn data structure so it shouldn't be re-used + after this +*/ +void +PQfinish(PGconn *conn) +{ + if (conn->status == CONNECTION_OK) + closePGconn(conn); + freePGconn(conn); +} + +/* PQreset : + resets the connection to the backend + closes the existing connection and makes a new one +*/ +void +PQreset(PGconn *conn) +{ + closePGconn(conn); + conn->status = connectDB(conn); +} + +/* + * PacketSend() + * + this is just like PacketSend(), defined in backend/libpq/pqpacket.c + but we define it here to avoid linking in all of libpq.a + + * packetSend -- send a single-packet message. + * + * RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise. + * SIDE_EFFECTS: may block. + * NOTES: Non-blocking writes would significantly complicate + * buffer management. For now, we're not going to do it. + * +*/ +static int +packetSend(Port *port, + PacketBuf *buf, + PacketLen len, + bool nonBlocking) +{ + PacketLen totalLen; + int addrLen = sizeof(struct sockaddr_in); + + totalLen = len; + + len = sendto(port->sock, (Addr) buf, totalLen, /* flags */ 0, + (struct sockaddr *)&(port->raddr), addrLen); + + if (len < totalLen) { + return(STATUS_ERROR); + } + + return(STATUS_OK); +} + +/* + * startup2PacketBuf() + * + * this is just like StartupInfo2Packet(), defined in backend/libpq/pqpacket.c + * but we repeat it here so we don't have to link in libpq.a + * + * converts a StartupInfo structure to a PacketBuf + */ +static void +startup2PacketBuf(StartupInfo* s, PacketBuf* res) +{ + char* tmp; + +/* res = (PacketBuf*)malloc(sizeof(PacketBuf)); */ + res->len = htonl(sizeof(PacketBuf)); + /* use \n to delimit the strings */ + res->data[0] = '\0'; + + tmp= res->data; + + strncpy(tmp, s->database, sizeof(s->database)); + tmp += sizeof(s->database); + strncpy(tmp, s->user, sizeof(s->user)); + tmp += sizeof(s->user); + strncpy(tmp, s->options, sizeof(s->options)); + tmp += sizeof(s->options); + strncpy(tmp, s->execFile, sizeof(s->execFile)); + tmp += sizeof(s->execFile); + strncpy(tmp, s->tty, sizeof(s->execFile)); + +} + + +/* =========== accessor functions for PGconn ========= */ +char* +PQdb(PGconn* conn) +{ + return conn->dbName; +} + +char* +PQhost(PGconn* conn) +{ + return conn->pghost; +} + +char* +PQoptions(PGconn* conn) +{ + return conn->pgoptions; +} + +char* +PQtty(PGconn* conn) +{ + return conn->pgtty; +} + +char* +PQport(PGconn* conn) +{ + return conn->pgport; +} + +ConnStatusType +PQstatus(PGconn* conn) +{ + return conn->status; +} + +char* +PQerrorMessage(PGconn* conn) +{ + return conn->errorMessage; +} + +void +PQtrace(PGconn *conn, FILE* debug_port) +{ + if (conn == NULL || + conn->status == CONNECTION_BAD) { + return; + } + PQuntrace(conn); + conn->Pfdebug = debug_port; +} + +void +PQuntrace(PGconn *conn) +{ + if (conn == NULL || + conn->status == CONNECTION_BAD) { + return; + } + if (conn->Pfdebug) { + fflush(conn->Pfdebug); + fclose(conn->Pfdebug); + conn->Pfdebug = NULL; + } +} diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c new file mode 100644 index 00000000000..78854ed73fe --- /dev/null +++ b/src/interfaces/libpq/fe-exec.c @@ -0,0 +1,1061 @@ +/*------------------------------------------------------------------------- + * + * fe-exec.c-- + * functions related to sending a query down to the backend + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "postgres.h" +#include "libpq/pqcomm.h" +#include "libpq-fe.h" +#include <signal.h> + +/* the tuples array in a PGresGroup has to grow to accommodate the tuples */ +/* returned. Each time, we grow by this much: */ +#define TUPARR_GROW_BY 100 + +/* keep this in same order as ExecStatusType in pgtclCmds.h */ +char* pgresStatus[] = { + "PGRES_EMPTY_QUERY", + "PGRES_COMMAND_OK", + "PGRES_TUPLES_OK", + "PGRES_BAD_RESPONSE", + "PGRES_NONFATAL_ERROR", + "PGRES_FATAL_ERROR" +}; + + +static PGresult* makePGresult(PGconn *conn, char *pname); +static void addTuple(PGresult *res, PGresAttValue *tup); +static PGresAttValue* getTuple(PGconn *conn, PGresult *res, int binary); +static PGresult* makeEmptyPGresult(PGconn *conn, ExecStatusType status); +static void fill(int length, int max, char filler, FILE *fp); + +/* + * PQclear - + * free's the memory associated with a PGresult + * + */ +void +PQclear(PGresult* res) +{ + int i,j; + + if (!res) + return; + + /* free all the tuples */ + for (i=0;i<res->ntups;i++) { + for (j=0;j<res->numAttributes;j++) { + if (res->tuples[i][j].value) + free(res->tuples[i][j].value); + } + free(res->tuples[i]); + } + free(res->tuples); + + /* free all the attributes */ + for (i=0;i<res->numAttributes;i++) { + if (res->attDescs[i].name) + free(res->attDescs[i].name); + } + free(res->attDescs); + + /* free the structure itself */ + free(res); +} + +/* + * PGresult - + * returns a newly allocated, initialized PGresult + * + */ + +static PGresult* +makeEmptyPGresult(PGconn *conn, ExecStatusType status) +{ + PGresult *result; + + result = (PGresult*)malloc(sizeof(PGresult)); + + result->conn = conn; + result->ntups = 0; + result->numAttributes = 0; + result->attDescs = NULL; + result->tuples = NULL; + result->tupArrSize = 0; + result->resultStatus = status; + result->cmdStatus[0] = '\0'; + result->binary = 0; + return result; +} + +/* + * getTuple - + * get the next tuple from the stream + * + * the CALLER is responsible from freeing the PGresAttValue returned + */ + +static PGresAttValue* +getTuple(PGconn *conn, PGresult* result, int binary) +{ + char bitmap[MAX_FIELDS]; /* the backend sends us a bitmap of */ + /* which attributes are null */ + int bitmap_index = 0; + int i; + int nbytes; /* the number of bytes in bitmap */ + char bmap; /* One byte of the bitmap */ + int bitcnt = 0; /* number of bits examined in current byte */ + int vlen; /* length of the current field value */ + FILE *Pfin = conn->Pfin; + FILE *Pfdebug = conn->Pfdebug; + + PGresAttValue* tup; + + int nfields = result->numAttributes; + + result->binary = binary; + + tup = (PGresAttValue*) malloc(nfields * sizeof(PGresAttValue)); + + nbytes = nfields / BYTELEN; + if ( (nfields % BYTELEN) > 0) + nbytes++; + + if (pqGetnchar(bitmap, nbytes, Pfin, Pfdebug) == 1){ + sprintf(conn->errorMessage, + "Error reading null-values bitmap from tuple data stream\n"); + return NULL; + } + + bmap = bitmap[bitmap_index]; + + for (i=0;i<nfields;i++) { + if (!(bmap & 0200)) { + /* if the field value is absent, make it '\0' */ + /* XXX this makes it impossible to distinguish NULL + attributes from "". Is that OK? */ + tup[i].value = (char*)malloc(1); + tup[i].value[0] = '\0'; + tup[i].len = 0; + } + else { + /* get the value length (the first four bytes are for length) */ + pqGetInt(&vlen, VARHDRSZ, Pfin, Pfdebug); + if (binary == 0) { + vlen = vlen - VARHDRSZ; + } + if (vlen < 0) + vlen = 0; + tup[i].len = vlen; + tup[i].value = (char*) malloc(vlen + 1); + /* read in the value; */ + if (vlen > 0) + pqGetnchar((char*)(tup[i].value), vlen, Pfin, Pfdebug); + tup[i].value[vlen] = '\0'; + } + /* get the appropriate bitmap */ + bitcnt++; + if (bitcnt == BYTELEN) { + bitmap_index++; + bmap = bitmap[bitmap_index]; + bitcnt = 0; + } else + bmap <<= 1; + } + + return tup; +} + + +/* + * addTuple + * add a tuple to the PGresult structure, growing it if necessary + * to accommodate + * + */ +static void +addTuple(PGresult* res, PGresAttValue* tup) +{ + if (res->ntups == res->tupArrSize) { + /* grow the array */ + res->tupArrSize += TUPARR_GROW_BY; + + if (res->ntups == 0) + res->tuples = (PGresAttValue**) + malloc(res->tupArrSize * sizeof(PGresAttValue*)); + else + /* we can use realloc because shallow copying of the structure is okay */ + res->tuples = (PGresAttValue**) + realloc(res->tuples, res->tupArrSize * sizeof(PGresAttValue*)); + } + + res->tuples[res->ntups] = tup; + res->ntups++; +} + +/* + * PGresult + * fill out the PGresult structure with result tuples from the backend + * this is called after query has been successfully run and we have + * a portal name + * + * ASSUMPTION: we assume only *1* tuple group is returned from the backend + * + * the CALLER is reponsible for free'ing the new PGresult allocated here + * + */ + +static PGresult* +makePGresult(PGconn* conn, char* pname) +{ + PGresult* result; + int id; + int nfields; + int i; + int done = 0; + + PGresAttValue* newTup; + + FILE* Pfin = conn->Pfin; + FILE* Pfdebug = conn->Pfdebug; + + result = makeEmptyPGresult(conn, PGRES_TUPLES_OK); + + /* makePGresult() should only be called when the */ + /* id of the stream is 'T' to start with */ + + /* the next two bytes are the number of fields */ + if (pqGetInt(&nfields, 2, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "could not get the number of fields from the 'T' message\n"); + goto makePGresult_badResponse_return; + } + else + result->numAttributes = nfields; + + /* allocate space for the attribute descriptors */ + if (nfields > 0) { + result->attDescs = (PGresAttDesc*) malloc(nfields * sizeof(PGresAttDesc)); + } + + /* get type info */ + for (i=0;i<nfields;i++) { + char typName[MAX_MESSAGE_LEN]; + int adtid; + int adtsize; + + if ( pqGets(typName, MAX_MESSAGE_LEN, Pfin, Pfdebug) || + pqGetInt(&adtid, 4, Pfin, Pfdebug) || + pqGetInt(&adtsize, 2, Pfin, Pfdebug)) { + sprintf(conn->errorMessage, + "error reading type information from the 'T' message\n"); + goto makePGresult_badResponse_return; + } + result->attDescs[i].name = malloc(strlen(typName)+1); + strcpy(result->attDescs[i].name,typName); + result->attDescs[i].adtid = adtid; + result->attDescs[i].adtsize = adtsize; /* casting from int to int2 here */ + } + + id = pqGetc(Pfin,Pfdebug); + + /* process the data stream until we're finished */ + while(!done) { + switch (id) { + case 'T': /* a new tuple group */ + sprintf(conn->errorMessage, + "makePGresult() -- is not equipped to handle multiple tuple groups.\n"); + goto makePGresult_badResponse_return; + case 'B': /* a tuple in binary format */ + case 'D': /* a tuple in ASCII format */ + newTup = getTuple(conn, result, (id == 'B')); + if (newTup == NULL) + goto makePGresult_badResponse_return; + addTuple(result,newTup); + break; +/* case 'A': + sprintf(conn->errorMessage, "Asynchronous portals not supported"); + result->resultStatus = PGRES_NONFATAL_ERROR; + return result; + break; +*/ + case 'C': /* end of portal tuple stream */ + { + char command[MAX_MESSAGE_LEN]; + pqGets(command,MAX_MESSAGE_LEN, Pfin, Pfdebug); /* read the command tag */ + done = 1; + } + break; + case 'E': /* errors */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Error return detected from backend, but error message cannot be read"); + } + result->resultStatus = PGRES_FATAL_ERROR; + return result; + break; + case 'N': /* notices from the backend */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Notice return detected from backend, but error message cannot be read"); + } else + /* XXXX send Notices to stderr for now */ + fprintf(stderr, "%s\n", conn->errorMessage); + break; + default: /* uh-oh + this should never happen but frequently does when the + backend dumps core */ + sprintf(conn->errorMessage,"FATAL: unexpected results from the backend, it probably dumped core."); + fprintf(stderr, conn->errorMessage); + result->resultStatus = PGRES_FATAL_ERROR; + return result; + break; + } + if (!done) + id = getc(Pfin); + } /* while (1) */ + + result->resultStatus = PGRES_TUPLES_OK; + return result; + +makePGresult_badResponse_return: + result->resultStatus = PGRES_BAD_RESPONSE; + return result; + +} + + + +/* + * PQexec + * send a query to the backend and package up the result in a Pgresult + * + * if the query failed, return NULL, conn->errorMessage is set to + * a relevant message + * if query is successful, a new PGresult is returned + * the use is responsible for freeing that structure when done with it + * + */ + +PGresult* +PQexec(PGconn* conn, char* query) +{ + PGresult *result; + int id, clear; + char buffer[MAX_MESSAGE_LEN]; + char cmdStatus[MAX_MESSAGE_LEN]; + char pname[MAX_MESSAGE_LEN]; /* portal name */ + PGnotify *newNotify; + FILE *Pfin = conn->Pfin; + FILE *Pfout = conn->Pfout; + FILE* Pfdebug = conn->Pfdebug; + + pname[0]='\0'; + + /*clear the error string */ + conn->errorMessage[0] = '\0'; + + /* check to see if the query string is too long */ + if (strlen(query) > MAX_MESSAGE_LEN) { + sprintf(conn->errorMessage, "PQexec() -- query is too long. Maximum length is %d\n", MAX_MESSAGE_LEN -2 ); + return NULL; + } + + /* the frontend-backend protocol uses 'Q' to designate queries */ + sprintf(buffer,"Q%s",query); + + /* send the query to the backend; */ + if (pqPuts(buffer,Pfout, Pfdebug) == 1) { + (void) sprintf(conn->errorMessage, + "PQexec() -- while sending query: %s\n-- fprintf to Pfout failed: errno=%d\n%s\n", + query, errno,strerror(errno)); + return NULL; + } + + /* loop forever because multiple messages, especially NOTICES, + can come back from the backend + NOTICES are output directly to stderr + */ + + while (1) { + + /* read the result id */ + id = pqGetc(Pfin,Pfdebug); + if (id == EOF) { + /* hmm, no response from the backend-end, that's bad */ + (void) sprintf(conn->errorMessage, + "PQexec() -- No response from backend\n"); + return (PGresult*)NULL; + } + + switch (id) { + case 'A': + newNotify = (PGnotify*)malloc(sizeof(PGnotify)); + pqGetInt(&(newNotify->be_pid), 4, Pfin, Pfdebug); + pqGets(newNotify->relname, NAMEDATALEN, Pfin, Pfdebug); + DLAddTail(conn->notifyList, DLNewElem(newNotify)); + /* async messages are piggy'ed back on other messages, + so we stay in the while loop for other messages */ + break; + case 'C': /* portal query command, no tuples returned */ + if (pqGets(cmdStatus, MAX_MESSAGE_LEN, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "PQexec() -- query command completed, but return message from backend cannot be read"); + return (PGresult*)NULL; + } + else { + /* + // since backend may produce more than one result for some commands + // need to poll until clear + // send an empty query down, and keep reading out of the pipe + // until an 'I' is received. + */ + clear = 0; + + pqPuts("Q ",Pfout,Pfdebug); /* send an empty query */ + while (!clear) + { + if (pqGets(buffer,ERROR_MSG_LENGTH,Pfin,Pfdebug) == 1) + clear = 1; + clear = (buffer[0] == 'I'); + } + result = makeEmptyPGresult(conn,PGRES_COMMAND_OK); + strncpy(result->cmdStatus,cmdStatus, CMDSTATUS_LEN-1); + return result; + } + break; + case 'E': /* error return */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + (void) sprintf(conn->errorMessage, + "PQexec() -- error return detected from backend, but error message cannot be read"); + } + return (PGresult*)NULL; + break; + case 'I': /* empty query */ + /* read the throw away the closing '\0' */ + { + int c; + if ((c = pqGetc(Pfin,Pfdebug)) != '\0') { + fprintf(stderr,"error!, unexpected character %c following 'I'\n", c); + } + result = makeEmptyPGresult(conn, PGRES_EMPTY_QUERY); + return result; + } + break; + case 'N': /* notices from the backend */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "PQexec() -- error return detected from backend, but error message cannot be read"); + return (PGresult*)NULL; + } + else + fprintf(stderr,"%s", conn->errorMessage); + break; + case 'P': /* synchronous (normal) portal */ + pqGets(pname,MAX_MESSAGE_LEN,Pfin, Pfdebug); /* read in the portal name*/ + break; + case 'T': /* actual tuple results: */ + return makePGresult(conn, pname); + break; + case 'D': /* copy command began successfully */ + return makeEmptyPGresult(conn,PGRES_COPY_IN); + break; + case 'B': /* copy command began successfully */ + return makeEmptyPGresult(conn,PGRES_COPY_OUT); + break; + default: + sprintf(conn->errorMessage, + "unknown protocol character %c read from backend\n", + id); + return (PGresult*)NULL; + } /* switch */ +} /* while (1)*/ + +} + +/* + * PQnotifies + * returns a PGnotify* structure of the latest async notification + * that has not yet been handled + * + * returns NULL, if there is currently + * no unhandled async notification from the backend + * + * the CALLER is responsible for FREE'ing the structure returned + */ + +PGnotify* +PQnotifies(PGconn *conn) +{ + Dlelem *e; + if (conn->status != CONNECTION_OK) + return NULL; + /* RemHead returns NULL if list is empy */ + e = DLRemHead(conn->notifyList); + if (e) + return (PGnotify*)DLE_VAL(e); + else + return NULL; +} + +/* + * PQgetline - gets a newline-terminated string from the backend. + * + * Chiefly here so that applications can use "COPY <rel> to stdout" + * and read the output string. Returns a null-terminated string in s. + * + * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips + * the terminating \n (like gets(3)). + * + * RETURNS: + * EOF if it is detected or invalid arguments are given + * 0 if EOL is reached (i.e., \n has been read) + * (this is required for backward-compatibility -- this + * routine used to always return EOF or 0, assuming that + * the line ended within maxlen bytes.) + * 1 in other cases + */ +int +PQgetline(PGconn *conn, char *s, int maxlen) +{ + int c = '\0'; + + if (!conn->Pfin || !s || maxlen <= 1) + return(EOF); + + for (; maxlen > 1 && + (c = pqGetc(conn->Pfin, conn->Pfdebug)) != '\n' && + c != EOF; + --maxlen) { + *s++ = c; + } + *s = '\0'; + + if (c == EOF) { + return(EOF); /* error -- reached EOF before \n */ + } else if (c == '\n') { + return(0); /* done with this line */ + } + return(1); /* returning a full buffer */ +} + + +/* + * PQputline -- sends a string to the backend. + * + * Chiefly here so that applications can use "COPY <rel> from stdin". + * + */ +void +PQputline(PGconn *conn, char *s) +{ + if (conn->Pfout) { + (void) fputs(s, conn->Pfout); + fflush(conn->Pfout); + } +} + +/* + * PQendcopy + * called while waiting for the backend to respond with success/failure + * to a "copy". + * + * RETURNS: + * 0 on failure + * 1 on success + */ +int +PQendcopy(PGconn *conn) +{ + char id; + FILE *Pfin = conn->Pfin; + FILE* Pfdebug = conn->Pfdebug; + + if ( (id = pqGetc(Pfin,Pfdebug)) > 0) + return(0); + switch (id) { + case 'Z': /* backend finished the copy */ + return(1); + case 'E': + case 'N': + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Error return detected from backend, but error message cannot be read"); + } + return(0); + break; + default: + (void) sprintf(conn->errorMessage, + "FATAL: PQendcopy: protocol error: id=%x\n", + id); + fputs(conn->errorMessage, stderr); + fprintf(stderr,"resetting connection\n"); + PQreset(conn); + return(0); + } +} + +/* simply send out max-length number of filler characters to fp */ +static void +fill (int length, int max, char filler, FILE *fp) +{ + int count; + char filltmp[2]; + + filltmp[0] = filler; + filltmp[1] = 0; + count = max - length; + while (count-- >= 0) + { + fprintf(fp, "%s", filltmp); + } + } + + +/* + * PQdisplayTuples() + * + * a better version of PQprintTuples() + * that can optionally do padding of fields with spaces and use different + * field separators + */ +void +PQdisplayTuples(PGresult *res, + FILE *fp, /* where to send the output */ + int fillAlign, /* pad the fields with spaces */ + char *fieldSep, /* field separator */ + int printHeader, /* display headers? */ + int quiet + ) +{ +#define DEFAULT_FIELD_SEP " " + + char *pager; + int i, j; + int nFields; + int nTuples; + int fLength[MAX_FIELDS]; + int usePipe = 0; + + if (fieldSep == NULL) + fieldSep == DEFAULT_FIELD_SEP; + + if (fp == NULL) + fp = stdout; + if (fp == stdout) { + /* try to pipe to the pager program if possible */ + pager=getenv("PAGER"); + if (pager != NULL) { + fp = popen(pager, "w"); + if (fp) { + usePipe = 1; + signal(SIGPIPE, SIG_IGN); + } else + fp = stdout; + } + } + + /* Get some useful info about the results */ + nFields = PQnfields(res); + nTuples = PQntuples(res); + + /* Zero the initial field lengths */ + for (j=0 ; j < nFields; j++) { + fLength[j] = strlen(PQfname(res,j)); + } + /* Find the max length of each field in the result */ + /* will be somewhat time consuming for very large results */ + if (fillAlign) { + for (i=0; i < nTuples; i++) { + for (j=0 ; j < nFields; j++) { + if (PQgetlength(res,i,j) > fLength[j]) + fLength[j] = PQgetlength(res,i,j); + } + } + } + + if (printHeader) { + /* first, print out the attribute names */ + for (i=0; i < nFields; i++) { + fputs(PQfname(res,i), fp); + if (fillAlign) + fill (strlen (PQfname(res,i)), fLength[i], ' ', fp); + fputs(fieldSep,fp); + } + fprintf(fp, "\n"); + + /* Underline the attribute names */ + for (i=0; i < nFields; i++) { + if (fillAlign) + fill (0, fLength[i], '-', fp); + fputs(fieldSep,fp); + } + fprintf(fp, "\n"); + } + + /* next, print out the instances */ + for (i=0; i < nTuples; i++) { + for (j=0 ; j < nFields; j++) { + fprintf(fp, "%s", PQgetvalue(res,i,j)); + if (fillAlign) + fill (strlen (PQgetvalue(res,i,j)), fLength[j], ' ', fp); + fputs(fieldSep,fp); + } + fprintf(fp, "\n"); + } + + if (!quiet) + fprintf (fp, "\nQuery returned %d row%s.\n",PQntuples(res), + (PQntuples(res) == 1) ? "" : "s"); + + fflush(fp); + if (usePipe) { + pclose(fp); + signal(SIGPIPE, SIG_DFL); + } +} + + + +/* + * PQprintTuples() + * + * This is the routine that prints out the tuples that + * are returned from the backend. + * Right now all columns are of fixed length, + * this should be changed to allow wrap around for + * tuples values that are wider. + */ +void +PQprintTuples(PGresult *res, + FILE* fout, /* output stream */ + int PrintAttNames,/* print attribute names or not*/ + int TerseOutput, /* delimiter bars or not?*/ + int colWidth /* width of column, if 0, use variable width */ + ) +{ + int nFields; + int nTups; + int i,j; + char formatString[80]; + + char *tborder = NULL; + + nFields = PQnfields(res); + nTups = PQntuples(res); + + if (colWidth > 0) { + sprintf(formatString,"%%s %%-%ds",colWidth); + } else + sprintf(formatString,"%%s %%s"); + + if ( nFields > 0 ) { /* only print tuples with at least 1 field. */ + + if (!TerseOutput) + { + int width; + width = nFields * 14; + tborder = malloc (width+1); + for (i = 0; i <= width; i++) + tborder[i] = '-'; + tborder[i] = '\0'; + fprintf(fout,"%s\n",tborder); + } + + for (i=0; i < nFields; i++) { + if (PrintAttNames) { + fprintf(fout,formatString, + TerseOutput ? "" : "|", + PQfname(res, i)); + } + } + + if (PrintAttNames) { + if (TerseOutput) + fprintf(fout,"\n"); + else + fprintf(fout, "|\n%s\n",tborder); + } + + for (i = 0; i < nTups; i++) { + for (j = 0; j < nFields; j++) { + char *pval = PQgetvalue(res,i,j); + fprintf(fout, formatString, + TerseOutput ? "" : "|", + pval ? pval : ""); + } + if (TerseOutput) + fprintf(fout,"\n"); + else + fprintf(fout, "|\n%s\n",tborder); + } + } +} + + +/* ---------------- + * PQfn - Send a function call to the POSTGRES backend. + * + * conn : backend connection + * fnid : function id + * result_buf : pointer to result buffer (&int if integer) + * result_len : length of return value. + * actual_result_len: actual length returned. (differs from result_len + * for varlena structures.) + * result_type : If the result is an integer, this must be 1, + * otherwise this should be 0 + * args : pointer to a NULL terminated arg array. + * (length, if integer, and result-pointer) + * nargs : # of arguments in args array. + * + * RETURNS + * NULL on failure. PQerrormsg will be set. + * "G" if there is a return value. + * "V" if there is no return value. + * ---------------- + */ + +PGresult* +PQfn(PGconn *conn, + int fnid, + int *result_buf, + int *actual_result_len, + int result_is_int, + PQArgBlock *args, + int nargs) +{ + FILE *Pfin = conn->Pfin; + FILE *Pfout = conn->Pfout; + FILE* Pfdebug = conn->Pfdebug; + int id; + int i; + + /* clear the error string */ + conn->errorMessage[0] = '\0'; + + pqPuts("F ",Pfout,Pfdebug); /* function */ + pqPutInt(fnid, 4, Pfout, Pfdebug); /* function id */ + pqPutInt(nargs, 4, Pfout, Pfdebug); /* # of args */ + + for (i = 0; i < nargs; ++i) { /* len.int4 + contents */ + pqPutInt(args[i].len, 4, Pfout, Pfdebug); + if (args[i].isint) { + pqPutInt(args[i].u.integer, 4, Pfout, Pfdebug); + } else { + pqPutnchar((char *)args[i].u.ptr, args[i].len, Pfout, Pfdebug); + } + } + pqFlush(Pfout, Pfdebug); + + id = pqGetc(Pfin, Pfdebug); + if (id != 'V') { + if (id == 'E') { + pqGets(conn->errorMessage,ERROR_MSG_LENGTH,Pfin,Pfdebug); + } else + sprintf(conn->errorMessage, + "PQfn: expected a 'V' from the backend. Got '%c' instead", + id); + return makeEmptyPGresult(conn,PGRES_FATAL_ERROR); + } + + id = pqGetc(Pfin, Pfdebug); + for (;;) { + int c; + switch (id) { + case 'G': /* function returned properly */ + pqGetInt(actual_result_len,4,Pfin,Pfdebug); + if (result_is_int) { + pqGetInt(result_buf,4,Pfin,Pfdebug); + } else { + pqGetnchar((char *) result_buf, *actual_result_len, + Pfin, Pfdebug); + } + c = pqGetc(Pfin, Pfdebug); /* get the last '0'*/ + return makeEmptyPGresult(conn,PGRES_COMMAND_OK); + case 'E': + sprintf(conn->errorMessage, + "PQfn: returned an error"); + return makeEmptyPGresult(conn,PGRES_FATAL_ERROR); + case 'N': + /* print notice and go back to processing return values */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Notice return detected from backend, but error message cannot be read"); + } else + fprintf(stderr, "%s\n", conn->errorMessage); + /* keep iterating */ + break; + case '0': /* no return value */ + return makeEmptyPGresult(conn,PGRES_COMMAND_OK); + default: + /* The backend violates the protocol. */ + sprintf(conn->errorMessage, + "FATAL: PQfn: protocol error: id=%x\n", id); + return makeEmptyPGresult(conn,PGRES_FATAL_ERROR); + } + } +} + + + + +/* ====== accessor funcs for PGresult ======== */ + +ExecStatusType +PQresultStatus(PGresult* res) +{ + return res->resultStatus; +} + +int +PQntuples(PGresult *res) +{ + return res->ntups; +} + +int +PQnfields(PGresult *res) +{ + return res->numAttributes; +} + +/* + returns NULL if the field_num is invalid +*/ +char* +PQfname(PGresult *res, int field_num) +{ + if (field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQfname: ERROR! name of field %d(of %d) is not available", + field_num, res->numAttributes -1); + return NULL; + } + if (res->attDescs) { + return res->attDescs[field_num].name; + } else + return NULL; +} + +/* + returns -1 on a bad field name +*/ +int +PQfnumber(PGresult *res, char* field_name) +{ + int i; + + if (field_name == NULL || + field_name[0] == '\0' || + res->attDescs == NULL) + return -1; + + for (i=0;i<res->numAttributes;i++) { + if ( strcmp(field_name, res->attDescs[i].name) == 0 ) + return i; + } + return -1; + +} + +Oid +PQftype(PGresult *res, int field_num) +{ + if (field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQftype: ERROR! type of field %d(of %d) is not available", + field_num, res->numAttributes -1); + } + if (res->attDescs) { + return res->attDescs[field_num].adtid; + } else + return InvalidOid; +} + +int2 +PQfsize(PGresult *res, int field_num) +{ + if (field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQfsize: ERROR! size of field %d(of %d) is not available", + field_num, res->numAttributes -1); + } + if (res->attDescs) { + return res->attDescs[field_num].adtsize; + } else + return 0; +} + +char* PQcmdStatus(PGresult *res) { + return res->cmdStatus; +} + +/* + PQoidStatus - + if the last command was an INSERT, return the oid string + if not, return "" +*/ +char* PQoidStatus(PGresult *res) { + if (!res->cmdStatus) + return ""; + + if (strncmp(res->cmdStatus, "INSERT",6) == 0) { + return res->cmdStatus+7; + } else + return ""; +} + +/* + PQgetvalue: + return the attribute value of field 'field_num' of + tuple 'tup_num' + + If res is binary, then the value returned is NOT a null-terminated + ASCII string, but the binary representation in the server's native + format. + + if res is not binary, a null-terminated ASCII string is returned. +*/ +char* +PQgetvalue(PGresult *res, int tup_num, int field_num) +{ + if (tup_num > (res->ntups - 1) || + field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQgetvalue: ERROR! field %d(of %d) of tuple %d(of %d) is not available", + field_num, res->numAttributes - 1, tup_num, res->ntups); + } + + + return res->tuples[tup_num][field_num].value; +} + +/* PQgetlength: + returns the length of a field value in bytes. If res is binary, + i.e. a result of a binary portal, then the length returned does + NOT include the size field of the varlena. +*/ +int +PQgetlength(PGresult *res, int tup_num, int field_num) +{ + if (tup_num > (res->ntups - 1 )|| + field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQgetlength: ERROR! field %d(of %d) of tuple %d(of %d) is not available", + field_num, res->numAttributes - 1, tup_num, res->ntups); + } + + return res->tuples[tup_num][field_num].len; + } diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c new file mode 100644 index 00000000000..c0c60d4a781 --- /dev/null +++ b/src/interfaces/libpq/fe-lobj.c @@ -0,0 +1,381 @@ +/*------------------------------------------------------------------------- + * + * fe-lobj.c-- + * Front-end large object interface + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include "postgres.h" +#include "libpq-fe.h" +#include "obj/fmgr.h" +#include "libpq/libpq-fs.h" + +#ifndef MAXPATHLEN +#define MAXPATHLEN 1024 +#endif + +#define LO_BUFSIZE 1024 + +/* + * lo_open + * opens an existing large object + * + * returns the file descriptor for use in later lo_* calls + * return -1 upon failure. + */ +int +lo_open(PGconn* conn, Oid lobjId, int mode) +{ + int fd; + int result_len; + PQArgBlock argv[2]; + PGresult *res; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = lobjId; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = mode; + + res = PQfn(conn, F_LO_OPEN,&fd,&result_len,1,argv,2); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + + /* have to do this to reset offset in shared fd cache */ + /* but only if fd is valid */ + if (fd >= 0 && lo_lseek(conn, fd, 0L, SEEK_SET) < 0) + return -1; + return fd; + } else + return -1; +} + +/* + * lo_close + * closes an existing large object + * + * returns 0 upon success + * returns -1 upon failure. + */ +int +lo_close(PGconn *conn, int fd) +{ + PQArgBlock argv[1]; + PGresult *res; + int retval; + int result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + res = PQfn(conn, F_LO_CLOSE,&retval,&result_len,1,argv,1); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return retval; + } else + return -1; +} + +/* + * lo_read + * read len bytes of the large object into buf + * + * returns the length of bytes read. + * the CALLER must have allocated enough space to hold the result returned + */ + +int +lo_read(PGconn *conn, int fd, char *buf, int len) +{ + PQArgBlock argv[2]; + PGresult *res; + int result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = len; + + res = PQfn(conn, F_LOREAD,(int*)buf,&result_len,0,argv,2); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return result_len; + } else + return -1; +} + +/* + * lo_write + * write len bytes of buf into the large object fd + * + */ +int +lo_write(PGconn *conn, int fd, char *buf, int len) +{ + PQArgBlock argv[2]; + PGresult *res; + int result_len; + int retval; + + if (len <= 0) + return 0; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 0; + argv[1].len = len; + argv[1].u.ptr = (int*)buf; + + res = PQfn(conn, F_LOWRITE,&retval,&result_len,1,argv,2); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return retval; + } else + return -1; +} + +/* + * lo_lseek + * change the current read or write location on a large object + * currently, only L_SET is a legal value for whence + * + */ + +int +lo_lseek(PGconn *conn, int fd, int offset, int whence) +{ + PQArgBlock argv[3]; + PGresult *res; + int retval; + int result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = offset; + + argv[2].isint = 1; + argv[2].len = 4; + argv[2].u.integer = whence; + + res = PQfn(conn, F_LO_LSEEK,&retval,&result_len,1,argv,3); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return retval; + } else + return -1; +} + +/* + * lo_creat + * create a new large object + * the mode is a bitmask describing different attributes of the new object + * + * returns the oid of the large object created or + * InvalidOid upon failure + */ + +Oid +lo_creat(PGconn *conn, int mode) +{ + PQArgBlock argv[1]; + PGresult *res; + int retval; + int result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = mode; + res = PQfn(conn, F_LO_CREAT,&retval,&result_len,1,argv,1); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return (Oid)retval; + } else + return InvalidOid; +} + + +/* + * lo_tell + * returns the current seek location of the large object + * + */ + +int +lo_tell(PGconn *conn, int fd) +{ + int retval; + PQArgBlock argv[1]; + PGresult *res; + int result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + res = PQfn(conn, F_LO_TELL,&retval,&result_len,1,argv,1); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return retval; + } else + return -1; +} + +/* + * lo_unlink + * delete a file + * + */ + +int +lo_unlink(PGconn *conn, Oid lobjId) +{ + PQArgBlock argv[1]; + PGresult *res; + int result_len; + int retval; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = lobjId; + + res = PQfn(conn, F_LO_UNLINK,&retval,&result_len,1,argv,1); + if (PQresultStatus(res) == PGRES_COMMAND_OK) { + PQclear(res); + return retval; + } else + return -1; +} + +/* + * lo_import - + * imports a file as an (inversion) large object. + * returns the oid of that object upon success, + * returns InvalidOid upon failure + * + */ + +Oid +lo_import(PGconn *conn, char* filename) +{ + int fd; + int nbytes, tmp; + char buf[LO_BUFSIZE]; + Oid lobjOid; + int lobj; + + /* + * open the file to be read in + */ + fd = open(filename, O_RDONLY, 0666); + if (fd < 0) { /* error */ + sprintf(conn->errorMessage, + "lo_import: can't open unix file\"%s\"\n", filename); + return InvalidOid; + } + + /* + * create an inversion "object" + */ + lobjOid = lo_creat(conn, INV_READ|INV_WRITE); + if (lobjOid == InvalidOid) { + sprintf(conn->errorMessage, + "lo_import: can't create inv object for \"%s\"", filename); + return InvalidOid; + } + + lobj = lo_open(conn, lobjOid, INV_WRITE); + if (lobj == -1) { + sprintf(conn->errorMessage, + "lo_import: could not open inv object oid %d",lobjOid); + return InvalidOid; + } + /* + * read in from the Unix file and write to the inversion file + */ + while ((nbytes = read(fd, buf, LO_BUFSIZE)) > 0) { + tmp = lo_write(conn,lobj, buf, nbytes); + if (tmp < nbytes) { + sprintf(conn->errorMessage, + "lo_import: error while reading \"%s\"",filename); + return InvalidOid; + } + } + + (void) close(fd); + (void) lo_close(conn, lobj); + + return lobjOid; +} + +/* + * lo_export - + * exports an (inversion) large object. + * returns -1 upon failure, 1 otherwise + */ +int +lo_export(PGconn *conn, Oid lobjId, char *filename) +{ + int fd; + int nbytes, tmp; + char buf[LO_BUFSIZE]; + int lobj; + + /* + * create an inversion "object" + */ + lobj = lo_open(conn, lobjId, INV_READ); + if (lobj == -1) { + sprintf(conn->errorMessage, + "lo_export: can't open inv object %d",lobjId); + return -1; + } + + /* + * open the file to be written to + */ + fd = open(filename, O_CREAT|O_WRONLY, 0666); + if (fd < 0) { /* error */ + sprintf(conn->errorMessage, + "lo_export: can't open unix file\"%s\"",filename); + return 0; + } + + /* + * read in from the Unix file and write to the inversion file + */ + while ((nbytes = lo_read(conn, lobj, buf, LO_BUFSIZE)) > 0) { + tmp = write(fd, buf, nbytes); + if (tmp < nbytes) { + sprintf(conn->errorMessage, + "lo_export: error while writing \"%s\"", + filename); + return -1; + } + } + + (void) lo_close(conn,lobj); + (void) close(fd); + + return 1; +} diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c new file mode 100644 index 00000000000..c8b703b9d33 --- /dev/null +++ b/src/interfaces/libpq/fe-misc.c @@ -0,0 +1,193 @@ +/*------------------------------------------------------------------------- + * + * FILE + * fe-misc.c + * + * DESCRIPTION + * miscellaneous useful functions + * these routines are analogous to the ones in libpq/pqcomm.c + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <stdlib.h> +#include <stdio.h> + +/* pqGetc: + get a character from stream f + + if debug is set, also echo the character fetched +*/ +int +pqGetc(FILE* fin, FILE* debug) +{ + int c; + + c = getc(fin); + if (debug && c != EOF) + putc(c,debug); + return c; +} + +/* pqPutnchar: + send a string of exactly len length into stream f + + returns 1 if there was an error, 0 otherwise. +*/ +int +pqPutnchar(char* s, int len, FILE *f, FILE *debug) +{ + int status; + + if (f == NULL) + return 1; + + while (len--) { + status = fputc(*s,f); + if (debug) + fputc(*s,debug); + s++; + if (status == EOF) + return 1; + } + return 0; +} + +/* pqGetnchar: + get a string of exactly len length from stream f +*/ +int +pqGetnchar(char* s, int len, FILE *f, FILE *debug) +{ + int c; + + if (f == NULL) + return 1; + + while (len-- && (c = getc(f)) != EOF) + *s++ = c; + *s = '\0'; + + if (debug) { + fputs(s,debug); + } + return 0; +} + +/* pqGets: + get a string of up to length len from stream f +*/ +int +pqGets(char* s, int len, FILE *f, FILE *debug) +{ + int c; + + if (f == NULL) + return 1; + + while (len-- && (c = getc(f)) != EOF && c) + *s++ = c; + *s = '\0'; + + if (debug) { + fputs(s,debug); + } + return 0; +} + + +/* pgPutInt + send an integer of up to 4 bytesto the file stream + do this one byte at at time. + This insures that machines with different ENDIANness can talk to each other + get a n-byte integer from the stream into result + returns 0 if successful, 1 otherwise +*/ +int +pqPutInt(int i, int bytes, FILE* f, FILE *debug) +{ + int status; + + if (bytes > 4) + bytes = 4; + + while (bytes--) { + status = fputc(i & 0xff, f); + if (debug) + fputc(i & 0xff, debug); + i >>= 8; + if (status == EOF) { + return 1; + } + } + return 0; +} + +/* pgGetInt + reconstructs the integer one byte at a time. + This insures that machines with different ENDIANness can talk to each other + get a n-byte integer from the stream into result + returns 0 if successful +*/ +int +pqGetInt(int* result, int bytes, FILE* f, FILE *debug) +{ + int c; + int p; + int n; + + if (f == NULL) + return 1; + + p = 0; + n = 0; + while (bytes && (c = getc(f)) != EOF) + { + n |= (c & 0xff) << p; + p += 8; + bytes--; + } + + if (bytes != 0) + return 1; + + *result = n; + if (debug) + fprintf(debug,"%d",*result); + return 0; +} + + +int +pqPuts(char* s, FILE *f, FILE *debug) +{ + if (f == NULL) + return 1; + + if (fputs(s,f) == EOF) + return 1; + + fputc('\0',f); /* important to send an ending EOF since backend expects it */ + fflush(f); + + if (debug) { + fputs(s,debug); + } + return 0; +} + + +void +pqFlush(FILE *f, FILE *debug) +{ + if (f) + fflush(f); + if (debug) + fflush(debug); +} diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h new file mode 100644 index 00000000000..af3413b4375 --- /dev/null +++ b/src/interfaces/libpq/libpq-fe.h @@ -0,0 +1,251 @@ +/*------------------------------------------------------------------------- + * + * libpq-fe.h-- + * This file contains definitions for structures and + * externs for functions used by frontend postgres applications. + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: libpq-fe.h,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef LIBPQ_FE_H +#define LIBPQ_FE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------- + * include stuff common to fe and be + * ---------------- + */ +/* #include "libpq/libpq.h" */ +#include "libpq/pqcomm.h" +#include "lib/dllist.h" + +typedef enum {CONNECTION_OK, + CONNECTION_BAD} ConnStatusType; + +typedef enum { + PGRES_EMPTY_QUERY = 0, + PGRES_COMMAND_OK, /* a query command that doesn't return */ + /* anything was executed properly by the backend */ + PGRES_TUPLES_OK, /* a query command that returns tuples */ + /* was executed properly by the backend, PGresult */ + /* contains the resulttuples */ + PGRES_COPY_OUT, + PGRES_COPY_IN, + PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from the backend */ + PGRES_NONFATAL_ERROR, + PGRES_FATAL_ERROR + +} ExecStatusType; + +/* string descriptions of the ExecStatusTypes */ +extern char* pgresStatus[]; + +/* + * POSTGRES backend dependent Constants. + */ + +/* ERROR_MSG_LENGTH should really be the same as ELOG_MAXLEN in utils/elog.h*/ +#define ERROR_MSG_LENGTH 4096 +#define COMMAND_LENGTH 20 +#define REMARK_LENGTH 80 +#define PORTAL_NAME_LENGTH 16 + +/* ---------------- + * PQArgBlock -- + * Information (pointer to array of this structure) required + * for the PQfn() call. + * ---------------- + */ +typedef struct { + int len; + int isint; + union { + int *ptr; /* can't use void (dec compiler barfs) */ + int integer; + } u; +} PQArgBlock; + +typedef struct pgresAttDesc { + char* name; /* type name */ + Oid adtid; /* type id */ + int2 adtsize; /* type size */ +} PGresAttDesc; + +/* use char* for Attribute values, + ASCII tuples are guaranteed to be null-terminated + For binary tuples, the first four bytes of the value is the size, + and the bytes afterwards are the value. The binary value is + not guaranteed to be null-terminated. In fact, it can have embedded nulls*/ +typedef struct pgresAttValue { + int len; /* length in bytes of the value */ + char *value; /* actual value */ +} PGresAttValue; + +typedef struct pgNotify { + char relname[NAMEDATALEN]; /* name of relation containing data */ + int be_pid; /* process id of backend */ +} PGnotify; + +/* PGconn encapsulates a connection to the backend */ +typedef struct pg_conn{ + char *pghost; /* the machine on which the server is running */ + char *pgtty; /* tty on which the backend messages is displayed */ + char *pgport; /* the communication port with the backend */ + char *pgoptions; /* options to start the backend with */ + char *dbName; /* database name */ + ConnStatusType status; + char errorMessage[ERROR_MSG_LENGTH]; + /* pipes for be/fe communication */ + FILE *Pfin; + FILE *Pfout; + FILE *Pfdebug; + void *port; /* really a Port* */ + int asyncNotifyWaiting; + Dllist* notifyList; +} PGconn; + +#define CMDSTATUS_LEN 40 + +/* PGresult encapsulates the result of a query */ +/* unlike the old libpq, we assume that queries only return in one group */ +typedef struct pg_result{ + int ntups; + int numAttributes; + PGresAttDesc *attDescs; + PGresAttValue* *tuples; /* each PGresTuple is an array of PGresAttValue's */ + int tupArrSize; /* size of tuples array allocated */ + ExecStatusType resultStatus; + char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the last insert query*/ + int binary; /* binary tuple values if binary == 1, otherwise ASCII */ + PGconn* conn; +} PGresult; + + +/* === in fe-connect.c === */ + /* make a new client connection to the backend */ +extern PGconn* PQsetdb(char* pghost, char* pgport, char* pgoptions, + char* pgtty, char* dbName); + /* close the current connection and free the PGconn data structure */ +extern void PQfinish(PGconn* conn); + /* close the current connection and restablish a new one with the same + parameters */ +extern void PQreset(PGconn* conn); + +extern char* PQdb(PGconn* conn); +extern char* PQhost(PGconn* conn); +extern char* PQoptions(PGconn* conn); +extern char* PQport(PGconn* conn); +extern char* PQtty(PGconn* conn); +extern ConnStatusType PQstatus(PGconn* conn); +extern char* PQerrorMessage(PGconn* conn); +extern void PQtrace(PGconn *conn, FILE* debug_port); +extern void PQuntrace(PGconn *conn); + +/* === in fe-exec.c === */ +extern PGresult* PQexec(PGconn* conn, char* query); +extern int PQgetline(PGconn *conn, char* string, int length); +extern int PQendcopy(PGconn *conn); +extern void PQputline(PGconn *conn, char* string); +extern ExecStatusType PQresultStatus(PGresult* res); +extern int PQntuples(PGresult *res); +extern int PQnfields(PGresult *res); +extern char* PQfname(PGresult *res, int field_num); +extern int PQfnumber(PGresult *res, char* field_name); +extern Oid PQftype(PGresult *res, int field_num); +extern int2 PQfsize(PGresult *res, int field_num); +extern char* PQcmdStatus(PGresult *res); +extern char* PQoidStatus(PGresult *res); +extern char* PQgetvalue(PGresult *res, int tup_num, int field_num); +extern int PQgetlength(PGresult *res, int tup_num, int field_num); +extern void PQclear(PGresult* res); +/* PQdisplayTuples() is a better version of PQprintTuples() */ +extern void PQdisplayTuples(PGresult *res, + FILE *fp, /* where to send the output */ + int fillAlign, /* pad the fields with spaces */ + char *fieldSep, /* field separator */ + int printHeader, /* display headers? */ + int quiet); +extern void PQprintTuples(PGresult* res, + FILE* fout, /* output stream */ + int printAttName,/* print attribute names or not*/ + int terseOutput, /* delimiter bars or not?*/ + int width /* width of column, + if 0, use variable width */ + ); +extern PGnotify* PQnotifies(PGconn *conn); +extern PGresult* PQfn(PGconn* conn, + int fnid, + int *result_buf, + int *result_len, + int result_is_int, + PQArgBlock *args, + int nargs); +/* === in fe-auth.c === */ +extern MsgType fe_getauthsvc(char* PQerrormsg); +extern void fe_setauthsvc(char *name, char* PQerrormsg); +extern char *fe_getauthname(char* PQerrormsg); + +/* === in fe-misc.c === */ +/* pqGets and pqPuts gets and sends strings to the file stream + returns 0 if successful + if debug is non-null, debugging output is sent to that stream +*/ +extern int pqGets(char* s, int maxlen, FILE* stream, FILE* debug); +extern int pqGetnchar(char* s, int maxlen, FILE* stream, FILE* debug); +extern int pqPutnchar(char* s, int maxlen, FILE* stream, FILE* debug); +extern int pqPuts(char* s, FILE* stream, FILE* debug ); +extern int pqGetc(FILE* stream, FILE *debug); +/* get a n-byte integer from the stream into result */ +/* returns 0 if successful */ +extern int pqGetInt(int* result, int bytes, FILE* stream, FILE *debug ); +/* put a n-byte integer into the stream */ +/* returns 0 if successful */ +extern int pqPutInt(int n, int bytes, FILE* stream, FILE *debug ); +extern void pqFlush(FILE* stream, FILE* debug); + +/* === in fe-lobj.c === */ +int lo_open(PGconn* conn, Oid lobjId, int mode); +int lo_close(PGconn *conn, int fd); +int lo_read(PGconn *conn, int fd, char *buf, int len); +int lo_write(PGconn *conn, int fd, char *buf, int len); +int lo_lseek(PGconn *conn, int fd, int offset, int whence); +Oid lo_creat(PGconn *conn, int mode); +int lo_tell(PGconn *conn, int fd); +int lo_unlink(PGconn *conn, Oid lobjId); +Oid lo_import(PGconn *conn, char *filename); +int lo_export(PGconn *conn, Oid lobjId, char *filename); +/* max length of message to send */ +#define MAX_MESSAGE_LEN 8193 + +/* maximum number of fields in a tuple */ +#define BYTELEN 8 +#define MAX_FIELDS 512 + +/* fall back options if they are not specified by arguments or defined + by environment variables */ +#define DefaultHost "localhost" +#define DefaultTty "" +#define DefaultOption "" + +typedef void *TUPLE; +#define palloc malloc +#define pfree free + +#if defined(PORTNAME_sparc) +extern char *sys_errlist[]; +#define strerror(A) (sys_errlist[(A)]) +#endif /* PORTNAME_sparc */ + +#ifdef __cplusplus +}; +#endif + +#endif /* LIBPQ_FE_H */ + diff --git a/src/interfaces/libpq/pg_hba b/src/interfaces/libpq/pg_hba new file mode 100644 index 00000000000..22a83beb091 --- /dev/null +++ b/src/interfaces/libpq/pg_hba @@ -0,0 +1,13 @@ +# +# Example config file for Postgres95 host based access +# +# Lines starting with "all" apply to all databases. Otherwise the first +# column has to match the name of the database being connected to. Up to +# ten config lines can apply to each database. Mask specifies bits that +# aren't counted. After those bits are taken out, the connection address +# must match the address in the middle column. +# +# <name> <address> <mask> +# +all 127.0.0.1 0.0.0.0 + diff --git a/src/interfaces/libpq/pqsignal.c b/src/interfaces/libpq/pqsignal.c new file mode 100644 index 00000000000..638c494eda7 --- /dev/null +++ b/src/interfaces/libpq/pqsignal.c @@ -0,0 +1,40 @@ +/*------------------------------------------------------------------------- + * + * pqsignal.c-- + * reliable BSD-style signal(2) routine stolen from RWW who stole it + * from Stevens... + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/pqsignal.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + * NOTES + * This shouldn't be in libpq, but the monitor and some other + * things need it... + * + *------------------------------------------------------------------------- + */ +#include "libpq/pqsignal.h" + +pqsigfunc +pqsignal(int signo, pqsigfunc func) +{ +#if defined(USE_POSIX_SIGNALS) + struct sigaction act, oact; + + act.sa_handler = func; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + if (signo != SIGALRM) { + act.sa_flags |= SA_RESTART; + } + if (sigaction(signo, &act, &oact) < 0) + return(SIG_ERR); + return(oact.sa_handler); +#else /* !USE_POSIX_SIGNALS */ + exit(1); /* this should never be reached, pqsignal should only + be called if USE_POSIX_SIGNALS is true*/ +#endif /* !USE_POSIX_SIGNALS */ +} diff --git a/src/interfaces/libpq/pqsignal.h b/src/interfaces/libpq/pqsignal.h new file mode 100644 index 00000000000..77d01b0a8eb --- /dev/null +++ b/src/interfaces/libpq/pqsignal.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * pqsignal.h-- + * prototypes for the reliable BSD-style signal(2) routine. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pqsignal.h,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + * NOTES + * This shouldn't be in libpq, but the monitor and some other + * things need it... + * + *------------------------------------------------------------------------- + */ +#ifndef PQSIGNAL_H +#define PQSIGNAL_H + +#include <signal.h> + +#include "c.h" + +typedef void (*pqsigfunc)(int); + +extern pqsigfunc pqsignal(int signo, pqsigfunc func); + +#if defined(USE_POSIX_SIGNALS) +#define signal(signo, handler) pqsignal(signo, (pqsigfunc)(handler)) +#endif /* USE_POSIX_SIGNALS */ + +#endif /* PQSIGNAL_H */ |