diff -rupN postgresql-8.4.0-orig/src/backend/commands/copy.c postgresql-8.4.0-copy/src/backend/commands/copy.c --- postgresql-8.4.0-orig/src/backend/commands/copy.c 2009-06-11 16:48:55.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/commands/copy.c 2009-08-11 11:20:49.000000000 +0200 @@ -42,6 +42,10 @@ #include "utils/memutils.h" #include "utils/snapmgr.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + +#define COPY_BUFFER_SIZE 131072 #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) #define OCTVALUE(c) ((c) - '0') @@ -91,6 +95,9 @@ typedef struct CopyStateData FILE *copy_file; /* used if copy_dest == COPY_FILE */ StringInfo fe_msgbuf; /* used for all dests during COPY TO, only for * dest == COPY_NEW_FE in COPY FROM */ + WBuf wbuf; + OutputFunctionContext *out_context; + bool fe_copy; /* true for all FE copy dests */ bool fe_eof; /* true if detected end of copy data */ EolType eol_type; /* EOL type of input */ @@ -267,15 +274,10 @@ static char *limit_printout_length(const static void SendCopyBegin(CopyState cstate); static void ReceiveCopyBegin(CopyState cstate); static void SendCopyEnd(CopyState cstate); -static void CopySendData(CopyState cstate, void *databuf, int datasize); -static void CopySendString(CopyState cstate, const char *str); -static void CopySendChar(CopyState cstate, char c); static void CopySendEndOfRow(CopyState cstate); static int CopyGetData(CopyState cstate, void *databuf, int minread, int maxread); -static void CopySendInt32(CopyState cstate, int32 val); static bool CopyGetInt32(CopyState cstate, int32 *val); -static void CopySendInt16(CopyState cstate, int16 val); static bool CopyGetInt16(CopyState cstate, int16 *val); @@ -347,6 +349,7 @@ ReceiveCopyBegin(CopyState cstate) pq_endmessage(&buf); cstate->copy_dest = COPY_NEW_FE; cstate->fe_msgbuf = makeStringInfo(); + cstate->wbuf = NULL; } else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) { @@ -379,51 +382,92 @@ SendCopyEnd(CopyState cstate) { /* Shouldn't have any unsent data */ Assert(cstate->fe_msgbuf->len == 0); + Assert( wbuf_is_empty( cstate->wbuf )); + /* Send Copy Done message */ pq_putemptymessage('c'); } else { - CopySendData(cstate, "\\.", 2); /* Need to flush out the trailer (this also appends a newline) */ + wbuf_put( cstate->wbuf, "\\.", 2 ); CopySendEndOfRow(cstate); + wbuf_flush( cstate->wbuf ); pq_endcopyout(false); } } -/*---------- - * CopySendData sends output data to the destination (file or frontend) - * CopySendString does the same for null-terminated strings - * CopySendChar does the same for single characters - * CopySendEndOfRow does the appropriate thing at end of each data row - * (data is not actually flushed except by CopySendEndOfRow) - * - * NB: no data conversion is applied by these functions - *---------- - */ -static void -CopySendData(CopyState cstate, void *databuf, int datasize) -{ - appendBinaryStringInfo(cstate->fe_msgbuf, (char *) databuf, datasize); -} +/******** data sinks for copy output buffer ***********/ -static void -CopySendString(CopyState cstate, const char *str) +static void wbufCopySink_COPY_FILE( WBuf buf, const char* data, int length ) { - appendBinaryStringInfo(cstate->fe_msgbuf, str, strlen(str)); + CopyState cstate = (CopyState) buf->sink_arg; + + /* First, send any data in the WBuf, since we were called because it needs flushing. */ + (void) fwrite( buf->data, wbuf_get_used_space( buf ), 1, cstate->copy_file ); + if( ferror(cstate->copy_file) ) + goto error; + + /* It is now empty. */ + wbuf_reset( buf ); + + /* send any extra data */ + if( data && length ) + { + (void) fwrite(data, length, 1, cstate->copy_file ); + if( ferror(cstate->copy_file) ) + goto error; + } + + return; + +error: + if (ferror(cstate->copy_file)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to COPY file: %m"))); +} + +static void wbufCopySink_COPY_OLD_FE( WBuf buf, const char* data, int length ) +{ + /* First, send any data in the WBuf, since we were called because it needs flushing. */ + if (pq_putbytes(buf->data, wbuf_get_used_space( buf ))) + goto error; + + /* It is now empty. */ + wbuf_reset( buf ); + + /* send any extra data */ + if( data && length ) + if (pq_putbytes( data, length )) + goto error; + + return; + +error: + /* no hope of recovering connection sync, so FATAL */ + ereport(FATAL, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("connection lost during COPY to stdout"))); +} + +static void wbufCopySink_COPY_COPY_NEW_FE( WBuf buf, const char* data, int length ) +{ + /* First, send any data in the WBuf, since we were called because it needs flushing. */ + (void) pq_putmessage('d', buf->data, wbuf_get_used_space( buf )); + + /* It is now empty. */ + wbuf_reset( buf ); + + /* send any extra data */ + if( data && length ) + (void) pq_putmessage('d',data, length); } -static void -CopySendChar(CopyState cstate, char c) -{ - appendStringInfoCharMacro(cstate->fe_msgbuf, c); -} static void CopySendEndOfRow(CopyState cstate) { - StringInfo fe_msgbuf = cstate->fe_msgbuf; - switch (cstate->copy_dest) { case COPY_FILE: @@ -431,43 +475,23 @@ CopySendEndOfRow(CopyState cstate) { /* Default line termination depends on platform */ #ifndef WIN32 - CopySendChar(cstate, '\n'); + wbuf_put_char( cstate->wbuf, '\n'); #else - CopySendString(cstate, "\r\n"); + wbuf_put_string( cstate->wbuf, "\r\n"); #endif } - - (void) fwrite(fe_msgbuf->data, fe_msgbuf->len, - 1, cstate->copy_file); - if (ferror(cstate->copy_file)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write to COPY file: %m"))); break; case COPY_OLD_FE: /* The FE/BE protocol uses \n as newline for all platforms */ if (!cstate->binary) - CopySendChar(cstate, '\n'); - - if (pq_putbytes(fe_msgbuf->data, fe_msgbuf->len)) - { - /* no hope of recovering connection sync, so FATAL */ - ereport(FATAL, - (errcode(ERRCODE_CONNECTION_FAILURE), - errmsg("connection lost during COPY to stdout"))); - } + wbuf_put_char( cstate->wbuf, '\n'); break; case COPY_NEW_FE: /* The FE/BE protocol uses \n as newline for all platforms */ if (!cstate->binary) - CopySendChar(cstate, '\n'); - - /* Dump the accumulated row as one CopyData message */ - (void) pq_putmessage('d', fe_msgbuf->data, fe_msgbuf->len); + wbuf_put_char( cstate->wbuf, '\n'); break; } - - resetStringInfo(fe_msgbuf); } /* @@ -581,23 +605,6 @@ CopyGetData(CopyState cstate, void *data return bytesread; } - -/* - * These functions do apply some data conversion - */ - -/* - * CopySendInt32 sends an int32 in network byte order - */ -static void -CopySendInt32(CopyState cstate, int32 val) -{ - uint32 buf; - - buf = htonl((uint32) val); - CopySendData(cstate, &buf, sizeof(buf)); -} - /* * CopyGetInt32 reads an int32 that appears in network byte order * @@ -618,18 +625,6 @@ CopyGetInt32(CopyState cstate, int32 *va } /* - * CopySendInt16 sends an int16 in network byte order - */ -static void -CopySendInt16(CopyState cstate, int16 val) -{ - uint16 buf; - - buf = htons((uint16) val); - CopySendData(cstate, &buf, sizeof(buf)); -} - -/* * CopyGetInt16 reads an int16 that appears in network byte order */ static bool @@ -883,6 +878,11 @@ DoCopy(const CopyStmt *stmt, const char * more than strictly necessary, but seems best for consistency and * future-proofing. Likewise we disallow all digits though only octal * digits are actually dangerous. + * + * note : do not change this behavior, since the OutputFunctions that go directly + * through the output wbuf bypass any encoding verification on COPY TO. This is fine + * for things like numbers, bool( 't', 'f' ) etc, unless someone uses a character like '1' + * as a delimiter... */ if (!cstate->csv_mode && strchr("\\.abcdefghijklmnopqrstuvwxyz0123456789", @@ -1311,8 +1311,23 @@ CopyTo(CopyState cstate) num_phys_attrs = tupDesc->natts; cstate->null_print_client = cstate->null_print; /* default */ - /* We use fe_msgbuf as a per-row buffer regardless of copy_dest */ - cstate->fe_msgbuf = makeStringInfo(); + /* We no longer use fe_msgbuf as a per-row buffer regardless of copy_dest */ + cstate->fe_msgbuf = NULL; + + /* Create a WBuf as a multirow buffer to store data to be forwarded to the COPY destination */ + cstate->wbuf = wbuf_make( COPY_BUFFER_SIZE ); + + /* Setup forwarding in wbuf : when it needs flushing, it will call the flush function */ + switch( cstate->copy_dest ) + { + case COPY_OLD_FE: wbuf_set_sink( cstate->wbuf, wbufCopySink_COPY_OLD_FE, (void *)cstate ); break; + case COPY_FILE: wbuf_set_sink( cstate->wbuf, wbufCopySink_COPY_FILE, (void *)cstate ); break; + case COPY_NEW_FE: wbuf_set_sink( cstate->wbuf, wbufCopySink_COPY_COPY_NEW_FE, (void *)cstate ); break; + } + + /* Setup output context to help output functions */ + cstate->out_context = makeNode( OutputFunctionContext ); + cstate->out_context->out_buf = cstate->wbuf; /* allows output functions to pass data "almost" directly to the client */ /* Get info about the columns we need to process. */ cstate->out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo)); @@ -1351,21 +1366,23 @@ CopyTo(CopyState cstate) int32 tmp; /* Signature */ - CopySendData(cstate, (char *) BinarySignature, 11); + wbuf_put( cstate->wbuf, (char *) BinarySignature, 11 ); + /* Flags field */ tmp = 0; if (cstate->oids) tmp |= (1 << 16); - CopySendInt32(cstate, tmp); + wbuf_put_int32_net( cstate->wbuf, tmp); + /* No header extension */ tmp = 0; - CopySendInt32(cstate, tmp); + wbuf_put_int32_net( cstate->wbuf, tmp); } else { /* * For non-binary copy, we need to convert null_print to client - * encoding, because it will be sent directly with CopySendString. + * encoding, because it will be sent directly with wbuf_put_string. */ if (cstate->need_transcoding) cstate->null_print_client = pg_server_to_client(cstate->null_print, @@ -1382,7 +1399,7 @@ CopyTo(CopyState cstate) char *colname; if (hdr_delim) - CopySendChar(cstate, cstate->delim[0]); + wbuf_put_char( cstate->wbuf, cstate->delim[0]); hdr_delim = true; colname = NameStr(attr[attnum - 1]->attname); @@ -1429,10 +1446,15 @@ CopyTo(CopyState cstate) if (cstate->binary) { /* Generate trailer for a binary copy */ - CopySendInt16(cstate, -1); - /* Need to flush out the trailer */ + wbuf_put_int16_net(cstate->wbuf, -1); + + /* Send trailer */ CopySendEndOfRow(cstate); } + + /* Flush buffer */ + + wbuf_flush( cstate->wbuf ); MemoryContextDelete(cstate->rowcontext); } @@ -1455,13 +1477,13 @@ CopyOneRowTo(CopyState cstate, Oid tuple if (cstate->binary) { /* Binary per-tuple header */ - CopySendInt16(cstate, list_length(cstate->attnumlist)); + wbuf_put_int16_net( cstate->wbuf, list_length(cstate->attnumlist)); /* Send OID if wanted --- note attnumlist doesn't include it */ if (cstate->oids) { /* Hack --- assume Oid is same size as int32 */ - CopySendInt32(cstate, sizeof(int32)); - CopySendInt32(cstate, tupleOid); + wbuf_put_int32_net( cstate->wbuf, sizeof(int32)); + wbuf_put_int32_net( cstate->wbuf, tupleOid); } } else @@ -1472,7 +1494,7 @@ CopyOneRowTo(CopyState cstate, Oid tuple { string = DatumGetCString(DirectFunctionCall1(oidout, ObjectIdGetDatum(tupleOid))); - CopySendString(cstate, string); + wbuf_put_string( cstate->wbuf, string); need_delim = true; } } @@ -1486,39 +1508,45 @@ CopyOneRowTo(CopyState cstate, Oid tuple if (!cstate->binary) { if (need_delim) - CopySendChar(cstate, cstate->delim[0]); + wbuf_put_char( cstate->wbuf, cstate->delim[0]); need_delim = true; - } - - if (isnull) - { - if (!cstate->binary) - CopySendString(cstate, cstate->null_print_client); + + if (isnull) + wbuf_put_string( cstate->wbuf, cstate->null_print_client); else - CopySendInt32(cstate, -1); + { + string = OutputFunctionCallContext(&out_functions[attnum - 1], + value, + (fmNodePtr)cstate->out_context); + + if( string ) /* if NULL, SendFunction has written directly to our output buffer, we have nothing to do. */ + { + if (cstate->csv_mode) + CopyAttributeOutCSV(cstate, string, + cstate->force_quote_flags[attnum - 1], + list_length(cstate->attnumlist) == 1); + else + CopyAttributeOutText(cstate, string); + } + } + } else { - if (!cstate->binary) - { - string = OutputFunctionCall(&out_functions[attnum - 1], - value); - if (cstate->csv_mode) - CopyAttributeOutCSV(cstate, string, - cstate->force_quote_flags[attnum - 1], - list_length(cstate->attnumlist) == 1); - else - CopyAttributeOutText(cstate, string); - } + if (isnull) + wbuf_put_int32_net( cstate->wbuf, -1); else { bytea *outputbytes; - outputbytes = SendFunctionCall(&out_functions[attnum - 1], - value); - CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); - CopySendData(cstate, VARDATA(outputbytes), - VARSIZE(outputbytes) - VARHDRSZ); + outputbytes = SendFunctionCallContext(&out_functions[attnum - 1], + value, + (fmNodePtr)cstate->out_context); + if( outputbytes ) /* if NULL, SendFunction has written directly to our output buffer, we have nothing to do. */ + { + wbuf_put_int32_net( cstate->wbuf, VARSIZE(outputbytes) - VARHDRSZ); + wbuf_put(cstate->wbuf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); + } } } } @@ -3107,7 +3135,7 @@ CopyReadBinaryAttribute(CopyState cstate #define DUMPSOFAR() \ do { \ if (ptr > start) \ - CopySendData(cstate, start, ptr - start); \ + wbuf_put(wbuf, start, ptr - start); \ } while (0) static void @@ -3117,7 +3145,8 @@ CopyAttributeOutText(CopyState cstate, c char *start; char c; char delimc = cstate->delim[0]; - + WBuf wbuf = cstate->wbuf; + if (cstate->need_transcoding) ptr = pg_server_to_client(string, strlen(string)); else @@ -3181,14 +3210,14 @@ CopyAttributeOutText(CopyState cstate, c } /* if we get here, we need to convert the control char */ DUMPSOFAR(); - CopySendChar(cstate, '\\'); - CopySendChar(cstate, c); + wbuf_put_char( cstate->wbuf, '\\'); + wbuf_put_char( cstate->wbuf, c); start = ++ptr; /* do not include char in next run */ } else if (c == '\\' || c == delimc) { DUMPSOFAR(); - CopySendChar(cstate, '\\'); + wbuf_put_char( cstate->wbuf, '\\'); start = ptr++; /* we include char in next run */ } else if (IS_HIGHBIT_SET(c)) @@ -3241,14 +3270,14 @@ CopyAttributeOutText(CopyState cstate, c } /* if we get here, we need to convert the control char */ DUMPSOFAR(); - CopySendChar(cstate, '\\'); - CopySendChar(cstate, c); + wbuf_put_char( cstate->wbuf, '\\'); + wbuf_put_char( cstate->wbuf, c); start = ++ptr; /* do not include char in next run */ } else if (c == '\\' || c == delimc) { DUMPSOFAR(); - CopySendChar(cstate, '\\'); + wbuf_put_char( cstate->wbuf, '\\'); start = ptr++; /* we include char in next run */ } else @@ -3273,6 +3302,7 @@ CopyAttributeOutCSV(CopyState cstate, ch char delimc = cstate->delim[0]; char quotec = cstate->quote[0]; char escapec = cstate->escape[0]; + WBuf wbuf = cstate->wbuf; /* force quoting if it matches null_print (before conversion!) */ if (!use_quote && strcmp(string, cstate->null_print) == 0) @@ -3315,7 +3345,7 @@ CopyAttributeOutCSV(CopyState cstate, ch if (use_quote) { - CopySendChar(cstate, quotec); + wbuf_put_char( cstate->wbuf, quotec); /* * We adopt the same optimization strategy as in CopyAttributeOutText @@ -3326,7 +3356,7 @@ CopyAttributeOutCSV(CopyState cstate, ch if (c == quotec || c == escapec) { DUMPSOFAR(); - CopySendChar(cstate, escapec); + wbuf_put_char( cstate->wbuf, escapec); start = ptr; /* we include char in next run */ } if (IS_HIGHBIT_SET(c) && cstate->encoding_embeds_ascii) @@ -3336,12 +3366,12 @@ CopyAttributeOutCSV(CopyState cstate, ch } DUMPSOFAR(); - CopySendChar(cstate, quotec); + wbuf_put_char( cstate->wbuf, quotec); } else { /* If it doesn't need quoting, we can just dump it as-is */ - CopySendString(cstate, ptr); + wbuf_put_string( cstate->wbuf, ptr); } } diff -rupN postgresql-8.4.0-orig/src/backend/lib/Makefile postgresql-8.4.0-copy/src/backend/lib/Makefile --- postgresql-8.4.0-orig/src/backend/lib/Makefile 2008-02-19 11:30:07.000000000 +0100 +++ postgresql-8.4.0-copy/src/backend/lib/Makefile 2009-08-10 15:33:50.000000000 +0200 @@ -12,6 +12,6 @@ subdir = src/backend/lib top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = dllist.o stringinfo.o +OBJS = dllist.o stringinfo.o wbuf.o include $(top_srcdir)/src/backend/common.mk diff -rupN postgresql-8.4.0-orig/src/backend/lib/wbuf.c postgresql-8.4.0-copy/src/backend/lib/wbuf.c --- postgresql-8.4.0-orig/src/backend/lib/wbuf.c 1970-01-01 01:00:00.000000000 +0100 +++ postgresql-8.4.0-copy/src/backend/lib/wbuf.c 2009-08-11 02:02:33.000000000 +0200 @@ -0,0 +1,290 @@ +/*------------------------------------------------------------------------- + * + * stringinfo.c + * + * StringInfo provides an indefinitely-extensible string data type. + * It can be used to buffer either ordinary C strings (null-terminated text) + * or arbitrary binary data. All storage is allocated with palloc(). + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/backend/lib/stringinfo.c,v 1.50 2009/01/01 17:23:42 momjian Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "lib/rwbuf.h" +#include "utils/memutils.h" + +/* + * wbuf_flush + * + * Calls the sink() if there is data to flush, which empties the WBuf. + * + */ +void wbuf_flush( WBuf buf ) +{ + Assert( buf->sink ); + if( ! wbuf_is_empty( buf )) /* flushing an empty buffer should not call the sink function */ + buf->sink( buf, NULL, 0 ); /* Call sink function */ +} + +/* + * wbuf_flush_forced + * + * Calls the sink(), which empties the WBuf. Does not check presence of data. + * + */ +void wbuf_flush_forced( WBuf buf ) +{ + Assert( buf->sink ); + buf->sink( buf, NULL, 0 ); /* Call sink function */ +} + +/* + * Ensures there is "needed" bytes in the buffer. + * This is only called by the inlined wbuf_reserve if the buffer + * needs to be flushed. + */ +void wbuf_reserve2_( WBuf buf, int needed ) +{ + Assert( buf->sink ); + buf->sink( buf, NULL, needed ); /* Call sink function, it will throw an error if needed */ + Assert( wbuf_has_space( wbuf, needed )); /* check if sink() did its job */ +} + +void wbuf_put_big( WBuf buf, const void *data, int datalen ) +{ + Assert( data ); + + /* empty buffer (it's probably full since we were called by wbuf_put) */ + Assert( buf->sink ); + if( ! wbuf_is_empty( buf )) /* flushing an empty buffer should not call the sink function */ + buf->sink( buf, NULL, datalen ); /* Call sink function */ + + /* pipe rest of data only if it's needed, or worth it */ + if( wbuf_has_space( buf, datalen ) && datalen < 4096 ) + { + memcpy( buf->wp, data, datalen ); + buf->wp += datalen; + } + else + buf->sink( buf, data, datalen ); +} + +void wbuf_put_string_big( WBuf buf, const char *str ) +{ + int n; + + Assert( str ); + + wbuf_flush( buf ); /* empty buffer (it's probably full since we were called by wbuf_putString) */ + + /* try to put the rest of the string in the buffer in case it's short enough */ + n = wbuf_get_free_space( buf ); + if( n > 4096 ) + n = 4096; + + if( strncpypartial( &buf->wp, &str, n )) + { + buf->wp--; /* forget the '\0' */ + return; + } + + /* insuficient space, send the rest (str and buf->wp were updated by strncpypartial() */ + buf->sink( buf, str, strlen( str )); /* pipe rest of data */ +} + +void wbuf_put_string_0_big( WBuf buf, const char *str ) +{ + int n; + + Assert( str ); + + wbuf_flush( buf ); /* empty buffer (it's probably full since we were called by wbuf_putString) */ + + /* try to put the rest of the string in the buffer in case it's short enough */ + n = wbuf_get_free_space( buf ); + if( n > 4096 ) + n = 4096; + + if( strncpypartial( &buf->wp, &str, n )) + return; + + /* insuficient space, send the rest (str and buf->wp were updated by strncpypartial() */ + buf->sink( buf, str, strlen( str ) + 1 ); /* pipe rest of data including terminator */ +} + + +/* -------------------------------- + * pq_sendint64 - append a binary 8-byte int to a StringInfo buffer + * + * It is tempting to merge this with pq_sendint, but we'd have to make the + * argument int64 for all data widths --- that could be a big performance + * hit on machines where int64 isn't efficient. + * -------------------------------- + */ +void wbuf_put_int64_net(WBuf buf, int64 i) +{ + uint32 n32; + + /* High order half first, since we're doing MSB-first */ +#ifdef INT64_IS_BUSTED + /* don't try a right shift of 32 on a 32-bit word */ + n32 = (i < 0) ? -1 : 0; +#else + n32 = (uint32) (i >> 32); +#endif + n32 = htonl(n32); + wbuf_put(buf, &n32, 4); + + /* Now the low order half */ + n32 = (uint32) i; + n32 = htonl(n32); + wbuf_put(buf, &n32, 4); +} + +/* -------------------------------- + * pq_sendfloat4 - append a float4 to a StringInfo buffer + * + * The point of this routine is to localize knowledge of the external binary + * representation of float4, which is a component of several datatypes. + * + * We currently assume that float4 should be byte-swapped in the same way + * as int4. This rule is not perfect but it gives us portability across + * most IEEE-float-using architectures. + * -------------------------------- + */ +void wbuf_put_float4_net( WBuf buf, float4 f ) +{ + union + { + float4 f; + uint32 i; + } swap; + + swap.f = f; + swap.i = htonl(swap.i); + + wbuf_put(buf, &swap.i, 4); +} + +/* -------------------------------- + * pq_sendfloat8 - append a float8 to a StringInfo buffer + * + * The point of this routine is to localize knowledge of the external binary + * representation of float8, which is a component of several datatypes. + * + * We currently assume that float8 should be byte-swapped in the same way + * as int8. This rule is not perfect but it gives us portability across + * most IEEE-float-using architectures. + * -------------------------------- + */ +void wbuf_put_float8_net( WBuf buf, float8 f ) +{ +#ifdef INT64_IS_BUSTED + union + { + float8 f; + uint32 h[2]; + } swap; + + swap.f = f; + swap.h[0] = htonl(swap.h[0]); + swap.h[1] = htonl(swap.h[1]); + +#ifdef WORDS_BIGENDIAN + /* machine seems to be big-endian, send h[0] first */ + wbuf_put(buf, (char *) &swap.h[0], 4); + wbuf_put(buf, (char *) &swap.h[1], 4); +#else + /* machine seems to be little-endian, send h[1] first */ + wbuf_put(buf, (char *) &swap.h[1], 4); + wbuf_put(buf, (char *) &swap.h[0], 4); +#endif +#else /* INT64 works */ + union + { + float8 f; + int64 i; + } swap; + + swap.f = f; + wbuf_put_int64_net(buf, swap.i); +#endif +} + + + +/* + * strncpypartial + * + * Combines strncpy and a kind of strlen in one call + * + * source is copied into dest. + * + * pdest is a char** since this function modifies it + * psource is a char** since this function modifies it + * dest_size is the size of buffer dest. At most, dest_size bytes are copied, including the '\0' terminator. + * + * If the '\0' terminator could be copied, all is well. + * The return value is true (success). + * Unlike strncpy(), the rest of dest buffer is not zeroed. + * Caller finds *pdest and *psource modified : + * they point after the last byte copied ('\0' in this case.) + * + * If there was not enough space to copy everything, the copy stops after writing the last byte in "dest". + * The return value is false (failure). + * Caller finds *pdest and *psource modified : + * they point after the last byte copied (which is NOT '\0' in this case.) + * Caller can copy the rest of the string in another buffer simply by reusing the same pointers. + */ +bool strncpypartial( char ** pdest, const char ** psource, int dest_size ) +{ + register char c; + char *dest = *pdest; + const char *source = *psource; + int n4; + + /* unroll loop for speed */ + for( n4 = dest_size >> 2; n4; n4-- ) + { + *dest++ = c = *source++; + if( c != '\0') + { + *dest++ = c = *source++; + if( c != '\0') + { + *dest++ = c = *source++; + if( c != '\0') + { + *dest++ = c = *source++; + if( c != '\0') + continue; + } + } + } + /* \0 terminator was found and copied */ + goto success; + } + + /* process remaining chars */ + for( dest_size&=3; dest_size; dest_size-- ) + { + *dest++ = c = *source++; + if (c == '\0') + goto success; /* \0 terminator was found and copied */ + } + + /* \0 terminator was not copied. */ + *pdest = dest; + *psource = source; + return false; + +success: + *pdest = dest; + *psource = source; + return true; +} diff -rupN postgresql-8.4.0-orig/src/backend/libpq/pqcomm.c postgresql-8.4.0-copy/src/backend/libpq/pqcomm.c --- postgresql-8.4.0-orig/src/backend/libpq/pqcomm.c 2009-01-01 18:23:42.000000000 +0100 +++ postgresql-8.4.0-copy/src/backend/libpq/pqcomm.c 2009-08-11 02:30:36.000000000 +0200 @@ -123,7 +123,7 @@ static bool DoingCopyOut; /* Internal functions */ static void pq_close(int code, Datum arg); static int internal_putbytes(const char *s, size_t len); -static int internal_flush(void); +static int internal_flush(const char *send_instead, size_t len); #ifdef HAVE_UNIX_SOCKETS static int Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName); @@ -1035,12 +1035,25 @@ internal_putbytes(const char *s, size_t { size_t amount; + /* if there is a large amount of data to send, + * rather than memcpy(), we could send it directly... */ + + amount = PQ_BUFFER_SIZE - PqSendPointer; + if( len > amount && len > (PQ_BUFFER_SIZE/2) ) + { + if (internal_flush( s, len )) /* flushes internal buffer and sends data at (s,len) */ + return EOF; + return 0; + } + while (len > 0) { /* If buffer is full, then flush it out */ if (PqSendPointer >= PQ_BUFFER_SIZE) - if (internal_flush()) + { + if (internal_flush( NULL, 0 )) return EOF; + } amount = PQ_BUFFER_SIZE - PqSendPointer; if (amount > len) amount = len; @@ -1067,19 +1080,32 @@ pq_flush(void) if (PqCommBusy) return 0; PqCommBusy = true; - res = internal_flush(); + res = internal_flush( NULL, 0 ); PqCommBusy = false; return res; } static int -internal_flush(void) +internal_flush(const char *send_instead, size_t len) { static int last_reported_send_errno = 0; char *bufptr = PqSendBuffer; char *bufend = PqSendBuffer + PqSendPointer; + /* when sending lots of data, avoid memcpy(), but first flush this buffer */ + if( send_instead ) + { + int r; + + if( bufptr != bufend ) + if(( r = internal_flush( NULL, 0 ))) + return r; + + bufptr = (char*)send_instead; + bufend = bufptr + len; + } + while (bufptr < bufend) { int r; diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/bool.c postgresql-8.4.0-copy/src/backend/utils/adt/bool.c --- postgresql-8.4.0-orig/src/backend/utils/adt/bool.c 2009-06-11 16:49:03.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/adt/bool.c 2009-08-11 00:22:41.000000000 +0200 @@ -20,6 +20,9 @@ #include "libpq/pqformat.h" #include "utils/builtins.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + /* * Try to interpret value as boolean value. Valid values are: true, * false, yes, no, on, off, 1, 0; as well as unique prefixes thereof. @@ -163,11 +166,23 @@ Datum boolout(PG_FUNCTION_ARGS) { bool b = PG_GETARG_BOOL(0); - char *result = (char *) palloc(2); - result[0] = (b) ? 't' : 'f'; - result[1] = '\0'; - PG_RETURN_CSTRING(result); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + if( ctx ) + { + Assert( IsA( ctx->out_buf, OutputFunctionContext )); + + wbuf_put_char( ctx->out_buf, (b) ? 't' : 'f' ); + + PG_RETURN_CSTRING( NULL ); /* return a null pointer */ + } + else + { + char *result = (char *) palloc(2); + result[0] = (b) ? 't' : 'f'; /*fixme: why palloc a constant string ? */ + result[1] = '\0'; + PG_RETURN_CSTRING( result ); + } } /* @@ -193,11 +208,27 @@ Datum boolsend(PG_FUNCTION_ARGS) { bool arg1 = PG_GETARG_BOOL(0); - StringInfoData buf; - pq_begintypsend(&buf); - pq_sendbyte(&buf, arg1 ? 1 : 0); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, 1 ); /* length */ + wbuf_put_char( out_buf, arg1 ? 1 : 0 ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, arg1 ? 1 : 0); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } /* diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/date.c postgresql-8.4.0-copy/src/backend/utils/adt/date.c --- postgresql-8.4.0-orig/src/backend/utils/adt/date.c 2009-06-11 16:49:03.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/adt/date.c 2009-08-11 02:10:32.000000000 +0200 @@ -29,6 +29,9 @@ #include "utils/date.h" #include "utils/nabstime.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + /* * gcc's -ffast-math switch breaks routines that expect exact results from * expressions like timeval / SECS_PER_HOUR, where timeval is double. @@ -214,11 +217,27 @@ Datum date_send(PG_FUNCTION_ARGS) { DateADT date = PG_GETARG_DATEADT(0); - StringInfoData buf; - - pq_begintypsend(&buf); - pq_sendint(&buf, date, sizeof(date)); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + Assert( sizeof( date ) == 4 ); + + wbuf_put_int32_net( out_buf, 4 ); /* length */ + wbuf_put_int32_net( out_buf, date ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint(&buf, date, sizeof(date)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } /* diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/float.c postgresql-8.4.0-copy/src/backend/utils/adt/float.c --- postgresql-8.4.0-orig/src/backend/utils/adt/float.c 2009-06-11 16:49:03.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/adt/float.c 2009-08-11 02:00:44.000000000 +0200 @@ -24,6 +24,9 @@ #include "utils/array.h" #include "utils/builtins.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + #ifndef M_PI /* from my RH5.2 gcc math.h file - thomas 2000-04-03 */ @@ -359,11 +362,26 @@ Datum float4send(PG_FUNCTION_ARGS) { float4 num = PG_GETARG_FLOAT4(0); - StringInfoData buf; - - pq_begintypsend(&buf); - pq_sendfloat4(&buf, num); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, 4 ); /* length */ + wbuf_put_float4_net( out_buf, num ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat4(&buf, num); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } /* @@ -548,11 +566,26 @@ Datum float8send(PG_FUNCTION_ARGS) { float8 num = PG_GETARG_FLOAT8(0); - StringInfoData buf; - - pq_begintypsend(&buf); - pq_sendfloat8(&buf, num); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, 8 ); /* length */ + wbuf_put_float8_net( out_buf, num ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, num); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/geo_ops.c postgresql-8.4.0-copy/src/backend/utils/adt/geo_ops.c --- postgresql-8.4.0-orig/src/backend/utils/adt/geo_ops.c 2009-06-23 18:25:02.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/adt/geo_ops.c 2009-08-11 02:34:54.000000000 +0200 @@ -23,6 +23,9 @@ #include "utils/builtins.h" #include "utils/geo_decls.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -458,14 +461,32 @@ Datum box_send(PG_FUNCTION_ARGS) { BOX *box = PG_GETARG_BOX_P(0); - StringInfoData buf; - - pq_begintypsend(&buf); - pq_sendfloat8(&buf, box->high.x); - pq_sendfloat8(&buf, box->high.y); - pq_sendfloat8(&buf, box->low.x); - pq_sendfloat8(&buf, box->low.y); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, 32 ); /* length */ + wbuf_put_float8_net( out_buf, box->high.x ); /* data */ + wbuf_put_float8_net( out_buf, box->high.y ); /* data */ + wbuf_put_float8_net( out_buf, box->low.x ); /* data */ + wbuf_put_float8_net( out_buf, box->low.y ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, box->high.x); + pq_sendfloat8(&buf, box->high.y); + pq_sendfloat8(&buf, box->low.x); + pq_sendfloat8(&buf, box->low.y); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/int.c postgresql-8.4.0-copy/src/backend/utils/adt/int.c --- postgresql-8.4.0-orig/src/backend/utils/adt/int.c 2009-01-01 18:23:49.000000000 +0100 +++ postgresql-8.4.0-copy/src/backend/utils/adt/int.c 2009-08-11 00:57:17.000000000 +0200 @@ -37,6 +37,8 @@ #include "utils/array.h" #include "utils/builtins.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" #define SAMESIGN(a,b) (((a) < 0) == ((b) < 0)) @@ -72,10 +74,24 @@ Datum int2out(PG_FUNCTION_ARGS) { int16 arg1 = PG_GETARG_INT16(0); - char *result = (char *) palloc(7); /* sign, 5 digits, '\0' */ - - pg_itoa(arg1, result); - PG_RETURN_CSTRING(result); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + if( ctx ) + { + char * result; + Assert( IsA( ctx->out_buf, OutputFunctionContext )); + + result = wbuf_ensure( ctx->out_buf, 7 ); + + pg_itoa(arg1, result); + wbuf_increment( ctx->out_buf, strlen( result )); + PG_RETURN_CSTRING( NULL ); /* return a null pointer (not a null datum !)*/ + } + else + { + char *result = (char *) palloc(7); /* sign, 5 digits, '\0' */ + pg_itoa(arg1, result); + PG_RETURN_CSTRING(result); + } } /* @@ -96,11 +112,26 @@ Datum int2send(PG_FUNCTION_ARGS) { int16 arg1 = PG_GETARG_INT16(0); - StringInfoData buf; + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, sizeof( arg1 ) ); /* length */ + wbuf_put_int16_net( out_buf, arg1 ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; - pq_begintypsend(&buf); - pq_sendint(&buf, arg1, sizeof(int16)); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + pq_begintypsend(&buf); + pq_sendint(&buf, arg1, sizeof(int16)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } /* @@ -282,10 +313,24 @@ Datum int4out(PG_FUNCTION_ARGS) { int32 arg1 = PG_GETARG_INT32(0); - char *result = (char *) palloc(12); /* sign, 10 digits, '\0' */ - - pg_ltoa(arg1, result); - PG_RETURN_CSTRING(result); + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + if( ctx ) + { + char * result; + Assert( IsA( ctx->out_buf, OutputFunctionContext )); + + result = wbuf_ensure( ctx->out_buf, 12 ); /* sign, 10 digits, '\0' */ + + pg_ltoa(arg1, result); + wbuf_increment( ctx->out_buf, strlen( result )); + PG_RETURN_CSTRING(0); /* return a null pointer */ + } + else + { + char *result = (char *) palloc(12); /* sign, 10 digits, '\0' */ + pg_ltoa(arg1, result); + PG_RETURN_CSTRING(result); + } } /* @@ -306,11 +351,26 @@ Datum int4send(PG_FUNCTION_ARGS) { int32 arg1 = PG_GETARG_INT32(0); - StringInfoData buf; + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, sizeof( arg1 ) ); /* length */ + wbuf_put_int32_net( out_buf, arg1 ); /* data */ + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; - pq_begintypsend(&buf); - pq_sendint(&buf, arg1, sizeof(int32)); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + pq_begintypsend(&buf); + pq_sendint(&buf, arg1, sizeof(int32)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/timestamp.c postgresql-8.4.0-copy/src/backend/utils/adt/timestamp.c --- postgresql-8.4.0-orig/src/backend/utils/adt/timestamp.c 2009-06-11 16:49:04.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/adt/timestamp.c 2009-08-11 02:05:46.000000000 +0200 @@ -32,6 +32,9 @@ #include "utils/builtins.h" #include "utils/datetime.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + /* * gcc's -ffast-math switch breaks routines that expect exact results from * expressions like timeval / SECS_PER_HOUR, where timeval is double. @@ -275,15 +278,34 @@ Datum timestamp_send(PG_FUNCTION_ARGS) { Timestamp timestamp = PG_GETARG_TIMESTAMP(0); - StringInfoData buf; + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + + if( ctx ) /* fast buffer write */ + { + /* get output buffer */ + WBuf out_buf = ctx->out_buf; + Assert( IsA( fcinfo->context, OutputFunctionContext )); + + wbuf_put_int32_net( out_buf, 8 ); /* length */ +#ifdef HAVE_INT64_TIMESTAMP + wbuf_put_int64_net(out_buf, timestamp); +#else + wbuf_put_float8_net(out_buf, timestamp); +#endif + PG_RETURN_BYTEA_P( NULL ); + } + else /* palloc a bytea */ + { + StringInfoData buf; - pq_begintypsend(&buf); + pq_begintypsend(&buf); #ifdef HAVE_INT64_TIMESTAMP - pq_sendint64(&buf, timestamp); + pq_sendint64(&buf, timestamp); #else - pq_sendfloat8(&buf, timestamp); + pq_sendfloat8(&buf, timestamp); #endif - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } Datum diff -rupN postgresql-8.4.0-orig/src/backend/utils/adt/varlena.c postgresql-8.4.0-copy/src/backend/utils/adt/varlena.c --- postgresql-8.4.0-orig/src/backend/utils/adt/varlena.c 2009-06-11 16:49:04.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/adt/varlena.c 2009-08-11 01:35:13.000000000 +0200 @@ -27,6 +27,9 @@ #include "utils/lsyscache.h" #include "utils/pg_locale.h" +#include "lib/rwbuf.h" +#include "utils/io_context.h" + typedef struct varlena unknown; @@ -391,11 +394,23 @@ Datum textsend(PG_FUNCTION_ARGS) { text *t = PG_GETARG_TEXT_PP(0); - StringInfoData buf; + OutputFunctionContext *ctx = (OutputFunctionContext*) fcinfo->context; + if( ctx ) + { + Assert( IsA( ctx->out_buf, OutputFunctionContext )); + + wbuf_put_int32_net( ctx->out_buf, VARSIZE_ANY_EXHDR(t) ); /* length */ + wbuf_put( ctx->out_buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); + PG_RETURN_BYTEA_P( NULL ); + } + else + { + StringInfoData buf; - pq_begintypsend(&buf); - pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + pq_begintypsend(&buf); + pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); + } } diff -rupN postgresql-8.4.0-orig/src/backend/utils/fmgr/fmgr.c postgresql-8.4.0-copy/src/backend/utils/fmgr/fmgr.c --- postgresql-8.4.0-orig/src/backend/utils/fmgr/fmgr.c 2009-06-11 16:49:05.000000000 +0200 +++ postgresql-8.4.0-copy/src/backend/utils/fmgr/fmgr.c 2009-08-10 23:41:05.000000000 +0200 @@ -1920,6 +1920,42 @@ OutputFunctionCall(FmgrInfo *flinfo, Dat } /* + * Call a previously-looked-up datatype output, using a context. + * The output function should write its data to the context's wbuf + * with perhaps encoding conversion, (fixme ;) + * If it does not support this, simply return a cstring as usual. + * + * Do not call this on NULL datums. + * + * This is almost just window dressing for FunctionCall1, but it includes + * SPI context pushing for the same reasons as InputFunctionCall. + */ +char * +OutputFunctionCallContext( FmgrInfo *flinfo, Datum val, fmNodePtr context ) +{ + FunctionCallInfoData fcinfo; + char *result; + bool pushed; + + pushed = SPI_push_conditional(); + + InitFunctionCallInfoData(fcinfo, flinfo, 1, context, NULL); + + fcinfo.arg[0] = val; + fcinfo.argnull[0] = false; + + result = DatumGetCString(FunctionCallInvoke(&fcinfo)); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo.isnull) + elog(ERROR, "function %u returned NULL", fcinfo.flinfo->fn_oid); + + SPI_pop_conditional(pushed); + + return result; +} + +/* * Call a previously-looked-up datatype binary-input function. * * "buf" may be NULL to indicate we are reading a NULL. In this case @@ -1997,6 +2033,51 @@ SendFunctionCall(FmgrInfo *flinfo, Datum } /* + * Call a previously-looked-up datatype binary-output function, using a context. + * The output function should write its data to the context's wbuf, and return + * a NULL POINTER. + * + * If it does not support this, simply return a bytea as usual. + * + * Do not call this on NULL datums. + * + */ +bytea * +SendFunctionCallContext(FmgrInfo *flinfo, Datum val, fmNodePtr context ) +{ + FunctionCallInfoData fcinfo; + Datum r ; + bytea *result; + bool pushed; + + pushed = SPI_push_conditional(); + + InitFunctionCallInfoData(fcinfo, flinfo, 1, context, NULL); + + fcinfo.arg[0] = val; + fcinfo.argnull[0] = false; + + r = FunctionCallInvoke(&fcinfo); + + /* If the SendFunction has written its data directly in the context's + output buffer, it returns a NULL pointer (this is normal : there is nothing + to return !). DatumGetByteaP would try to detoast it and crash. So : */ + + if( r ) result = DatumGetByteaP( r ); + else result = NULL; + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo.isnull) + elog(ERROR, "function %u returned NULL", fcinfo.flinfo->fn_oid); + + + SPI_pop_conditional(pushed); + + return result; +} + + +/* * As above, for I/O functions identified by OID. These are only to be used * in seldom-executed code paths. They are not only slow but leak memory. */ diff -rupN postgresql-8.4.0-orig/src/include/fmgr.h postgresql-8.4.0-copy/src/include/fmgr.h --- postgresql-8.4.0-orig/src/include/fmgr.h 2009-01-01 18:23:55.000000000 +0100 +++ postgresql-8.4.0-copy/src/include/fmgr.h 2009-08-10 22:17:21.000000000 +0200 @@ -506,6 +506,9 @@ extern Datum OidReceiveFunctionCall(Oid extern bytea *SendFunctionCall(FmgrInfo *flinfo, Datum val); extern bytea *OidSendFunctionCall(Oid functionId, Datum val); +extern bytea *SendFunctionCallContext(FmgrInfo *flinfo, Datum val, fmNodePtr context ); +extern char *OutputFunctionCallContext( FmgrInfo *flinfo, Datum val, fmNodePtr context ); + /* * Routines in fmgr.c diff -rupN postgresql-8.4.0-orig/src/include/lib/rwbuf.h postgresql-8.4.0-copy/src/include/lib/rwbuf.h --- postgresql-8.4.0-orig/src/include/lib/rwbuf.h 1970-01-01 01:00:00.000000000 +0100 +++ postgresql-8.4.0-copy/src/include/lib/rwbuf.h 2009-08-11 02:02:09.000000000 +0200 @@ -0,0 +1,523 @@ +/*------------------------------------------------------------------------- + * + * rwbuf.h + * Declarations/definitions for "RBuf / WBuf" functions. + * + * StringInfo provides an indefinitely-extensible string data type. + * It can be used to buffer either ordinary C strings (null-terminated text) + * or arbitrary binary data. All storage is allocated with palloc(). + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/lib/rwbuf.h,v 1.36 2009/01/01 17:23:59 momjian Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RWBUF_H +#define RWBUF_H + +/* Writes to a WBuf of short datums (like int, etc) cannot be split to allow + * for a buffer flush, so this should be the size of the biggest object we + * expect to write atomically to a WBuf */ +#define WBUF_MIN_SIZE 64 + +struct WBufData; +typedef struct WBufData *WBuf; + +/* WBuf sink function. + * It is called by the WBuf functions when a WBuf is full, or when we have finished + * to fill it and want to flush the contents. + * + * 1. It must check if there is data in the WBuf, then process it entirely, or not at all. + * Partial writes cannot be handled. If an error occurs, it must ereport() it, + * because the return code of sink() is void, so there is no way to pass an error to the + * caller. + * + * 2. It must look at the data and length parameters. + * If data is not null, it must process "length" bytes of data starting at address "data" + * If data is null, it must make sure there will be at least "length" free space in the buffer. + * + * ** If sink() is something like writing to a file... + * + * We do it like this, so you can write a large amount of data to a WBuf with a small buffer + * without reallocating it and eating up memory. In this case, any data in the buffer is + * flushed in step 1., and the rest of the data is simply piped to the data sink in step 2. + * After the call to sink(), the buffer should be empty. + * + * ** If sink() is a reallocator that enlarges the buffer + * + * sink() can be a function that grows the buffer, and stores the extra + * data in it. In this case, it behaves just like StringInfo. sink() does not touch + * the data already in the buffer, it merely enlarges it. + * After the call to sink(), the buffer will contain exactly as much data as before, + * and a lot more free space. + * + * If a reallocator is used, it must extend the buffer by a substantial amount + * because functions that send small data (like an int) to the buffer don't recheck + * if there is space after calling sink(), they assume sink() did remove enough data. + * + * buf the WBuf + * data extra data to write + * length length of extra data to write + * + * sink_arg is not passed as a parameter, since it is stored in WBufData. + * sink() needs sink_arg to know where to put the data. + * + */ +typedef void (*WBufDataSink) ( WBuf buf, const char* data, int length ); + +typedef struct WBufData +{ + char *data; /* pointer to data buffer */ + char *wp; /* position in buffer of next byte to be written */ + char *endp; /* end of data buffer */ + WBufDataSink sink; /* function to call when buffer is full and we try to write to it */ + void * sink_arg; /* opaque data for sink() where we may, . */ +} WBufData; + + +/************************************************* + + Setup Functions + +**************************************************/ + +/* + * wbuf_reset + * + * Reset the WBuf: the data buffer remains valid, but its + * previous content, if any, is cleared. + */ +static inline void wbuf_reset(WBuf buf) +{ + buf->wp = buf->data; +} + +/* + * wbuf_init + * + * Initialize a WBufData struct (with previously undefined contents) + * to describe an empty buffer, of size "size" + */ +static inline void wbuf_init( WBuf buf, int size ) +{ + Assert(size >= WBUF_MIN_SIZE); + buf->data = (char *) palloc(size); + buf->endp = buf->data + size; + wbuf_reset(buf); +} + +/* + * wbuf_make + * + * Create a WBufData, with an empty buffer of size "size", + * & return a pointer to it. + */ +static inline WBuf wbuf_make( int size ) +{ + WBuf buf = (WBuf) palloc(sizeof(WBufData)); + wbuf_init( buf, size ); + return buf; +} + +/* + * wbuf_set_sink + * + * Sets the sink function and its parameter. + */ +static inline void wbuf_set_sink( WBuf buf, WBufDataSink sink, void* opaque ) +{ + buf->sink = sink; + buf->sink_arg = opaque; +} + +/************************************************* + + Simple Getters + +**************************************************/ + + +/* + * wbuf_get_used_space + * + * Returns amount of used bytes in buffer. + * + */ +static inline int wbuf_get_used_space( WBuf buf ) +{ + return buf->wp - buf->data; +} + +/* + * wbuf_get_free_space + * + * Returns amount of unused bytes in buffer. + * + */ +static inline int wbuf_get_free_space( WBuf buf ) +{ + return buf->endp - buf->wp; +} + +/* + * wbuf_get_free_space + * + * Returns size of buffer. + * + */ +static inline int wbuf_get_size( WBuf buf ) +{ + return buf->endp - buf->data; +} + +/* + * wbuf_is_empty + * + * Returns true if WBuf is empty + * + */ +static inline int wbuf_is_empty( WBuf buf ) +{ + return buf->wp == buf->data; +} + +/* + * wbuf_is_full + * + * Returns true if WBuf is full + * + */ +static inline int wbuf_is_full( WBuf buf ) +{ + return buf->wp == buf->endp; +} + +/* + * wbuf_has_space + * + * Returns true if "needed" bytes will fit in the buffer. + * + */ +static inline bool wbuf_has_space( WBuf buf, int needed ) +{ + return ( buf->wp + needed ) <= buf->endp; +} + +/************************************************* + + Flushing + +**************************************************/ + +/* + * wbuf_flush + * + * Calls the sink() if there is data to flush, which empties the WBuf (or not). + * + */ +extern void wbuf_flush( WBuf buf ); + +/* + * wbuf_flush_forced + * + * Calls the sink(), which empties the WBuf. Does not check presence of data. + * Used when we know the buffer is not empty. + * + */ +extern void wbuf_flush_forced( WBuf buf ); + + +/************************************************* + + Pre-Allocation + +**************************************************/ + +/* private */ +extern void wbuf_reserve2_( WBuf buf, int needed ); + +/* + * wbuf_ensure + * + * Ensures than "needed" bytes will fit in the buffer. + * Returns a pointer to the allocated space, + * It is up to the caller to write the data and increment the pointer. + * + * This saves lots of checks if all you want is to write a few ints... + * + * "needed" MUST be <= bus->maxlen (this is verified in reserveWBuf2). + * + * To write big amounts of data, use wbuf_put_big directly. + * + */ +static inline char* wbuf_ensure( WBuf buf, int needed ) +{ + /* check if it will fit */ + if( !wbuf_has_space( buf, needed )) + wbuf_reserve2_( buf, needed ); /* no need to inline this code */ + + /* return a pointer to the reserved space */ + return buf->wp; +} + +/* + * After calling p = wbuf_ensure( buf, N ) to be sure there are + * N free bytes starting at pointer p, the caller can insert up to N + * bytes of data (perhaps less), then call wbuf_increment( buf, added ) + * to tell the wbuf how many bytes were actually added. + */ +static inline void wbuf_increment( WBuf buf, int added ) +{ + buf->wp += added; + Assert( buf->wp <= buf->endp ); +} + +/* + * wbuf_reserve + * + * Exactly like wbuf_ensure(), but it increments the pointer. + */ +static inline char* wbuf_reserve( WBuf buf, int needed ) +{ + char *p = wbuf_ensure( buf, needed ); + p = buf->wp; + buf->wp += needed; + return p; +} + + + +/************************************************* + + Scalar Type Send Functions + +**************************************************/ + +/* + * wbuf_put_char + */ +static inline void wbuf_put_char( WBuf buf, char x ) +{ + /* check if it will fit */ + if( wbuf_is_full( buf )) + wbuf_flush_forced( buf ); + + *(buf->wp++) = x; +} + +/************************************************* + + Native Byte Order Integer Type Send Functions + +**************************************************/ + +static inline void wbuf_put_int16_native( WBuf buf, int16 x ) +{ + if( !wbuf_has_space( buf, sizeof(x) )) + wbuf_flush_forced( buf ); + + memcpy( buf->wp, &x, sizeof( x )); + buf->wp += sizeof( x ); +} + +static inline void wbuf_put_int32_native( WBuf buf, int32 x ) +{ + if( !wbuf_has_space( buf, sizeof(x) )) + wbuf_flush_forced( buf ); + + memcpy( buf->wp, &x, sizeof( x )); + buf->wp += sizeof( x ); +} + +/************************************************* + + Network-Order Integer Type Send Functions + +**************************************************/ + +static inline void wbuf_put_int16_net( WBuf buf, int16 x ) +{ + if( !wbuf_has_space( buf, sizeof(x) )) + wbuf_flush_forced( buf ); + + x = (int16)htons( x ); + + memcpy( buf->wp, &x, sizeof( x )); + buf->wp += sizeof( x ); +} + +static inline void wbuf_put_int32_net( WBuf buf, int32 x ) +{ + /* check if it will fit */ + if( !wbuf_has_space( buf, sizeof(x) )) + wbuf_flush_forced( buf ); + + x = htonl( x ); + + memcpy( buf->wp, &x, sizeof( x )); + buf->wp += sizeof( x ); +} + +extern void wbuf_put_int64_net( WBuf buf, int64 i); +extern void wbuf_put_float4_net( WBuf buf, float4 f ); +extern void wbuf_put_float8_net( WBuf buf, float8 f ); + +/************************************************* + + Binary Send Functions + +**************************************************/ + +/* + * wbuf_put_big + * + * Same as wbuf_put (see below) but we know the data will not + * fit in the buffer, so we will flush, then pipe the data directly to sink() + */ +extern void wbuf_put_big( WBuf buf, const void *data, int datalen ); + +/* + * wbuf_put + * + * Sends binary data (data,datalen) to the WBuf. + * If the data is too large to fit, at most 2 flushes will happen. + * First, the WBuf itself (if not empty) will be flushed. + * Then, depending on the size of the data, it may be written directly, + * or piped straight to the sink() without going through the buffer. + */ +static inline void wbuf_put( WBuf buf, const void *data, int datalen ) +{ + Assert(data != NULL); + + /* check if it will fit */ + if( wbuf_has_space( buf, datalen )) + { + /* append data */ + memcpy( buf->wp, data, datalen ); /* inling wbuf_put makes gcc do smart things with memcpy */ + buf->wp += datalen; + return; + } + + /* no need to inline this code */ + wbuf_put_big( buf, data, datalen ); +} + +/************************************************* + + String Send Functions + (not sending the NULL) + +**************************************************/ + +/* + * wbuf_put_text + * + * Sends a non-null terminated string to the WBuf. + * Does not append a \0 in wbuf. + * Behaves the same as wbuf_put. + */ +#define wbuf_put_text wbuf_put + +/* + * wbuf_put_string_big + * + * Same as wbuf_put_string (see below) but we know the data will not + * fit in the buffer, so we will flush, then pipe the data directly to sink() + */ +extern void wbuf_put_string_big( WBuf buf, const char *str ); + +/* private-ish function (see wbuf.c) +*/ +extern bool strncpypartial( char ** pdest, const char ** psource, int dest_size ); + +/* + * wbuf_put_string + * + * Sends a null terminated string to the WBuf, including the '\0' terminator. + * Behaves the same as wbuf_put. + */ +static inline void wbuf_put_string( WBuf buf, const char *str ) +{ + Assert(str != NULL); + + /* try to put it in the buffer */ + if( strncpypartial( &buf->wp, &str, wbuf_get_free_space( buf )) ) + { + buf->wp--; /* forget the '\0' */ + return; + } + + /* insuficient space, send the rest (str and buf->wp were updated by strncpypartial() */ + wbuf_put_string_big( buf, str ); /* no need to inline this code */ +} + +/************************************************* + + String Send Functions + (sending the NULL) + +**************************************************/ + +/* + * wbuf_put_text_0 + * + * Sends a non-null terminated string to the WBuf, plus the '\0' terminator. + * Behaves the same as wbuf_put. + */ +static inline void wbuf_put_text_0( WBuf buf, const char *str, int len ) +{ + Assert(str != NULL); + + /* check if it will fit, including the '\0' */ + if( wbuf_has_space( buf, len+1 )) + { + /* append data */ + memcpy( buf->wp, str, len ); + buf->wp += len; + *buf->wp++ = '\0'; /* append terminator */ + return; + } + + wbuf_put_big( buf, str, len ); /* no need to inline this code */ + wbuf_put_char( buf, '\0' ); /* append terminator */ +} + +/* + * wbuf_put_string_big + * + * Same as wbuf_put_string (see below) but we know the data will not + * fit in the buffer, so we will flush, then pipe the data directly to sink() + */ +extern void wbuf_put_string_0_big( WBuf buf, const char *str ); + +/* private-ish function (see wbuf.c) +*/ +extern bool strncpypartial( char ** pdest, const char ** psource, int dest_size ); + +/* + * wbuf_put_string + * + * Sends a null terminated string to the WBuf, including the '\0' terminator. + * Behaves the same as wbuf_put. + */ +static inline void wbuf_put_string_0( WBuf buf, const char *str ) +{ + Assert(str != NULL); + + /* try to put it in the buffer */ + if( strncpypartial( &buf->wp, &str, wbuf_get_free_space( buf )) ) + return; + + /* insuficient space, send the rest (str and buf->wp were updated by strncpypartial() */ + wbuf_put_string_big( buf, str ); /* no need to inline this code */ +} + +/* + Fake data sink function which actually reallocates the buffer. +*/ +/* void wbufReallocatorSink( WBuf buf, const char* data, int length ); */ + + +#endif /* RWBUF_H */ diff -rupN postgresql-8.4.0-orig/src/include/nodes/nodes.h postgresql-8.4.0-copy/src/include/nodes/nodes.h --- postgresql-8.4.0-orig/src/include/nodes/nodes.h 2009-06-11 16:49:11.000000000 +0200 +++ postgresql-8.4.0-copy/src/include/nodes/nodes.h 2009-08-10 22:52:54.000000000 +0200 @@ -388,7 +388,9 @@ typedef enum NodeTag T_TriggerData = 950, /* in commands/trigger.h */ T_ReturnSetInfo, /* in nodes/execnodes.h */ T_WindowObjectData, /* private in nodeWindowAgg.c */ - T_TIDBitmap /* in nodes/tidbitmap.h */ + T_TIDBitmap, /* in nodes/tidbitmap.h */ + T_OutputFunctionContext /* COPY passes this context to the OutFunctions + and SendFunctions, see utils/io_context, copy.c and util/adt/ *.c */ } NodeTag; /* diff -rupN postgresql-8.4.0-orig/src/include/utils/io_context.h postgresql-8.4.0-copy/src/include/utils/io_context.h --- postgresql-8.4.0-orig/src/include/utils/io_context.h 1970-01-01 01:00:00.000000000 +0100 +++ postgresql-8.4.0-copy/src/include/utils/io_context.h 2009-08-10 22:50:51.000000000 +0200 @@ -0,0 +1,8 @@ + +typedef struct OutputFunctionContext +{ + NodeTag type; + + WBuf out_buf; + +} OutputFunctionContext; diff -rupN postgresql-8.4.0-orig/src/Makefile.global postgresql-8.4.0-copy/src/Makefile.global --- postgresql-8.4.0-orig/src/Makefile.global 2009-08-07 14:33:32.000000000 +0200 +++ postgresql-8.4.0-copy/src/Makefile.global 2009-08-10 21:13:19.000000000 +0200 @@ -34,7 +34,7 @@ MAJORVERSION = 8.4 # Support for VPATH builds vpath_build = no -abs_top_srcdir = /home/peufeu/dev/postgres/postgresql-8.4.0 +abs_top_srcdir = /home/peufeu/dev/postgres/postgresql-8.4.0-copy ifneq ($(vpath_build),yes) top_srcdir = $(top_builddir) @@ -46,7 +46,7 @@ srcdir = $(top_srcdir)/$(subdir) endif # Saved arguments from configure -configure_args = '--prefix=/home/peufeu/dev/postgres/run/' +configure_args = '--prefix=/home/peufeu/dev/postgres/run/' '--enable-debug' '--enable-depend' ########################################################################## @@ -162,7 +162,7 @@ with_zlib = yes enable_shared = yes enable_rpath = yes enable_nls = no -enable_debug = no +enable_debug = yes enable_dtrace = no enable_coverage = no enable_thread_safety = no @@ -214,7 +214,7 @@ endif # not PGXS CC = gcc GCC = yes SUN_STUDIO_CC = no -CFLAGS = -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv +CFLAGS = -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv -g # Kind-of compilers @@ -539,7 +539,7 @@ install-strip: # whether this file needs to be updated. The dependency files are kept # in the .deps subdirectory of each directory. -autodepend = +autodepend = yes ifeq ($(autodepend), yes)