problems with large objects - Mailing list pgsql-interfaces
From | Wolfgang Hottgenroth |
---|---|
Subject | problems with large objects |
Date | |
Msg-id | 37C6C901.B4EF504@ieee.org Whole thread Raw |
List | pgsql-interfaces |
Hi. I use the postgreSQL-database-system and esspecially I do some work with the large-objects, which I use through the Perl-library. Unfortunately since I switched from 6.4.2 to 6.5.1 my tools refused to work. lo_import didn't work anymore. I traced it down through the libpq, where I found that a test with lo_lseek in the lo_open-function (file: fe-lobj.c) fails. Going deeper into the source and drawing a trace of debug-output across some files I switched tracing from the libpq to the backend. Here I found in be-fsstubs.c, that after the function lo_open sets a cookie using the function newLOfd, just this cookie will be cleared by lo_commit, which is called by CommitTransaction (in xact.c). The handle, handed out to the application by the library-function lo_open is now not longer valid and the next access through this handle fails. I attach the test-application (in Perl, named db_test), the debug-output of the application (and the outputs in the libpq) (db_test2.log), the debug-output of postmaster (post2.log) and the files I patched with my debug-output (you will find them easily because in every line I changed there is the string 'WN'). Did anyone of you ever see this problem? Is there something wrong with my application (which works fine with 6.4.2)? I hope someone of you find time to have a look on the files and also I hope he or she can help my. I want to use the postgreSQL on an Alpha, and 6.4.2 did not run on the Alpha (o yes, when I remove all optimisation it does). (But don't be confused: all the test I did runs on an Intel-Machine.) Thanks, Wolfgang Hottgenroth /*------------------------------------------------------------------------- * * be-fsstubs.c * support for filesystem operations on large objects * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /usr/local/cvsroot/pgsql/src/backend/libpq/be-fsstubs.c,v 1.35 1999/06/04 21:13:38 tgl Exp $ * * NOTES * This should be moved to a more appropriate place. It is here * for lack of a better place. * * Builtin functions for open/close/read/write operations on large objects. * * These functions operate in a private GlobalMemoryContext, which means * that large object descriptors hang around until we destroy the context. * That happens in lo_commit(). It'd be possible to prolong the lifetime * of the context so that LO FDs are good across transactions (for example, * we could release the context only if we see that no FDs remain open). * But we'd need additional state in order to do the right thing at the * end of an aborted transaction. FDs opened during an aborted xact would * still need to be closed, since they might not be pointing at valid * relations at all. For now, we'll stick with the existing documented * semantics of LO FDs: they're only good within a transaction. * *------------------------------------------------------------------------- */ #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> #include <postgres.h> #include <lib/dllist.h> #include <libpq/libpq.h> #include <libpq/libpq-fs.h> #include <nodes/nodes.h> #include <utils/memutils.h> #include <lib/fstack.h> #include <utils/mcxt.h> #include <catalog/pg_shadow.h> /* for superuser() */ #include <storage/fd.h> /* for O_ */ #include <storage/large_object.h> #include <libpq/be-fsstubs.h> /* [PA] is Pascal André <andre@via.ecp.fr> */ /*#define FSDB 1*/ #define MAX_LOBJ_FDS 256 #define BUFSIZE 1024 #define FNAME_BUFSIZE 8192 static LargeObjectDesc *cookies[MAX_LOBJ_FDS]; static GlobalMemory fscxt = NULL; static int newLOfd(LargeObjectDesc *lobjCookie); static void deleteLOfd(int fd); /***************************************************************************** * File Interfaces for Large Objects *****************************************************************************/ int lo_open(Oid lobjId, int mode) { LargeObjectDesc *lobjDesc; int fd; MemoryContext currentContext; #if 1 //FSDB elog(NOTICE, "LOopen(%u,%d)", lobjId, mode); #endif if (fscxt == NULL) fscxt = CreateGlobalMemory("Filesystem"); currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); lobjDesc = inv_open(lobjId, mode); elog(NOTICE, "WN: be-fsstubs: lo_open: lobjDesc: %x", lobjDesc); if (lobjDesc == NULL) { /* lookup failed */ MemoryContextSwitchTo(currentContext); #if 1 //FSDB elog(NOTICE, "cannot open large object %u", lobjId); #endif return -1; } fd = newLOfd(lobjDesc); elog(NOTICE, "WN: be-fsstubs: lo_open: fd: %x", fd); /* switch context back to orig. */ MemoryContextSwitchTo(currentContext); #if 1 //FSDB if (fd < 0) /* newLOfd couldn't find a slot */ elog(NOTICE, "Out of space for large object FDs"); #endif elog(NOTICE, "WN: be-fsstubs: lo_open: return: %x", fd); return fd; } int lo_close(int fd) { MemoryContext currentContext; if (fd < 0 || fd >= MAX_LOBJ_FDS) { elog(ERROR, "lo_close: large obj descriptor (%d) out of range", fd); return -2; } if (cookies[fd] == NULL) { elog(ERROR, "lo_close: invalid large obj descriptor (%d)", fd); return -3; } #if 1 //FSDB elog(NOTICE, "LOclose(%d)", fd); #endif Assert(fscxt != NULL); currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); inv_close(cookies[fd]); MemoryContextSwitchTo(currentContext); deleteLOfd(fd); return 0; } /* * We assume the large object supports byte oriented reads and seeks so * that our work is easier. */ int lo_read(int fd, char *buf, int len) { MemoryContext currentContext; int status; if (fd < 0 || fd >= MAX_LOBJ_FDS) { elog(ERROR, "lo_read: large obj descriptor (%d) out of range", fd); return -2; } if (cookies[fd] == NULL) { elog(ERROR, "lo_read: invalid large obj descriptor (%d)", fd); return -3; } Assert(fscxt != NULL); currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); status = inv_read(cookies[fd], buf, len); MemoryContextSwitchTo(currentContext); return (status); } int lo_write(int fd, char *buf, int len) { MemoryContext currentContext; int status; if (fd < 0 || fd >= MAX_LOBJ_FDS) { elog(ERROR, "lo_write: large obj descriptor (%d) out of range", fd); return -2; } if (cookies[fd] == NULL) { elog(ERROR, "lo_write: invalid large obj descriptor (%d)", fd); return -3; } Assert(fscxt != NULL); currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); status = inv_write(cookies[fd], buf, len); MemoryContextSwitchTo(currentContext); return (status); } int lo_lseek(int fd, int offset, int whence) { MemoryContext currentContext; int status; elog(NOTICE, "WN: be-fsstubs: lo_lseek, fd: %d", fd); if (fd < 0 || fd >= MAX_LOBJ_FDS) { elog(ERROR, "lo_lseek: large obj descriptor (%d) out of range", fd); return -2; } if (cookies[fd] == NULL) { elog(NOTICE, "WN: be-fsstubs: lo_lseek: cookies[%u]=%x", fd, cookies[fd]); elog(ERROR, "lo_lseek: invalid large obj descriptor (%d)", fd); return -3; } Assert(fscxt != NULL); currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); status = inv_seek(cookies[fd], offset, whence); MemoryContextSwitchTo(currentContext); return status; } Oid lo_creat(int mode) { LargeObjectDesc *lobjDesc; MemoryContext currentContext; Oid lobjId; if (fscxt == NULL) fscxt = CreateGlobalMemory("Filesystem"); currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); lobjDesc = inv_create(mode); if (lobjDesc == NULL) { MemoryContextSwitchTo(currentContext); return InvalidOid; } lobjId = RelationGetRelid(lobjDesc->heap_r); inv_close(lobjDesc); /* switch context back to original memory context */ MemoryContextSwitchTo(currentContext); return lobjId; } int lo_tell(int fd) { if (fd < 0 || fd >= MAX_LOBJ_FDS) { elog(ERROR, "lo_tell: large object descriptor (%d) out of range", fd); return -2; } if (cookies[fd] == NULL) { elog(ERROR, "lo_tell: invalid large object descriptor (%d)", fd); return -3; } /* * We assume we do not need to switch contexts for inv_tell. * That is true for now, but is probably more than this module * ought to assume... */ return inv_tell(cookies[fd]); } int lo_unlink(Oid lobjId) { /* * inv_destroy does not need a context switch, indeed it doesn't * touch any LO-specific data structures at all. (Again, that's * probably more than this module ought to be assuming.) * * XXX there ought to be some code to clean up any open LOs that * reference the specified relation... as is, they remain "open". */ return inv_destroy(lobjId); } /***************************************************************************** * Read/Write using varlena *****************************************************************************/ struct varlena * loread(int fd, int len) { struct varlena *retval; int totalread = 0; retval = (struct varlena *) palloc(VARHDRSZ + len); totalread = lo_read(fd, VARDATA(retval), len); VARSIZE(retval) = totalread + VARHDRSZ; return retval; } int lowrite(int fd, struct varlena * wbuf) { int totalwritten; int bytestowrite; bytestowrite = VARSIZE(wbuf) - VARHDRSZ; totalwritten = lo_write(fd, VARDATA(wbuf), bytestowrite); return totalwritten; } /***************************************************************************** * Import/Export of Large Object *****************************************************************************/ /* * lo_import - * imports a file as an (inversion) large object. */ Oid lo_import(text *filename) { File fd; int nbytes, tmp; char buf[BUFSIZE]; char fnamebuf[FNAME_BUFSIZE]; LargeObjectDesc *lobj; Oid lobjOid; #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS if (!superuser()) elog(ERROR, "You must have Postgres superuser privilege to use " "server-side lo_import().\n\tAnyone can use the " "client-side lo_import() provided by libpq."); #endif /* * open the file to be read in */ nbytes = VARSIZE(filename) - VARHDRSZ + 1; if (nbytes > FNAME_BUFSIZE) nbytes = FNAME_BUFSIZE; StrNCpy(fnamebuf, VARDATA(filename), nbytes); #ifndef __CYGWIN32__ fd = PathNameOpenFile(fnamebuf, O_RDONLY, 0666); #else fd = PathNameOpenFile(fnamebuf, O_RDONLY | O_BINARY, 0666); #endif if (fd < 0) { /* error */ elog(ERROR, "lo_import: can't open unix file \"%s\": %m", fnamebuf); } /* * create an inversion "object" */ lobj = inv_create(INV_READ | INV_WRITE); if (lobj == NULL) { elog(ERROR, "lo_import: can't create inv object for \"%s\"", fnamebuf); } /* * the oid for the large object is just the oid of the relation * XInv??? which contains the data. */ lobjOid = RelationGetRelid(lobj->heap_r); /* * read in from the Unix file and write to the inversion file */ while ((nbytes = FileRead(fd, buf, BUFSIZE)) > 0) { tmp = inv_write(lobj, buf, nbytes); if (tmp < nbytes) elog(ERROR, "lo_import: error while reading \"%s\"", fnamebuf); } FileClose(fd); inv_close(lobj); return lobjOid; } /* * lo_export - * exports an (inversion) large object. */ int4 lo_export(Oid lobjId, text *filename) { File fd; int nbytes, tmp; char buf[BUFSIZE]; char fnamebuf[FNAME_BUFSIZE]; LargeObjectDesc *lobj; mode_t oumask; #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS if (!superuser()) elog(ERROR, "You must have Postgres superuser privilege to use " "server-side lo_export().\n\tAnyone can use the " "client-side lo_export() provided by libpq."); #endif /* * open the inversion "object" */ lobj = inv_open(lobjId, INV_READ); if (lobj == NULL) elog(ERROR, "lo_export: can't open inv object %u", lobjId); /* * open the file to be written to * * Note: we reduce backend's normal 077 umask to the slightly * friendlier 022. This code used to drop it all the way to 0, * but creating world-writable export files doesn't seem wise. */ nbytes = VARSIZE(filename) - VARHDRSZ + 1; if (nbytes > FNAME_BUFSIZE) nbytes = FNAME_BUFSIZE; StrNCpy(fnamebuf, VARDATA(filename), nbytes); oumask = umask((mode_t) 0022); #ifndef __CYGWIN32__ fd = PathNameOpenFile(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC, 0666); #else fd = PathNameOpenFile(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0666); #endif umask(oumask); if (fd < 0) { /* error */ elog(ERROR, "lo_export: can't open unix file \"%s\": %m", fnamebuf); } /* * read in from the Unix file and write to the inversion file */ while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0) { tmp = FileWrite(fd, buf, nbytes); if (tmp < nbytes) elog(ERROR, "lo_export: error while writing \"%s\"", fnamebuf); } inv_close(lobj); FileClose(fd); return 1; } /* * lo_commit - * prepares large objects for transaction commit [PA, 7/17/98] */ void lo_commit(bool isCommit) { int i; MemoryContext currentContext; elog(NOTICE, "WN: be-fsstubs: lo_commit, isCommit: %u", isCommit); if (fscxt == NULL) return; /* no LO operations in this xact */ currentContext = MemoryContextSwitchTo((MemoryContext) fscxt); /* Clean out still-open index scans (not necessary if aborting) * and clear cookies array so that LO fds are no longer good. */ for (i = 0; i < MAX_LOBJ_FDS; i++) { if (cookies[i] != NULL) { if (isCommit) inv_cleanindex(cookies[i]); elog(NOTICE, "WN: be-fsstubs: cookies[%u]=NULL!", i); cookies[i] = NULL; } } MemoryContextSwitchTo(currentContext); /* Release the LO memory context to prevent permanent memory leaks. */ GlobalMemoryDestroy(fscxt); fscxt = NULL; } /***************************************************************************** * Support routines for this file *****************************************************************************/ static int newLOfd(LargeObjectDesc *lobjCookie) { int i; elog(NOTICE, "WN: be-fsstubs: newLOfd: MAX_LOBJ_FDS: %d, lobjCookie: %x", MAX_LOBJ_FDS, lobjCookie); for (i = 0; i < MAX_LOBJ_FDS; i++) { elog(NOTICE, "WN: be-fsstubs: newLOfd: i: %d, cookies: %x", i, cookies[i]); if (cookies[i] == NULL) { elog(NOTICE, "WN: be-fsstubs: newLOfd: cookie found!"); cookies[i] = lobjCookie; elog(NOTICE, "WN: be-fsstubs: newLOfd: cookies[%u]=%x",i,cookies[i]); return i; } } return -1; } static void deleteLOfd(int fd) { cookies[fd] = NULL; } #!/usr/bin/perl -w use Pg; my $conn; #DB_Open(\$conn, "dbname=adrema"); #$conn = Pg::connectdb("dbname=adrema"); $conn = PQsetdb('','','','','adrema'); my $filename = 'test.dat'; my $oid; #$oid = $conn->lo_import($filename); #$oid = PQlo_import($conn, $filename); $oid = PQlo_creat($conn, PGRES_INV_WRITE); print "db_test: lo_creat: oid: $oid\n"; #print $conn->errorMessage; my $fd; $fd = PQlo_open($conn, $oid, PGRES_INV_WRITE); print "db_test: lo_open: fd: $fd\n"; #print $conn->errorMessage; PQfinish($conn); NOTICE: WN: xact: StartTransaction NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 NOTICE: WN: xact: StartTransaction NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 NOTICE: WN: xact: StartTransaction NOTICE: LOopen(19425,131072) NOTICE: WN: be-fsstubs: lo_open: lobjDesc: 821fcd0 NOTICE: WN: be-fsstubs: newLOfd: MAX_LOBJ_FDS: 256, lobjCookie: 821fcd0 NOTICE: WN: be-fsstubs: newLOfd: i: 0, cookies: 0 NOTICE: WN: be-fsstubs: newLOfd: cookie found! NOTICE: WN: be-fsstubs: newLOfd: cookies[0]=821fcd0 NOTICE: WN: be-fsstubs: lo_open: fd: 0 NOTICE: WN: be-fsstubs: lo_open: return: 0 NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 NOTICE: WN: be-fsstubs: cookies[0]=NULL! NOTICE: WN: xact: StartTransaction NOTICE: WN: be-fsstubs: lo_lseek, fd: 0 NOTICE: WN: be-fsstubs: lo_lseek: cookies[0]=0 NOTICE: WN: be-fsstubs: lo_commit, isCommit: 0 fe-exec, PQfn, A fe-exec, PQfn, C: i:0 fe-exec, PQfn, E fe-exec, PQfn, I fe-exec, PQfn, J1: fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: V fe-exec, PQfn, 0 fe-exec, PQfn, 1 fe-exec, PQfn, 2 fe-exec, PQfn, 3: 4 fe-exec, PQfn, 3b: result_buf: 19425 fe-exec, PQfn, 5 fe-exec, PQfn, I fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: Z db_test: lo_creat: oid: 19425 fe-lobj, lo_open: 0 fe-lobj, lo_open: 1a fe-exec, PQfn, A fe-exec, PQfn, C: i:0 fe-exec, PQfn, E fe-exec, PQfn, C: i:1 fe-exec, PQfn, E fe-exec, PQfn, I fe-exec, PQfn, J1: @ fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: V fe-exec, PQfn, 0 fe-exec, PQfn, 1 fe-exec, PQfn, 2 fe-exec, PQfn, 3: 4 fe-exec, PQfn, 3b: result_buf: 0 fe-exec, PQfn, 5 fe-exec, PQfn, I fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: Z fe-lobj, lo_open: 2 (3b8,0) fe-lobj, lo_open: 3 fe-exec, PQfn, A fe-exec, PQfn, C: i:0 fe-exec, PQfn, E fe-exec, PQfn, C: i:1 fe-exec, PQfn, E fe-exec, PQfn, C: i:2 fe-exec, PQfn, E fe-exec, PQfn, I fe-exec, PQfn, J1: @ fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: E fe-exec, PQfn, I fe-exec, PQfn, J1: E fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: N fe-exec, PQfn, I fe-exec, PQfn, J1: N fe-exec, PQfn, I fe-exec, PQfn, J fe-exec, PQfn, ID: Z fe-lobj, lo_open: 3a (0,ffffffff) db_test: lo_open: fd: -1 /*------------------------------------------------------------------------- * * fe-exec.c * functions related to sending a query down to the backend * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /usr/local/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.81 1999/05/28 01:54:53 tgl Exp $ * *------------------------------------------------------------------------- */ #include "libpq-fe.h" #include "libpq-int.h" #include "postgres.h" #ifdef WIN32 #include "win32.h" #else #if !defined(NO_UNISTD_H) #include <unistd.h> #endif #endif #include <stdlib.h> #include <string.h> #include <errno.h> #include <ctype.h> /* keep this in same order as ExecStatusType in libpq-fe.h */ const char *const pgresStatus[] = { "PGRES_EMPTY_QUERY", "PGRES_COMMAND_OK", "PGRES_TUPLES_OK", "PGRES_COPY_OUT", "PGRES_COPY_IN", "PGRES_BAD_RESPONSE", "PGRES_NONFATAL_ERROR", "PGRES_FATAL_ERROR" }; #define DONOTICE(conn,message) \ ((*(conn)->noticeHook) ((conn)->noticeArg, (message))) static int addTuple(PGresult *res, PGresAttValue *tup); static void parseInput(PGconn *conn); static void handleSendFailure(PGconn *conn); static int getRowDescriptions(PGconn *conn); static int getAnotherTuple(PGconn *conn, int binary); static int getNotify(PGconn *conn); static int getNotice(PGconn *conn); /* ---------------- * Space management for PGresult. * * Formerly, libpq did a separate malloc() for each field of each tuple * returned by a query. This was remarkably expensive --- malloc/free * consumed a sizable part of the application's runtime. And there is * no real need to keep track of the fields separately, since they will * all be freed together when the PGresult is released. So now, we grab * large blocks of storage from malloc and allocate space for query data * within these blocks, using a trivially simple allocator. This reduces * the number of malloc/free calls dramatically, and it also avoids * fragmentation of the malloc storage arena. * The PGresult structure itself is still malloc'd separately. We could * combine it with the first allocation block, but that would waste space * for the common case that no extra storage is actually needed (that is, * the SQL command did not return tuples). * We also malloc the top-level array of tuple pointers separately, because * we need to be able to enlarge it via realloc, and our trivial space * allocator doesn't handle that effectively. (Too bad the FE/BE protocol * doesn't tell us up front how many tuples will be returned.) * All other subsidiary storage for a PGresult is kept in PGresult_data blocks * of size PGRESULT_DATA_BLOCKSIZE. The overhead at the start of each block * is just a link to the next one, if any. Free-space management info is * kept in the owning PGresult. * A query returning a small amount of data will thus require three malloc * calls: one for the PGresult, one for the tuples pointer array, and one * PGresult_data block. * Only the most recently allocated PGresult_data block is a candidate to * have more stuff added to it --- any extra space left over in older blocks * is wasted. We could be smarter and search the whole chain, but the point * here is to be simple and fast. Typical applications do not keep a PGresult * around very long anyway, so some wasted space within one is not a problem. * * Tuning constants for the space allocator are: * PGRESULT_DATA_BLOCKSIZE: size of a standard allocation block, in bytes * PGRESULT_ALIGN_BOUNDARY: assumed alignment requirement for binary data * PGRESULT_SEP_ALLOC_THRESHOLD: objects bigger than this are given separate * blocks, instead of being crammed into a regular allocation block. * Requirements for correct function are: * PGRESULT_ALIGN_BOUNDARY must be a multiple of the alignment requirements * of all machine data types. (Currently this is set from configure * tests, so it should be OK automatically.) * PGRESULT_SEP_ALLOC_THRESHOLD + PGRESULT_BLOCK_OVERHEAD <= * PGRESULT_DATA_BLOCKSIZE * pqResultAlloc assumes an object smaller than the threshold will fit * in a new block. * The amount of space wasted at the end of a block could be as much as * PGRESULT_SEP_ALLOC_THRESHOLD, so it doesn't pay to make that too large. * ---------------- */ #ifdef MAX #undef MAX #endif #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define PGRESULT_DATA_BLOCKSIZE 2048 #define PGRESULT_ALIGN_BOUNDARY MAXIMUM_ALIGNOF /* from configure */ #define PGRESULT_BLOCK_OVERHEAD MAX(sizeof(PGresult_data), PGRESULT_ALIGN_BOUNDARY) #define PGRESULT_SEP_ALLOC_THRESHOLD (PGRESULT_DATA_BLOCKSIZE / 2) /* * PQmakeEmptyPGresult * returns a newly allocated, initialized PGresult with given status. * If conn is not NULL and status indicates an error, the conn's * errorMessage is copied. * * Note this is exported --- you wouldn't think an application would need * to build its own PGresults, but this has proven useful in both libpgtcl * and the Perl5 interface, so maybe it's not so unreasonable. */ PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { PGresult *result; result = (PGresult *) malloc(sizeof(PGresult)); result->conn = conn; /* might be NULL */ 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; result->errMsg = NULL; result->null_field[0] = '\0'; result->curBlock = NULL; result->curOffset = 0; result->spaceLeft = 0; if (conn) /* consider copying conn's errorMessage */ { switch (status) { case PGRES_EMPTY_QUERY: case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_COPY_OUT: case PGRES_COPY_IN: /* non-error cases */ break; default: pqSetResultError(result, conn->errorMessage); break; } } return result; } /* * pqResultAlloc - * Allocate subsidiary storage for a PGresult. * * nBytes is the amount of space needed for the object. * If isBinary is true, we assume that we need to align the object on * a machine allocation boundary. * If isBinary is false, we assume the object is a char string and can * be allocated on any byte boundary. */ void * pqResultAlloc(PGresult *res, int nBytes, int isBinary) { char *space; PGresult_data *block; if (!res) return NULL; if (nBytes <= 0) return res->null_field; /* * If alignment is needed, round up the current position to an * alignment boundary. */ if (isBinary) { int offset = res->curOffset % PGRESULT_ALIGN_BOUNDARY; if (offset) { res->curOffset += PGRESULT_ALIGN_BOUNDARY - offset; res->spaceLeft -= PGRESULT_ALIGN_BOUNDARY - offset; } } /* If there's enough space in the current block, no problem. */ if (nBytes <= res->spaceLeft) { space = res->curBlock->space + res->curOffset; res->curOffset += nBytes; res->spaceLeft -= nBytes; return space; } /* * If the requested object is very large, give it its own block; this * avoids wasting what might be most of the current block to start a * new block. (We'd have to special-case requests bigger than the * block size anyway.) The object is always given binary alignment in * this case. */ if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD) { block = (PGresult_data *) malloc(nBytes + PGRESULT_BLOCK_OVERHEAD); if (!block) return NULL; space = block->space + PGRESULT_BLOCK_OVERHEAD; if (res->curBlock) { /* * Tuck special block below the active block, so that we don't * have to waste the free space in the active block. */ block->next = res->curBlock->next; res->curBlock->next = block; } else { /* Must set up the new block as the first active block. */ block->next = NULL; res->curBlock = block; res->spaceLeft = 0; /* be sure it's marked full */ } return space; } /* Otherwise, start a new block. */ block = (PGresult_data *) malloc(PGRESULT_DATA_BLOCKSIZE); if (!block) return NULL; block->next = res->curBlock; res->curBlock = block; if (isBinary) { /* object needs full alignment */ res->curOffset = PGRESULT_BLOCK_OVERHEAD; res->spaceLeft = PGRESULT_DATA_BLOCKSIZE - PGRESULT_BLOCK_OVERHEAD; } else { /* we can cram it right after the overhead pointer */ res->curOffset = sizeof(PGresult_data); res->spaceLeft = PGRESULT_DATA_BLOCKSIZE - sizeof(PGresult_data); } space = block->space + res->curOffset; res->curOffset += nBytes; res->spaceLeft -= nBytes; return space; } /* * pqResultStrdup - * Like strdup, but the space is subsidiary PGresult space. */ char * pqResultStrdup(PGresult *res, const char *str) { char *space = (char *) pqResultAlloc(res, strlen(str) + 1, FALSE); if (space) strcpy(space, str); return space; } /* * pqSetResultError - * assign a new error message to a PGresult */ void pqSetResultError(PGresult *res, const char *msg) { if (!res) return; if (msg && *msg) res->errMsg = pqResultStrdup(res, msg); else res->errMsg = NULL; } /* * PQclear - * free's the memory associated with a PGresult */ void PQclear(PGresult *res) { PGresult_data *block; if (!res) return; /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { res->curBlock = block->next; free(block); } /* Free the top-level tuple pointer array */ if (res->tuples) free(res->tuples); /* Free the PGresult structure itself */ free(res); } /* * Handy subroutine to deallocate any partially constructed async result. */ void pqClearAsyncResult(PGconn *conn) { if (conn->result) PQclear(conn->result); conn->result = NULL; conn->curTuple = NULL; } /* * addTuple * add a row pointer to the PGresult structure, growing it if necessary * Returns TRUE if OK, FALSE if not enough memory to add the row */ static int addTuple(PGresult *res, PGresAttValue *tup) { if (res->ntups >= res->tupArrSize) { /* * Try to grow the array. * * We can use realloc because shallow copying of the structure is * okay. Note that the first time through, res->tuples is NULL. * While ANSI says that realloc() should act like malloc() in that * case, some old C libraries (like SunOS 4.1.x) coredump instead. * On failure realloc is supposed to return NULL without damaging * the existing allocation. Note that the positions beyond * res->ntups are garbage, not necessarily NULL. */ int newSize = (res->tupArrSize > 0) ? res->tupArrSize * 2 : 128; PGresAttValue **newTuples; if (res->tuples == NULL) newTuples = (PGresAttValue **) malloc(newSize * sizeof(PGresAttValue *)); else newTuples = (PGresAttValue **) realloc(res->tuples, newSize * sizeof(PGresAttValue *)); if (!newTuples) return FALSE; /* malloc or realloc failed */ res->tupArrSize = newSize; res->tuples = newTuples; } res->tuples[res->ntups] = tup; res->ntups++; return TRUE; } /* * PQsendQuery * Submit a query, but don't wait for it to finish * * Returns: 1 if successfully submitted * 0 if error (conn->errorMessage is set) */ int PQsendQuery(PGconn *conn, const char *query) { if (!conn) return 0; if (!query) { sprintf(conn->errorMessage, "PQsendQuery() -- query pointer is null."); return 0; } /* check to see if the query string is too long */ if (strlen(query) > MAX_MESSAGE_LEN - 2) { sprintf(conn->errorMessage, "PQsendQuery() -- query is too long. " "Maximum length is %d\n", MAX_MESSAGE_LEN - 2); return 0; } /* Don't try to send if we know there's no live connection. */ if (conn->status != CONNECTION_OK) { sprintf(conn->errorMessage, "PQsendQuery() -- There is no connection " "to the backend.\n"); return 0; } /* Can't send while already busy, either. */ if (conn->asyncStatus != PGASYNC_IDLE) { sprintf(conn->errorMessage, "PQsendQuery() -- another query already in progress."); return 0; } /* clear the error string */ conn->errorMessage[0] = '\0'; /* initialize async result-accumulation state */ conn->result = NULL; conn->curTuple = NULL; /* send the query to the backend; */ /* the frontend-backend protocol uses 'Q' to designate queries */ if (pqPutnchar("Q", 1, conn) || pqPuts(query, conn) || pqFlush(conn)) { handleSendFailure(conn); return 0; } /* OK, it's launched! */ conn->asyncStatus = PGASYNC_BUSY; return 1; } /* * handleSendFailure: try to clean up after failure to send command. * * Primarily, what we want to accomplish here is to process an async * NOTICE message that the backend might have sent just before it died. * * NOTE: this routine should only be called in PGASYNC_IDLE state. */ static void handleSendFailure(PGconn *conn) { /* Preserve the error message emitted by the failing output routine */ char * svErrMsg = strdup(conn->errorMessage); /* * Accept any available input data, ignoring errors. Note that if * pqReadData decides the backend has closed the channel, it will * close our side of the socket --- that's just what we want here. */ while (pqReadData(conn) > 0) /* loop until no more data readable */ ; /* * Parse any available input messages. Since we are in PGASYNC_IDLE * state, only NOTICE and NOTIFY messages will be eaten. */ parseInput(conn); /* Restore error message generated by output routine, if any. */ if (*svErrMsg != '\0') strcpy(conn->errorMessage, svErrMsg); free(svErrMsg); } /* * Consume any available input from the backend * 0 return: some kind of trouble * 1 return: no problem */ int PQconsumeInput(PGconn *conn) { if (!conn) return 0; /* * Load more data, if available. We do this no matter what state we * are in, since we are probably getting called because the * application wants to get rid of a read-select condition. Note that * we will NOT block waiting for more input. */ if (pqReadData(conn) < 0) return 0; /* Parsing of the data waits till later. */ return 1; } /* * parseInput: if appropriate, parse input data from backend * until input is exhausted or a stopping state is reached. * Note that this function will NOT attempt to read more data from the backend. */ static void parseInput(PGconn *conn) { char id; /* * Loop to parse successive complete messages available in the buffer. */ for (;;) { /* * Quit if in COPY_OUT state: we expect raw data from the server * until PQendcopy is called. Don't try to parse it according to * the normal protocol. (This is bogus. The data lines ought to * be part of the protocol and have identifying leading * characters.) */ if (conn->asyncStatus == PGASYNC_COPY_OUT) return; /* * OK to try to read a message type code. */ conn->inCursor = conn->inStart; if (pqGetc(&id, conn)) return; /* * NOTIFY and NOTICE messages can happen in any state besides COPY * OUT; always process them right away. */ if (id == 'A') { if (getNotify(conn)) return; } else if (id == 'N') { if (getNotice(conn)) return; } else { /* * Other messages should only be processed while in BUSY * state. (In particular, in READY state we hold off further * parsing until the application collects the current * PGresult.) If the state is IDLE then we got trouble. */ if (conn->asyncStatus != PGASYNC_BUSY) { if (conn->asyncStatus == PGASYNC_IDLE) { sprintf(conn->errorMessage, "Backend message type 0x%02x arrived while idle\n", id); DONOTICE(conn, conn->errorMessage); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; } return; } switch (id) { case 'C': /* command complete */ if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); if (pqGets(conn->result->cmdStatus, CMDSTATUS_LEN, conn)) return; conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) return; /* delete any partially constructed result */ pqClearAsyncResult(conn); /* and build an error result holding the error message */ conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; break; case 'Z': /* backend is ready for new query */ conn->asyncStatus = PGASYNC_IDLE; break; case 'I': /* empty query */ /* read and throw away the closing '\0' */ if (pqGetc(&id, conn)) return; if (id != '\0') { sprintf(conn->errorMessage, "unexpected character %c following 'I'\n", id); DONOTICE(conn, conn->errorMessage); } if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); conn->asyncStatus = PGASYNC_READY; break; case 'K': /* secret key data from the backend */ /* * This is expected only during backend startup, but * it's just as easy to handle it as part of the main * loop. Save the data and continue processing. */ if (pqGetInt(&(conn->be_pid), 4, conn)) return; if (pqGetInt(&(conn->be_key), 4, conn)) return; break; case 'P': /* synchronous (normal) portal */ if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) return; /* We pretty much ignore this message type... */ break; case 'T': /* row descriptions (start of query * results) */ if (conn->result == NULL) { /* First 'T' in a query sequence */ if (getRowDescriptions(conn)) return; } else { /* * A new 'T' message is treated as the start of * another PGresult. (It is not clear that this * is really possible with the current backend.) * We stop parsing until the application accepts * the current result. */ conn->asyncStatus = PGASYNC_READY; return; } break; case 'D': /* ASCII data tuple */ if (conn->result != NULL) { /* Read another tuple of a normal query response */ if (getAnotherTuple(conn, FALSE)) return; } else { sprintf(conn->errorMessage, "Backend sent D message without prior T\n"); DONOTICE(conn, conn->errorMessage); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; return; } break; case 'B': /* Binary data tuple */ if (conn->result != NULL) { /* Read another tuple of a normal query response */ if (getAnotherTuple(conn, TRUE)) return; } else { sprintf(conn->errorMessage, "Backend sent B message without prior T\n"); DONOTICE(conn, conn->errorMessage); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; return; } break; case 'G': /* Start Copy In */ conn->asyncStatus = PGASYNC_COPY_IN; break; case 'H': /* Start Copy Out */ conn->asyncStatus = PGASYNC_COPY_OUT; break; default: sprintf(conn->errorMessage, "unknown protocol character '%c' read from backend. " "(The protocol character is the first character the " "backend sends in response to a query it receives).\n", id); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; /* delete any partially constructed result */ pqClearAsyncResult(conn); /* and build an error result holding the error message */ conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; return; } /* switch on protocol character */ } /* Successfully consumed this message */ conn->inStart = conn->inCursor; } } /* * parseInput subroutine to read a 'T' (row descriptions) message. * We build a PGresult structure containing the attribute data. * Returns: 0 if completed message, EOF if not enough data yet. * * Note that if we run out of data, we have to release the partially * constructed PGresult, and rebuild it again next time. Fortunately, * that shouldn't happen often, since 'T' messages usually fit in a packet. */ static int getRowDescriptions(PGconn *conn) { PGresult *result; int nfields; int i; result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK); /* parseInput already read the 'T' label. */ /* the next two bytes are the number of fields */ if (pqGetInt(&(result->numAttributes), 2, conn)) { PQclear(result); return EOF; } nfields = result->numAttributes; /* allocate space for the attribute descriptors */ if (nfields > 0) { result->attDescs = (PGresAttDesc *) pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE); MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc)); } /* get type info */ for (i = 0; i < nfields; i++) { char typName[MAX_MESSAGE_LEN]; int typid; int typlen; int atttypmod; if (pqGets(typName, MAX_MESSAGE_LEN, conn) || pqGetInt(&typid, 4, conn) || pqGetInt(&typlen, 2, conn) || pqGetInt(&atttypmod, 4, conn)) { PQclear(result); return EOF; } /* * Since pqGetInt treats 2-byte integers as unsigned, we need to * coerce the special value "-1" to signed form. (-1 is sent for * variable-length fields.) Formerly, libpq effectively did a * sign-extension on the 2-byte value by storing it in a signed * short. Now we only coerce the single value 65535 == -1; values * 32768..65534 are taken as valid field lengths. */ if (typlen == 0xFFFF) typlen = -1; result->attDescs[i].name = pqResultStrdup(result, typName); result->attDescs[i].typid = typid; result->attDescs[i].typlen = typlen; result->attDescs[i].atttypmod = atttypmod; } /* Success! */ conn->result = result; return 0; } /* * parseInput subroutine to read a 'B' or 'D' (row data) message. * We add another tuple to the existing PGresult structure. * Returns: 0 if completed message, EOF if error or not enough data yet. * * Note that if we run out of data, we have to suspend and reprocess * the message after more data is received. We keep a partially constructed * tuple in conn->curTuple, and avoid reallocating already-allocated storage. */ static int getAnotherTuple(PGconn *conn, int binary) { PGresult *result = conn->result; int nfields = result->numAttributes; PGresAttValue *tup; char bitmap[MAX_FIELDS]; /* the backend sends us a bitmap * of which attributes are null */ int i; int nbytes; /* the number of bytes in bitmap */ char bmap; /* One byte of the bitmap */ int bitmap_index; /* Its index */ int bitcnt; /* number of bits examined in current byte */ int vlen; /* length of the current field value */ result->binary = binary; /* Allocate tuple space if first time for this data message */ if (conn->curTuple == NULL) { conn->curTuple = (PGresAttValue *) pqResultAlloc(result, nfields * sizeof(PGresAttValue), TRUE); if (conn->curTuple == NULL) goto outOfMemory; MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue)); } tup = conn->curTuple; /* Get the null-value bitmap */ nbytes = (nfields + BYTELEN - 1) / BYTELEN; if (nbytes >= MAX_FIELDS) { /* Replace partially constructed result with an error result */ pqClearAsyncResult(conn); sprintf(conn->errorMessage, "getAnotherTuple() -- null-values bitmap is too large\n"); conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; /* Discard the broken message */ conn->inStart = conn->inEnd; return EOF; } if (pqGetnchar(bitmap, nbytes, conn)) return EOF; /* Scan the fields */ bitmap_index = 0; bmap = bitmap[bitmap_index]; bitcnt = 0; for (i = 0; i < nfields; i++) { if (!(bmap & 0200)) { /* if the field value is absent, make it a null string */ tup[i].value = result->null_field; tup[i].len = NULL_LEN; } else { /* get the value length (the first four bytes are for length) */ if (pqGetInt(&vlen, 4, conn)) return EOF; if (binary == 0) vlen = vlen - 4; if (vlen < 0) vlen = 0; if (tup[i].value == NULL) { tup[i].value = (char *) pqResultAlloc(result, vlen + 1, binary); if (tup[i].value == NULL) goto outOfMemory; } tup[i].len = vlen; /* read in the value */ if (vlen > 0) if (pqGetnchar((char *) (tup[i].value), vlen, conn)) return EOF; /* we have to terminate this ourselves */ tup[i].value[vlen] = '\0'; } /* advance the bitmap stuff */ bitcnt++; if (bitcnt == BYTELEN) { bitmap_index++; bmap = bitmap[bitmap_index]; bitcnt = 0; } else bmap <<= 1; } /* Success! Store the completed tuple in the result */ if (!addTuple(result, tup)) goto outOfMemory; /* and reset for a new message */ conn->curTuple = NULL; return 0; outOfMemory: /* Replace partially constructed result with an error result */ pqClearAsyncResult(conn); sprintf(conn->errorMessage, "getAnotherTuple() -- out of memory for result\n"); conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; /* Discard the failed message --- good idea? */ conn->inStart = conn->inEnd; return EOF; } /* * PQisBusy * Return TRUE if PQgetResult would block waiting for input. */ int PQisBusy(PGconn *conn) { if (!conn) return FALSE; /* Parse any available data, if our state permits. */ parseInput(conn); /* PQgetResult will return immediately in all states except BUSY. */ return conn->asyncStatus == PGASYNC_BUSY; } /* * PQgetResult * Get the next PGresult produced by a query. * Returns NULL if and only if no query work remains. */ PGresult * PQgetResult(PGconn *conn) { PGresult *res; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); /* If not ready to return something, block until we are. */ while (conn->asyncStatus == PGASYNC_BUSY) { /* Wait for some more data, and load it. */ if (pqWait(TRUE, FALSE, conn) || pqReadData(conn) < 0) { pqClearAsyncResult(conn); conn->asyncStatus = PGASYNC_IDLE; /* conn->errorMessage has been set by pqWait or pqReadData. */ return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); } /* Parse it. */ parseInput(conn); } /* Return the appropriate thing. */ switch (conn->asyncStatus) { case PGASYNC_IDLE: res = NULL; /* query is complete */ break; case PGASYNC_READY: /* * conn->result is the PGresult to return. If it is NULL * (which probably shouldn't happen) we assume there is an * appropriate error message in conn->errorMessage. */ res = conn->result; conn->result = NULL;/* handing over ownership to caller */ conn->curTuple = NULL; /* just in case */ if (!res) res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); else { /* * Make sure PQerrorMessage agrees with result; it could * be that we have done other operations that changed * errorMessage since the result's error message was * saved. */ strcpy(conn->errorMessage, PQresultErrorMessage(res)); } /* Set the state back to BUSY, allowing parsing to proceed. */ conn->asyncStatus = PGASYNC_BUSY; break; case PGASYNC_COPY_IN: res = PQmakeEmptyPGresult(conn, PGRES_COPY_IN); break; case PGASYNC_COPY_OUT: res = PQmakeEmptyPGresult(conn, PGRES_COPY_OUT); break; default: sprintf(conn->errorMessage, "PQgetResult: Unexpected asyncStatus %d\n", (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } return res; } /* * PQexec * send a query to the backend and package up the result in a PGresult * * If the query was not even sent, return NULL; conn->errorMessage is set to * a relevant message. * If the query was sent, a new PGresult is returned (which could indicate * either success or failure). * The user is responsible for freeing the PGresult via PQclear() * when done with it. */ PGresult * PQexec(PGconn *conn, const char *query) { PGresult *result; PGresult *lastResult; /* * Silently discard any prior query result that application didn't * eat. This is probably poor design, but it's here for backward * compatibility. */ while ((result = PQgetResult(conn)) != NULL) { if (result->resultStatus == PGRES_COPY_IN || result->resultStatus == PGRES_COPY_OUT) { PQclear(result); sprintf(conn->errorMessage, "PQexec: you gotta get out of a COPY state yourself.\n"); return NULL; } PQclear(result); } /* OK to send the message */ if (!PQsendQuery(conn, query)) return NULL; /* * For backwards compatibility, return the last result if there are * more than one. We have to stop if we see copy in/out, however. We * will resume parsing when application calls PQendcopy. */ lastResult = NULL; while ((result = PQgetResult(conn)) != NULL) { if (lastResult) PQclear(lastResult); lastResult = result; if (result->resultStatus == PGRES_COPY_IN || result->resultStatus == PGRES_COPY_OUT) break; } return lastResult; } /* * Attempt to read a Notice response message. * This is possible in several places, so we break it out as a subroutine. * Entry: 'N' flag character has already been consumed. * Exit: returns 0 if successfully consumed Notice message. * returns EOF if not enough data. */ static int getNotice(PGconn *conn) { if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) return EOF; DONOTICE(conn, conn->errorMessage); return 0; } /* * Attempt to read a Notify response message. * This is possible in several places, so we break it out as a subroutine. * Entry: 'A' flag character has already been consumed. * Exit: returns 0 if successfully consumed Notify message. * returns EOF if not enough data. */ static int getNotify(PGconn *conn) { PGnotify tempNotify; PGnotify *newNotify; if (pqGetInt(&(tempNotify.be_pid), 4, conn)) return EOF; if (pqGets(tempNotify.relname, NAMEDATALEN, conn)) return EOF; newNotify = (PGnotify *) malloc(sizeof(PGnotify)); memcpy(newNotify, &tempNotify, sizeof(PGnotify)); DLAddTail(conn->notifyList, DLNewElem(newNotify)); return 0; } /* * 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; PGnotify *event; if (!conn) return NULL; /* Parse any available data to see if we can extract NOTIFY messages. */ parseInput(conn); /* RemHead returns NULL if list is empty */ e = DLRemHead(conn->notifyList); if (!e) return NULL; event = (PGnotify *) DLE_VAL(e); DLFreeElem(e); return event; } /* * 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)). * * CAUTION: the caller is responsible for detecting the end-of-copy signal * (a line containing just "\.") when using this routine. * * 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 (i.e., the buffer was filled before \n is reached) */ int PQgetline(PGconn *conn, char *s, int maxlen) { int result = 1; /* return value if buffer overflows */ if (!s || maxlen <= 0) return EOF; if (!conn || conn->sock < 0) { *s = '\0'; return EOF; } /* * Since this is a purely synchronous routine, we don't bother to * maintain conn->inCursor; there is no need to back up. */ while (maxlen > 1) { if (conn->inStart < conn->inEnd) { char c = conn->inBuffer[conn->inStart++]; if (c == '\n') { result = 0; /* success exit */ break; } *s++ = c; maxlen--; } else { /* need to load more data */ if (pqWait(TRUE, FALSE, conn) || pqReadData(conn) < 0) { result = EOF; break; } } } *s = '\0'; return result; } /* * PQgetlineAsync - gets a newline-terminated string without blocking. * * This routine is for applications that want to do "COPY <rel> to stdout" * asynchronously, that is without blocking. Having issued the COPY command * and gotten a PGRES_COPY_OUT response, the app should call PQconsumeInput * and this routine until the end-of-data signal is detected. Unlike * PQgetline, this routine takes responsibility for detecting end-of-data. * * On each call, PQgetlineAsync will return data if a complete newline- * terminated data line is available in libpq's input buffer, or if the * incoming data line is too long to fit in the buffer offered by the caller. * Otherwise, no data is returned until the rest of the line arrives. * * If -1 is returned, the end-of-data signal has been recognized (and removed * from libpq's input buffer). The caller *must* next call PQendcopy and * then return to normal processing. * * RETURNS: * -1 if the end-of-copy-data marker has been recognized * 0 if no data is available * >0 the number of bytes returned. * The data returned will not extend beyond a newline character. If possible * a whole line will be returned at one time. But if the buffer offered by * the caller is too small to hold a line sent by the backend, then a partial * data line will be returned. This can be detected by testing whether the * last returned byte is '\n' or not. * The returned string is *not* null-terminated. */ int PQgetlineAsync(PGconn *conn, char *buffer, int bufsize) { int avail; if (!conn || conn->asyncStatus != PGASYNC_COPY_OUT) return -1; /* we are not doing a copy... */ /* * Move data from libpq's buffer to the caller's. We want to accept * data only in units of whole lines, not partial lines. This ensures * that we can recognize the terminator line "\\.\n". (Otherwise, if * it happened to cross a packet/buffer boundary, we might hand the * first one or two characters off to the caller, which we shouldn't.) */ conn->inCursor = conn->inStart; avail = bufsize; while (avail > 0 && conn->inCursor < conn->inEnd) { char c = conn->inBuffer[conn->inCursor++]; *buffer++ = c; --avail; if (c == '\n') { /* Got a complete line; mark the data removed from libpq */ conn->inStart = conn->inCursor; /* Is it the endmarker line? */ if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.') return -1; /* No, return the data line to the caller */ return bufsize - avail; } } /* * We don't have a complete line. We'd prefer to leave it in libpq's * buffer until the rest arrives, but there is a special case: what if * the line is longer than the buffer the caller is offering us? In * that case we'd better hand over a partial line, else we'd get into * an infinite loop. Do this in a way that ensures we can't * misrecognize a terminator line later: leave last 3 characters in * libpq buffer. */ if (avail == 0 && bufsize > 3) { conn->inStart = conn->inCursor - 3; return bufsize - 3; } return 0; } /* * PQputline -- sends a string to the backend. * Returns 0 if OK, EOF if not. * * Chiefly here so that applications can use "COPY <rel> from stdin". */ int PQputline(PGconn *conn, const char *s) { if (!conn || conn->sock < 0) return EOF; return pqPutnchar(s, strlen(s), conn); } /* * PQputnbytes -- like PQputline, but buffer need not be null-terminated. * Returns 0 if OK, EOF if not. */ int PQputnbytes(PGconn *conn, const char *buffer, int nbytes) { if (!conn || conn->sock < 0) return EOF; return pqPutnchar(buffer, nbytes, conn); } /* * PQendcopy * After completing the data transfer portion of a copy in/out, * the application must call this routine to finish the command protocol. * * RETURNS: * 0 on success * 1 on failure */ int PQendcopy(PGconn *conn) { PGresult *result; if (!conn) return 0; if (conn->asyncStatus != PGASYNC_COPY_IN && conn->asyncStatus != PGASYNC_COPY_OUT) { sprintf(conn->errorMessage, "PQendcopy() -- I don't think there's a copy in progress."); return 1; } (void) pqFlush(conn); /* make sure no data is waiting to be sent */ /* Return to active duty */ conn->asyncStatus = PGASYNC_BUSY; conn->errorMessage[0] = '\0'; /* Wait for the completion response */ result = PQgetResult(conn); /* Expecting a successful result */ if (result && result->resultStatus == PGRES_COMMAND_OK) { PQclear(result); return 0; } /* * Trouble. The worst case is that we've lost sync with the backend * entirely due to application screwup of the copy in/out protocol. To * recover, reset the connection (talk about using a sledgehammer...) */ PQclear(result); if (conn->errorMessage[0]) DONOTICE(conn, conn->errorMessage); DONOTICE(conn, "PQendcopy: resetting connection\n"); PQreset(conn); return 1; } /* ---------------- * 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 an array of function arguments. * (each has length, if integer, and value/pointer) * nargs : # of arguments in args array. * * RETURNS * PGresult with status = PGRES_COMMAND_OK if successful. * *actual_result_len is > 0 if there is a return value, 0 if not. * PGresult with status = PGRES_FATAL_ERROR if backend returns an error. * NULL on communications failure. conn->errorMessage will be set. * ---------------- */ PGresult * PQfn(PGconn *conn, int fnid, int *result_buf, int *actual_result_len, int result_is_int, PQArgBlock *args, int nargs) { bool needInput = false; ExecStatusType status = PGRES_FATAL_ERROR; char id; int i; *actual_result_len = 0; printf("fe-exec, PQfn, A\n"); if (!conn) return NULL; if (conn->sock < 0 || conn->asyncStatus != PGASYNC_IDLE) { printf("fe-exec, PQfn, B\n"); sprintf(conn->errorMessage, "PQfn() -- connection in wrong state\n"); return NULL; } /* clear the error string */ conn->errorMessage[0] = '\0'; if (pqPuts("F ", conn) || /* function */ pqPutInt(fnid, 4, conn) || /* function id */ pqPutInt(nargs, 4, conn)) /* # of args */ { printf("fe-exec, PQfn, C\n"); handleSendFailure(conn); return NULL; } for (i = 0; i < nargs; ++i) { /* len.int4 + contents */ printf("fe-exec, PQfn, C: i:%d\n",i); if (pqPutInt(args[i].len, 4, conn)) { printf("fe-exec, PQfn, D\n"); handleSendFailure(conn); return NULL; } if (args[i].isint) { printf("fe-exec, PQfn, E\n"); if (pqPutInt(args[i].u.integer, 4, conn)) { printf("fe-exec, PQfn, F\n"); handleSendFailure(conn); return NULL; } } else { printf("fe-exec, PQfn, G\n"); if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn)) { handleSendFailure(conn); return NULL; } } } if (pqFlush(conn)) { printf("fe-exec, PQfn, H\n"); handleSendFailure(conn); return NULL; } for (;;) { printf("fe-exec, PQfn, I\n"); if (needInput) { printf("fe-exec, PQfn, J\n"); /* Wait for some data to arrive (or for the channel to close) */ if (pqWait(TRUE, FALSE, conn) || pqReadData(conn) < 0) break; } /* * Scan the message. If we run out of data, loop around to try * again. */ conn->inCursor = conn->inStart; needInput = true; if (pqGetc(&id, conn)) { printf("fe-exec, PQfn, J1: %c\n",id); continue; } /* * We should see V or E response to the command, but might get N * and/or A notices first. We also need to swallow the final Z * before returning. */ printf("fe-exec, PQfn, ID: %c\n", id); switch (id) { case 'V': /* function result */ printf("fe-exec, PQfn, 0\n"); if (pqGetc(&id, conn)) continue; printf("fe-exec, PQfn, 1\n"); if (id == 'G') { printf("fe-exec, PQfn, 2\n"); /* function returned nonempty value */ if (pqGetInt(actual_result_len, 4, conn)) continue; if (result_is_int) { printf("fe-exec, PQfn, 3: %d\n",*actual_result_len); if (pqGetInt(result_buf, 4, conn)) { printf("fe-exec, PQfn, 3a: result_buf: %lu\n", *result_buf); continue; } printf("fe-exec, PQfn, 3b: result_buf: %lu\n", *result_buf); } else { printf("fe-exec, PQfn, 4\n"); if (pqGetnchar((char *) result_buf, *actual_result_len, conn)) continue; } if (pqGetc(&id, conn)) /* get the last '0' */ { printf("fe-exec, PQfn, 4a\n"); continue; } } if (id == '0') { printf("fe-exec, PQfn, 5\n"); /* correctly finished function result message */ status = PGRES_COMMAND_OK; } else { printf("fe-exec, PQfn, 6\n"); /* The backend violates the protocol. */ sprintf(conn->errorMessage, "FATAL: PQfn: protocol error: id=%x\n", id); conn->inStart = conn->inCursor; return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); } break; case 'E': /* error return */ if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) continue; status = PGRES_FATAL_ERROR; break; case 'A': /* notify message */ /* handle notify and go back to processing return values */ if (getNotify(conn)) continue; break; case 'N': /* notice */ /* handle notice and go back to processing return values */ if (getNotice(conn)) continue; break; case 'Z': /* backend is ready for new query */ /* consume the message and exit */ conn->inStart = conn->inCursor; return PQmakeEmptyPGresult(conn, status); default: /* The backend violates the protocol. */ sprintf(conn->errorMessage, "FATAL: PQfn: protocol error: id=%x\n", id); conn->inStart = conn->inCursor; return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); } /* Completed this message, keep going */ conn->inStart = conn->inCursor; needInput = false; } /* we fall out of the loop only upon failing to read data */ return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); } /* ====== accessor funcs for PGresult ======== */ ExecStatusType PQresultStatus(PGresult *res) { if (!res) return PGRES_NONFATAL_ERROR; return res->resultStatus; } const char * PQresStatus(ExecStatusType status) { if (((int) status) < 0 || ((int) status) >= (sizeof(pgresStatus) / sizeof(pgresStatus[0]))) return "Invalid ExecStatusType code"; return pgresStatus[status]; } const char * PQresultErrorMessage(PGresult *res) { if (!res || !res->errMsg) return ""; return res->errMsg; } int PQntuples(PGresult *res) { if (!res) return 0; return res->ntups; } int PQnfields(PGresult *res) { if (!res) return 0; return res->numAttributes; } int PQbinaryTuples(PGresult *res) { if (!res) return 0; return res->binary; } /* * Helper routines to range-check field numbers and tuple numbers. * Return TRUE if OK, FALSE if not */ static int check_field_number(const char *routineName, PGresult *res, int field_num) { if (!res) return FALSE; /* no way to display error message... */ if (field_num < 0 || field_num >= res->numAttributes) { if (res->conn) { sprintf(res->conn->errorMessage, "%s: ERROR! field number %d is out of range 0..%d\n", routineName, field_num, res->numAttributes - 1); DONOTICE(res->conn, res->conn->errorMessage); } return FALSE; } return TRUE; } static int check_tuple_field_number(const char *routineName, PGresult *res, int tup_num, int field_num) { if (!res) return FALSE; /* no way to display error message... */ if (tup_num < 0 || tup_num >= res->ntups) { if (res->conn) { sprintf(res->conn->errorMessage, "%s: ERROR! tuple number %d is out of range 0..%d\n", routineName, tup_num, res->ntups - 1); DONOTICE(res->conn, res->conn->errorMessage); } return FALSE; } if (field_num < 0 || field_num >= res->numAttributes) { if (res->conn) { sprintf(res->conn->errorMessage, "%s: ERROR! field number %d is out of range 0..%d\n", routineName, field_num, res->numAttributes - 1); DONOTICE(res->conn, res->conn->errorMessage); } return FALSE; } return TRUE; } /* returns NULL if the field_num is invalid */ char * PQfname(PGresult *res, int field_num) { if (!check_field_number("PQfname", res, field_num)) 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, const char *field_name) { int i; char *field_case; if (!res) return -1; if (field_name == NULL || field_name[0] == '\0' || res->attDescs == NULL) return -1; field_case = strdup(field_name); if (*field_case == '"') { strcpy(field_case, field_case + 1); *(field_case + strlen(field_case) - 1) = '\0'; } else for (i = 0; field_case[i]; i++) if (isascii((unsigned char) field_case[i]) && isupper(field_case[i])) field_case[i] = tolower(field_case[i]); for (i = 0; i < res->numAttributes; i++) { if (strcmp(field_case, res->attDescs[i].name) == 0) { free(field_case); return i; } } free(field_case); return -1; } Oid PQftype(PGresult *res, int field_num) { if (!check_field_number("PQftype", res, field_num)) return InvalidOid; if (res->attDescs) return res->attDescs[field_num].typid; else return InvalidOid; } int PQfsize(PGresult *res, int field_num) { if (!check_field_number("PQfsize", res, field_num)) return 0; if (res->attDescs) return res->attDescs[field_num].typlen; else return 0; } int PQfmod(PGresult *res, int field_num) { if (!check_field_number("PQfmod", res, field_num)) return 0; if (res->attDescs) return res->attDescs[field_num].atttypmod; else return 0; } char * PQcmdStatus(PGresult *res) { if (!res) return NULL; return res->cmdStatus; } /* PQoidStatus - if the last command was an INSERT, return the oid string if not, return "" */ const char * PQoidStatus(PGresult *res) { char *p, *e, *scan; int slen, olen; if (!res) return ""; if (strncmp(res->cmdStatus, "INSERT ", 7) != 0) return ""; /*---------- * The cmdStatus string looks like * INSERT oid count\0 * In order to be able to return an ordinary C string without * damaging the result for PQcmdStatus or PQcmdTuples, we copy * the oid part of the string to just after the null, so that * cmdStatus looks like * INSERT oid count\0oid\0 * ^ our return value points here * Pretty klugy eh? This routine should've just returned an Oid value. *---------- */ slen = strlen(res->cmdStatus); p = res->cmdStatus + 7; /* where oid is now */ e = res->cmdStatus + slen + 1; /* where to put the oid string */ for (scan = p; *scan && *scan != ' ';) scan++; olen = scan - p; if (slen + olen + 2 > sizeof(res->cmdStatus)) return ""; /* something very wrong if it doesn't fit */ strncpy(e, p, olen); e[olen] = '\0'; return e; } /* PQcmdTuples - if the last command was an INSERT/UPDATE/DELETE, return number of inserted/affected tuples, if not, return "" */ const char * PQcmdTuples(PGresult *res) { if (!res) return ""; if (strncmp(res->cmdStatus, "INSERT", 6) == 0 || strncmp(res->cmdStatus, "DELETE", 6) == 0 || strncmp(res->cmdStatus, "UPDATE", 6) == 0) { char *p = res->cmdStatus + 6; if (*p == 0) { if (res->conn) { sprintf(res->conn->errorMessage, "PQcmdTuples (%s) -- bad input from server\n", res->cmdStatus); DONOTICE(res->conn, res->conn->errorMessage); } return ""; } p++; if (*(res->cmdStatus) != 'I') /* UPDATE/DELETE */ return p; while (*p != ' ' && *p) p++; /* INSERT: skip oid */ if (*p == 0) { if (res->conn) { sprintf(res->conn->errorMessage, "PQcmdTuples (INSERT) -- there's no # of tuples\n"); DONOTICE(res->conn, res->conn->errorMessage); } return ""; } p++; return p; } return ""; } /* PQgetvalue: return the value of field 'field_num' of row '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 (!check_tuple_field_number("PQgetvalue", res, tup_num, field_num)) return NULL; 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. (The data returned by PQgetvalue doesn't either.) */ int PQgetlength(PGresult *res, int tup_num, int field_num) { if (!check_tuple_field_number("PQgetlength", res, tup_num, field_num)) return 0; if (res->tuples[tup_num][field_num].len != NULL_LEN) return res->tuples[tup_num][field_num].len; else return 0; } /* PQgetisnull: returns the null status of a field value. */ int PQgetisnull(PGresult *res, int tup_num, int field_num) { if (!check_tuple_field_number("PQgetisnull", res, tup_num, field_num)) return 1; /* pretend it is null */ if (res->tuples[tup_num][field_num].len == NULL_LEN) return 1; else return 0; } /*------------------------------------------------------------------------- * * fe-lobj.c * Front-end large object interface * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /usr/local/cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.20 1999/05/10 00:46:26 momjian Exp $ * *------------------------------------------------------------------------- */ #include "libpq-fe.h" #include "libpq-int.h" #include "postgres.h" #ifdef WIN32 #include "win32.h" #include <io.h> #else #if !defined(NO_UNISTD_H) #include <unistd.h> #endif #endif #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include "libpq/libpq-fs.h" /* must come after sys/stat.h */ #define LO_BUFSIZE 1024 static int lo_initialize(PGconn *conn); /* * 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; printf("fe-lobj, lo_open: 0\n"); if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { printf("fe-lobj, lo_open: 1\n"); if (lo_initialize(conn) < 0) return -1; } printf("fe-lobj, lo_open: 1a\n"); res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); printf("fe-lobj, lo_open: 2 (%x,%x)\n",conn->lobjfuncs->fn_lo_open,fd); if (PQresultStatus(res) == PGRES_COMMAND_OK) { int WN_TEMP; printf("fe-lobj, lo_open: 3\n"); PQclear(res); /* have to do this to reset offset in shared fd cache */ /* but only if fd is valid */ if (fd >= 0 && (WN_TEMP = lo_lseek(conn, fd, 0L, SEEK_SET)) < 0) { printf("fe-lobj, lo_open: 3a (%x,%x)\n",fd,WN_TEMP); return -1; } printf("fe-lobj, lo_open: 3b\n"); return fd; } else { printf("fe-lobj, lo_open: 4\n"); PQclear(res); 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; if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } argv[0].isint = 1; argv[0].len = 4; argv[0].u.integer = fd; res = PQfn(conn, conn->lobjfuncs->fn_lo_close, &retval, &result_len, 1, argv, 1); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return retval; } else { PQclear(res); 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; if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } 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, conn->lobjfuncs->fn_lo_read, (int *) buf, &result_len, 0, argv, 2); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return result_len; } else { PQclear(res); 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 (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } 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, conn->lobjfuncs->fn_lo_write, &retval, &result_len, 1, argv, 2); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return retval; } else { PQclear(res); 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; if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } 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, conn->lobjfuncs->fn_lo_lseek, &retval, &result_len, 1, argv, 3); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return retval; } else { PQclear(res); 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; if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } argv[0].isint = 1; argv[0].len = 4; argv[0].u.integer = mode; res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, &retval, &result_len, 1, argv, 1); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return (Oid) retval; } else { PQclear(res); 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; if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } argv[0].isint = 1; argv[0].len = 4; argv[0].u.integer = fd; res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, &retval, &result_len, 1, argv, 1); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return retval; } else { PQclear(res); return -1; } } /* * lo_unlink * delete a file * */ int lo_unlink(PGconn *conn, Oid lobjId) { PQArgBlock argv[1]; PGresult *res; int result_len; int retval; if (conn->lobjfuncs == (PGlobjfuncs *) NULL) { if (lo_initialize(conn) < 0) return -1; } argv[0].isint = 1; argv[0].len = 4; argv[0].u.integer = lobjId; res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, &retval, &result_len, 1, argv, 1); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return retval; } else { PQclear(res); 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 */ #ifndef __CYGWIN32__ fd = open(filename, O_RDONLY, 0666); #else fd = open(filename, O_RDONLY | O_BINARY, 0666); #endif 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 %u", 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 %u", lobjId); return -1; } /* * open the file to be written to */ #ifndef __CYGWIN32__ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); #else fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0666); #endif 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; } /* ---------------- * lo_initialize * * Initialize the large object interface for an existing connection. * We ask the backend about the functions OID's in pg_proc for all * functions that are required for large object operations. * ---------------- */ static int lo_initialize(PGconn *conn) { PGresult *res; PGlobjfuncs *lobjfuncs; int n; char *fname; Oid foid; /* ---------------- * Allocate the structure to hold the functions OID's * ---------------- */ lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); if (lobjfuncs == (PGlobjfuncs *) NULL) { strcpy(conn->errorMessage, "FATAL: malloc() failed in lo_initialize()\n"); return -1; } MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs)); /* ---------------- * Execute the query to get all the functions at once * ---------------- */ res = PQexec(conn, "select proname, oid from pg_proc \ where proname = 'lo_open' \ or proname = 'lo_close' \ or proname = 'lo_creat' \ or proname = 'lo_unlink' \ or proname = 'lo_lseek' \ or proname = 'lo_tell' \ or proname = 'loread' \ or proname = 'lowrite'"); if (res == (PGresult *) NULL) { free(lobjfuncs); return -1; } if (res->resultStatus != PGRES_TUPLES_OK) { free(lobjfuncs); PQclear(res); strcpy(conn->errorMessage, "ERROR: SELECT didn't return data in lo_initialize()\n"); return -1; } /* ---------------- * Examine the result and put the OID's into the struct * ---------------- */ for (n = 0; n < PQntuples(res); n++) { fname = PQgetvalue(res, n, 0); foid = (Oid) atoi(PQgetvalue(res, n, 1)); if (!strcmp(fname, "lo_open")) lobjfuncs->fn_lo_open = foid; else if (!strcmp(fname, "lo_close")) lobjfuncs->fn_lo_close = foid; else if (!strcmp(fname, "lo_creat")) lobjfuncs->fn_lo_creat = foid; else if (!strcmp(fname, "lo_unlink")) lobjfuncs->fn_lo_unlink = foid; else if (!strcmp(fname, "lo_lseek")) lobjfuncs->fn_lo_lseek = foid; else if (!strcmp(fname, "lo_tell")) lobjfuncs->fn_lo_tell = foid; else if (!strcmp(fname, "loread")) lobjfuncs->fn_lo_read = foid; else if (!strcmp(fname, "lowrite")) lobjfuncs->fn_lo_write = foid; } PQclear(res); /* ---------------- * Finally check that we really got all large object * interface functions. * ---------------- */ if (lobjfuncs->fn_lo_open == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lo_open\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_close == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lo_close\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_creat == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lo_creat\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_unlink == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lo_unlink\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_lseek == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lo_lseek\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_tell == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lo_tell\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_read == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function loread\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_write == 0) { strcpy(conn->errorMessage, "ERROR: Cannot determine OID for function lowrite\n"); free(lobjfuncs); return -1; } /* ---------------- * Put the structure into the connection control * ---------------- */ conn->lobjfuncs = lobjfuncs; return 0; } FindExec: found "/usr/local/postgres/bin/postgres" using argv[0] binding ShmemCreate(key=52e2c1, size=1063936) /usr/local/postgres/bin/postmaster: ServerLoop: handling reading 4 /usr/local/postgres/bin/postmaster: ServerLoop: handling reading 4 /usr/local/postgres/bin/postmaster: ServerLoop: handling writing 4 /usr/local/postgres/bin/postmaster: BackendStartup: environ dump: ----------------------------------------- REMOTEHOST=baikonur.home.hottis.intern HZ=100 HOSTNAME=talshiar PS1=\h:\w\$ USER=postgres MACHTYPE=i486-pc-linux-gnu MAIL=/var/spool/mail/wn DISPLAY=imhotep:0.0 LOGNAME=postgres SHLVL=3 HUSHLOGIN=FALSE SHELL=/bin/bash PGLIB=/usr/local/postgres/lib HOSTTYPE=i486 OSTYPE=linux-gnu TERM=xterm HOME=/var/local/postgres/home PGDATA=/var/local/postgres/data PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/X11R6/bin:/usr/local/postgres/bin:/usr/local/samba/bin _=/usr/local/postgres/bin/postmaster POSTPORT=5432 POSTID=2147483647 PG_USER=wn IPC_KEY=5432000 ----------------------------------------- /usr/local/postgres/bin/postmaster: BackendStartup: pid 13256 user wn db adrema socket 4 /usr/local/postgres/bin/postmaster child[13256]: starting with (/usr/local/postgres/bin/postgres -d3 -v131072 -p adrema ) FindExec: found "/usr/local/postgres/bin/postgres" using argv[0] debug info: User = wn RemoteHost = localhost RemotePort = 0 DatabaseName = adrema Verbose = 3 Noversion = f timings = f dates = Normal bufsize = 64 sortmem = 512 query echo = f InitPostgres StartTransactionCommand NOTICE: WN: xact: StartTransaction query: select proname, oid from pg_proc where proname = 'lo_open' or proname = 'lo_close' or proname = 'lo_creat' or proname= 'lo_unlink' or proname = 'lo_lseek' or proname = 'lo_tell' or proname = 'loread' or proname = 'lowrite' ProcessQuery CommitTransactionCommand NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 StartTransactionCommand NOTICE: WN: xact: StartTransaction CommitTransactionCommand NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 StartTransactionCommand NOTICE: WN: xact: StartTransaction NOTICE: LOopen(19425,131072) NOTICE: WN: be-fsstubs: lo_open: lobjDesc: 821fcd0 NOTICE: WN: be-fsstubs: newLOfd: MAX_LOBJ_FDS: 256, lobjCookie: 821fcd0 NOTICE: WN: be-fsstubs: newLOfd: i: 0, cookies: 0 NOTICE: WN: be-fsstubs: newLOfd: cookie found! NOTICE: WN: be-fsstubs: newLOfd: cookies[0]=821fcd0 NOTICE: WN: be-fsstubs: lo_open: fd: 0 NOTICE: WN: be-fsstubs: lo_open: return: 0 CommitTransactionCommand NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 NOTICE: WN: be-fsstubs: cookies[0]=NULL! StartTransactionCommand NOTICE: WN: xact: StartTransaction NOTICE: WN: be-fsstubs: lo_lseek, fd: 0 NOTICE: WN: be-fsstubs: lo_lseek: cookies[0]=0 ERROR: lo_lseek: invalid large obj descriptor (0) AbortCurrentTransaction NOTICE: WN: be-fsstubs: lo_commit, isCommit: 0 proc_exit(0) [#0] shmem_exit(0) [#0] NOTICE: WN: xact: StartTransaction pq_flush: send() failed: Bad file descriptor NOTICE: WN: be-fsstubs: lo_commit, isCommit: 1 pq_flush: send() failed: Bad file descriptor exit(0) /usr/local/postgres/bin/postmaster: reaping dead processes... /usr/local/postgres/bin/postmaster: CleanupProc: pid 13256 exited with status 0 pmdie 15 proc_exit(0) [#0] shmem_exit(0) [#0] exit(0)
pgsql-interfaces by date: