Index: postgresql/src/backend/commands/copy.c =================================================================== RCS file: /cvsroot/bizgres/bizgres/postgresql/src/backend/commands/copy.c,v retrieving revision 1.1.1.1 diff -c -r1.1.1.1 copy.c *** postgresql/src/backend/commands/copy.c 9 Mar 2005 09:29:33 -0000 1.1.1.1 --- postgresql/src/backend/commands/copy.c 1 Jun 2005 18:53:57 -0000 *************** *** 51,56 **** --- 51,57 ---- #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) #define OCTVALUE(c) ((c) - '0') + #define COPY_BUF_SIZE 65536 /* * Represents the different source/dest cases we need to worry about at *************** *** 84,89 **** --- 85,99 ---- EOL_CRNL } EolType; + typedef struct + { + char *bytes; + int chunksize; + int alloc_size; + int used_size; + char *pos; + int cursor; + } bytebuffer; static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0"; *************** *** 105,111 **** static const char *copy_relname; /* table name for error messages */ static int copy_lineno; /* line number for error messages */ static const char *copy_attname; /* current att for error messages */ ! /* * These static variables are used to avoid incurring overhead for each --- 115,127 ---- static const char *copy_relname; /* table name for error messages */ static int copy_lineno; /* line number for error messages */ static const char *copy_attname; /* current att for error messages */ ! static char data_buf[COPY_BUF_SIZE + 1]; /* leave room for extra null (to enable use of string functions) */ ! static char *begloc; ! static char *endloc; ! static bool bs_in_prevbuf; /* backslash was last character of the data input buffer */ ! static bool line_done; /* finished processing the whole line or stopped in the middle */ ! static bool buf_done; /* finished processing the current buffer */ ! static int data_bufInd; /* data buffer index */ /* * These static variables are used to avoid incurring overhead for each *************** *** 115,122 **** * for subsequent attributes. Note that CopyReadAttribute returns a pointer * to attribute_buf's data buffer! */ ! static StringInfoData attribute_buf; ! /* * Similarly, line_buf holds the whole input line being processed (its * cursor field points to the next character to be read by CopyReadAttribute). --- 131,138 ---- * for subsequent attributes. Note that CopyReadAttribute returns a pointer * to attribute_buf's data buffer! */ ! static StringInfoData attribute_buf; /* used in CopyFrom() */ ! static bytebuffer attr_fastbuf; /* used in FastCopyFrom() */ /* * Similarly, line_buf holds the whole input line being processed (its * cursor field points to the next character to be read by CopyReadAttribute). *************** *** 126,132 **** * directly, but that caused a lot of encoding issues and unnecessary logic * complexity.) */ ! static StringInfoData line_buf; static bool line_buf_converted; /* non-export function prototypes */ --- 142,150 ---- * directly, but that caused a lot of encoding issues and unnecessary logic * complexity.) */ ! static StringInfoData line_buf; /* used in CopyFrom() */ ! static bytebuffer line_fastbuf; /* used in FastCopyFrom() */ ! static bool line_buf_converted; /* non-export function prototypes */ *************** *** 139,147 **** --- 157,175 ---- static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, char *escape, List *force_notnull_atts); + static void FastCopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, + char *delim, char *null_print, bool csv_mode, char *quote, char *escape, + List *force_notnull_atts); static bool CopyReadLine(void); + static bool FastReadLineNL(size_t bytesread); static char *CopyReadAttribute(const char *delim, const char *null_print, CopyReadResult *result, bool *isnull); + static char * FastReadOidAttr(const char *delim, const char *null_print, int null_print_len, + CopyReadResult *result, bool *isnull); + + static void FastReadAllAttrs(const char *delim, const char *null_print, int null_print_len, + char *nulls, List *attnumlist , int *attr_offsets, int num_phys_attrs); + static char *CopyReadAttributeCSV(const char *delim, const char *null_print, char *quote, char *escape, CopyReadResult *result, bool *isnull); *************** *** 162,167 **** --- 190,196 ---- static void CopySendChar(char c); static void CopySendEndOfRow(bool binary); static void CopyGetData(void *databuf, int datasize); + static int FastCopyGetData(void *databuf, int datasize); static int CopyGetChar(void); #define CopyGetEof() (fe_eof) *************** *** 172,177 **** --- 201,217 ---- static void CopySendInt16(int16 val); static int16 CopyGetInt16(void); + /* Memory management functions (prototype) for a line buffer */ + static void bytebuffer_init(bytebuffer *dest); + static void bytebuffer_reset(bytebuffer *dest); + static void bytebuffer_free(bytebuffer *linebuf); + static void bytebuffer_load(char *source, int source_size, bytebuffer *dest); + static bool alloc_more_bytebuffer(bytebuffer *bbuf, int min_size); + + /* new parsing utils */ + static char *strchrlen(const char *s, int c, size_t len); + static char *strchrslen(const char *s, int c1, int c2, size_t len, char *c_found); + /* * Send copy start/stop messages for frontend copies. These have changed *************** *** 467,472 **** --- 507,608 ---- } } + /* + * A slightly different version of CopyGetData. This version does the same + * thing and also returns the number of bytes that were successfully read + * into the data buffer. + */ + static int + FastCopyGetData(void *databuf, int datasize) + { + size_t bytesread = 0; + + switch (copy_dest) + { + case COPY_FILE: + bytesread = fread(databuf, 1, datasize, copy_file); + if (feof(copy_file)) + fe_eof = true; + break; + case COPY_OLD_FE: + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("Old FE protocal not yet supported in fast COPY processing (FastCopyGetData())"))); + + if (pq_getbytes((char *) databuf, datasize)) + { + /* Only a \. terminator is legal EOF in old protocol */ + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("unexpected EOF on client connection"))); + } + break; + case COPY_NEW_FE: + while (datasize > 0 && !fe_eof) + { + int avail; + + while (copy_msgbuf->cursor >= copy_msgbuf->len) + { + /* Try to receive another message */ + int mtype; + + readmessage: + mtype = pq_getbyte(); + if (mtype == EOF) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("unexpected EOF on client connection"))); + if (pq_getmessage(copy_msgbuf, 0)) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("unexpected EOF on client connection"))); + switch (mtype) + { + case 'd': /* CopyData */ + break; + case 'c': /* CopyDone */ + /* COPY IN correctly terminated by frontend */ + fe_eof = true; + return bytesread; + case 'f': /* CopyFail */ + ereport(ERROR, + (errcode(ERRCODE_QUERY_CANCELED), + errmsg("COPY from stdin failed: %s", + pq_getmsgstring(copy_msgbuf)))); + break; + case 'H': /* Flush */ + case 'S': /* Sync */ + + /* + * Ignore Flush/Sync for the convenience of + * client libraries (such as libpq) that may + * send those without noticing that the + * command they just sent was COPY. + */ + goto readmessage; + default: + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("unexpected message type 0x%02X during COPY from stdin", + mtype))); + break; + } + } + avail = copy_msgbuf->len - copy_msgbuf->cursor; + if (avail > datasize) + avail = datasize; + pq_copymsgbytes(copy_msgbuf, databuf, avail); + databuf = (void *) ((char *) databuf + avail); + bytesread += avail; /* update the count of bytes that were read so far */ + datasize -= avail; + } + break; + } + + return bytesread; + } + static int CopyGetChar(void) { *************** *** 985,990 **** --- 1121,1129 ---- initStringInfo(&line_buf); line_buf_converted = false; + bytebuffer_init(&line_fastbuf); /* for FastCopyFrom */ + bytebuffer_init(&attr_fastbuf); + client_encoding = pg_get_client_encoding(); server_encoding = GetDatabaseEncoding(); *************** *** 1041,1048 **** errmsg("\"%s\" is a directory", filename))); } } ! CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode, quote, escape, force_notnull_atts); } else { /* copy from database to file */ --- 1180,1199 ---- errmsg("\"%s\" is a directory", filename))); } } ! ! /* we prefer FastCopyFrom, but it doesn't support binary, csv and encoding conversions */ ! if(!binary && !csv_mode && (client_encoding == server_encoding) ) ! { ! elog(LOG,"Starting COPY FROM with improved parsing capabilities"); ! FastCopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode, ! quote, escape, force_notnull_atts); ! } ! else ! { ! elog(LOG,"Starting COPY FROM without parsing improvements"); ! CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode, quote, escape, force_notnull_atts); + } } else { /* copy from database to file */ *************** *** 1120,1125 **** --- 1271,1279 ---- } pfree(attribute_buf.data); pfree(line_buf.data); + bytebuffer_free(&attr_fastbuf); + bytebuffer_free(&line_fastbuf); + /* * Close the relation. If reading, we can release the AccessShareLock *************** *** 1619,1625 **** * COPY. */ ExecBSInsertTriggers(estate, resultRelInfo); ! if (!binary) file_has_oids = oids; /* must rely on user to tell us this... */ else --- 1773,1779 ---- * COPY. */ ExecBSInsertTriggers(estate, resultRelInfo); ! if (!binary) file_has_oids = oids; /* must rely on user to tell us this... */ else *************** *** 1997,2002 **** --- 2151,2590 ---- FreeExecutorState(estate); } + /* + * Copy FROM file to relation with faster processing. Binary is not supported. + */ + static void + FastCopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, + char *delim, char *null_print, bool csv_mode, char *quote, + char *escape, List *force_notnull_atts) + { + HeapTuple tuple; + TupleDesc tupDesc; + Form_pg_attribute *attr; + AttrNumber num_phys_attrs, + attr_count, + num_defaults; + FmgrInfo *in_functions; + Oid *typioparams; + ExprState **constraintexprs; + bool *force_notnull; + bool hasConstraints = false; + int attnum; + int i; + Oid in_func_oid; + Datum *values; + char *nulls; + int *attr_offsets; /* an array of offsets into attr_fastbuf that point to beginning of attributes */ + bool isnull; + int null_print_len; /* length of null print */ + ResultRelInfo *resultRelInfo; + EState *estate = CreateExecutorState(); /* for ExecConstraints() */ + TupleTable tupleTable; + TupleTableSlot *slot; + bool file_has_oids; + int *defmap; + ExprState **defexprs; /* array of default att expressions */ + ExprContext *econtext; /* used for ExecEvalExpr for default atts */ + MemoryContext oldcontext = CurrentMemoryContext; + ErrorContextCallback errcontext; + + tupDesc = RelationGetDescr(rel); + attr = tupDesc->attrs; + num_phys_attrs = tupDesc->natts; + attr_count = list_length(attnumlist); + num_defaults = 0; + + /* + * We need a ResultRelInfo so we can use the regular executor's + * index-entry-making machinery. (There used to be a huge amount of + * code here that basically duplicated execUtils.c ...) + */ + resultRelInfo = makeNode(ResultRelInfo); + resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ + resultRelInfo->ri_RelationDesc = rel; + resultRelInfo->ri_TrigDesc = CopyTriggerDesc(rel->trigdesc); + + ExecOpenIndices(resultRelInfo); + + estate->es_result_relations = resultRelInfo; + estate->es_num_result_relations = 1; + estate->es_result_relation_info = resultRelInfo; + + /* Set up a dummy tuple table too */ + tupleTable = ExecCreateTupleTable(1); + slot = ExecAllocTableSlot(tupleTable); + ExecSetSlotDescriptor(slot, tupDesc, false); + + econtext = GetPerTupleExprContext(estate); + + /* + * Pick up the required catalog information for each attribute in the + * relation, including the input function, the element type (to pass + * to the input function), and info about defaults and constraints. + * (Which input function we use depends on text/binary format choice.) + */ + in_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo)); + typioparams = (Oid *) palloc(num_phys_attrs * sizeof(Oid)); + defmap = (int *) palloc(num_phys_attrs * sizeof(int)); + defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *)); + constraintexprs = (ExprState **) palloc0(num_phys_attrs * sizeof(ExprState *)); + force_notnull = (bool *) palloc(num_phys_attrs * sizeof(bool)); + + for (attnum = 1; attnum <= num_phys_attrs; attnum++) + { + /* We don't need info for dropped attributes */ + if (attr[attnum - 1]->attisdropped) + continue; + + getTypeInputInfo(attr[attnum - 1]->atttypid, + &in_func_oid, &typioparams[attnum - 1]); + + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + + if (list_member_int(force_notnull_atts, attnum)) + force_notnull[attnum - 1] = true; + else + force_notnull[attnum - 1] = false; + + /* Get default info if needed */ + if (!list_member_int(attnumlist, attnum)) + { + /* attribute is NOT to be copied from input */ + /* use default value if one exists */ + Node *defexpr = build_column_default(rel, attnum); + + if (defexpr != NULL) + { + defexprs[num_defaults] = ExecPrepareExpr((Expr *) defexpr, + estate); + defmap[num_defaults] = attnum - 1; + num_defaults++; + } + } + + /* If it's a domain type, set up to check domain constraints */ + if (get_typtype(attr[attnum - 1]->atttypid) == 'd') + { + Param *prm; + Node *node; + + /* + * Easiest way to do this is to use parse_coerce.c to set up + * an expression that checks the constraints. (At present, + * the expression might contain a length-coercion-function + * call and/or CoerceToDomain nodes.) The bottom of the + * expression is a Param node so that we can fill in the + * actual datum during the data input loop. + */ + prm = makeNode(Param); + prm->paramkind = PARAM_EXEC; + prm->paramid = 0; + prm->paramtype = getBaseType(attr[attnum - 1]->atttypid); + + node = coerce_to_domain((Node *) prm, + prm->paramtype, + attr[attnum - 1]->atttypid, + COERCE_IMPLICIT_CAST, false, false); + + constraintexprs[attnum - 1] = ExecPrepareExpr((Expr *) node, + estate); + hasConstraints = true; + } + } + + /* + * Prepare to catch AFTER triggers. + */ + AfterTriggerBeginQuery(); + + /* + * Check BEFORE STATEMENT insertion triggers. It's debateable whether + * we should do this for COPY, since it's not really an "INSERT" + * statement as such. However, executing these triggers maintains + * consistency with the EACH ROW triggers that we already fire on + * COPY. + */ + ExecBSInsertTriggers(estate, resultRelInfo); + + file_has_oids = oids; /* must rely on user to tell us this... */ + + values = (Datum *) palloc(num_phys_attrs * sizeof(Datum)); + nulls = (char *) palloc(num_phys_attrs * sizeof(char)); + attr_offsets = (int *) palloc(num_phys_attrs * sizeof(int)); + + /* Make room for a PARAM_EXEC value for domain constraint checks */ + if (hasConstraints) + econtext->ecxt_param_exec_vals = (ParamExecData *) + palloc0(sizeof(ParamExecData)); + + /* Initialize static variables */ + fe_eof = false; + eol_type = EOL_UNKNOWN; + copy_binary = binary; + copy_relname = RelationGetRelationName(rel); + copy_lineno = 0; + copy_attname = NULL; + line_buf_converted = true; /* this is always true b/c we rely on identical server/client encodings in FastCopy */ + null_print_len = strlen(null_print); /* it is MUCH faster to do this once here than in the attribute parse loop */ + + /* Set up callback to identify error line number */ + errcontext.callback = copy_in_error_callback; + errcontext.arg = NULL; + errcontext.previous = error_context_stack; + error_context_stack = &errcontext; + + /* Set up data buffer to hold a chunk of data*/ + MemSet(data_buf, ' ', COPY_BUF_SIZE * sizeof(char)); + data_buf[COPY_BUF_SIZE] = '\0'; + + bool no_more_data = FALSE; /* no more input data to read from file or FE */ + line_done = true; + + do + { + /* read a chunk of data into the buffer */ + size_t bytesread = FastCopyGetData(data_buf, COPY_BUF_SIZE); + buf_done = false; + + /* set buffer pointers to beginning of the buffer */ + begloc = data_buf; + data_bufInd = 0; + + /* continue if some bytes were read or if we didn't reach EOF. if we both * + * reached EOF _and_ no bytes were read, quit the loop we are done */ + if (bytesread > 0 || !fe_eof) + { + + while (!buf_done) + { + bool skip_tuple; + Oid loaded_oid = InvalidOid; + + CHECK_FOR_INTERRUPTS(); + + /* Reset the per-tuple exprcontext */ + ResetPerTupleExprContext(estate); + + /* Switch into its memory context */ + MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + + /* Initialize all values for row to NULL */ + MemSet(values, 0, num_phys_attrs * sizeof(Datum)); + MemSet(nulls, 'n', num_phys_attrs * sizeof(char)); + MemSet(attr_offsets, 0, num_phys_attrs * sizeof(int)); /* reset attribute pointers */ + + CopyReadResult result = NORMAL_ATTR; + + ListCell *cur; + + /* Actually read the line into memory here */ + line_done = FastReadLineNL(bytesread); + copy_lineno++; + + /* if didn't finish processing data line, skip att parsing and read more data, unless * + * there is no more data to read... (which means that the original last data line is missing * + * attrs and we want to catch that error) */ + if (!line_done) + { + copy_lineno--; + if(!fe_eof || buf_done) + break; + } + + if (file_has_oids) + { + char *oid_string; + /* can't be in CSV mode here */ + oid_string = FastReadOidAttr(delim, null_print, null_print_len, + &result, &isnull); + + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), + errmsg("null OID in COPY data"))); + else + { + copy_attname = "oid"; + loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin, + CStringGetDatum(oid_string))); + if (loaded_oid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), + errmsg("invalid OID in COPY data"))); + copy_attname = NULL; + } + } + + /* parse all the attribute in the data line */ + FastReadAllAttrs(delim, null_print, null_print_len, + nulls, attnumlist, attr_offsets, num_phys_attrs); + + /* + * Loop to read the user attributes on the line. + */ + foreach(cur, attnumlist) + { + int attnum = lfirst_int(cur); + int m = attnum - 1; + + if (nulls[m] == ' ') + isnull = false; + else + isnull = true; + + /* we read an SQL NULL, no need to do anything */ + if (!isnull) + { + copy_attname = NameStr(attr[m]->attname); + values[m] = FunctionCall3(&in_functions[m], + CStringGetDatum(attr_fastbuf.bytes + attr_offsets[m]), + ObjectIdGetDatum(typioparams[m]), + Int32GetDatum(attr[m]->atttypmod)); + copy_attname = NULL; + } + } + + /* + * Now compute and insert any defaults available for the columns + * not provided by the input data. Anything not processed here or + * above will remain NULL. + */ + for (i = 0; i < num_defaults; i++) + { + values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext, + &isnull, NULL); + if (!isnull) + nulls[defmap[i]] = ' '; + } + + /* + * Next apply any domain constraints + */ + if (hasConstraints) + { + ParamExecData *prmdata = &econtext->ecxt_param_exec_vals[0]; + + for (i = 0; i < num_phys_attrs; i++) + { + ExprState *exprstate = constraintexprs[i]; + + if (exprstate == NULL) + continue; /* no constraint for this attr */ + + /* Insert current row's value into the Param value */ + prmdata->value = values[i]; + prmdata->isnull = (nulls[i] == 'n'); + + /* + * Execute the constraint expression. Allow the + * expression to replace the value (consider e.g. a + * timestamp precision restriction). + */ + values[i] = ExecEvalExpr(exprstate, econtext, + &isnull, NULL); + nulls[i] = isnull ? 'n' : ' '; + } + } + + /* + * And now we can form the input tuple. + */ + tuple = heap_formtuple(tupDesc, values, nulls); + + if (oids && file_has_oids) + HeapTupleSetOid(tuple, loaded_oid); + + /* + * Triggers and stuff need to be invoked in query context. + */ + MemoryContextSwitchTo(oldcontext); + + skip_tuple = false; + + /* BEFORE ROW INSERT Triggers */ + if (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) + { + HeapTuple newtuple; + + newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); + + if (newtuple == NULL) /* "do nothing" */ + skip_tuple = true; + else if (newtuple != tuple) /* modified by Trigger(s) */ + { + heap_freetuple(tuple); + tuple = newtuple; + } + } + + if (!skip_tuple) + { + /* Place tuple in tuple slot */ + ExecStoreTuple(tuple, slot, InvalidBuffer, false); + + /* + * Check the constraints of the tuple + */ + if (rel->rd_att->constr) + ExecConstraints(resultRelInfo, slot, estate); + + /* + * OK, store the tuple and create index entries for it + */ + simple_heap_insert(rel, tuple); + + if (resultRelInfo->ri_NumIndices > 0) + ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); + + /* AFTER ROW INSERT Triggers */ + ExecARInsertTriggers(estate, resultRelInfo, tuple); + } + + bytebuffer_reset(&line_fastbuf); /* we can reset line buffer now. */ + } /* end while(!buf_done) */ + } /* end if(bytesread > 0 || !fe_eof) */ + else /* no bytes read, end of data */ + { + no_more_data = TRUE; + } + }while(!no_more_data); + + /* + * Done, clean up + */ + error_context_stack = errcontext.previous; + + MemoryContextSwitchTo(oldcontext); + + /* + * Execute AFTER STATEMENT insertion triggers + */ + ExecASInsertTriggers(estate, resultRelInfo); + + /* + * Handle queued AFTER triggers + */ + AfterTriggerEndQuery(); + + pfree(values); + pfree(nulls); + pfree(attr_offsets); + pfree(in_functions); + pfree(typioparams); + pfree(defmap); + pfree(defexprs); + pfree(constraintexprs); + pfree(force_notnull); + + ExecDropTupleTable(tupleTable, true); + + ExecCloseIndices(resultRelInfo); + + FreeExecutorState(estate); + } + /* * Read the next input line and stash it in line_buf, with conversion to *************** *** 2194,2199 **** --- 2782,2869 ---- return result; } + /* + * A faster version of CopyReadLine, and only supports lines terminated with a LF char. + * Instead of fetching a byte at a time from a file or from the FE receive buffer, it just + * iterates through the data buffer that is already filled in FastCopyFrom(). + * + * Returns an indication if the line that was read is complete (if an unescaped '\n' was + * encountered). If we reached the end of buffer before the whole line was written into the + * line buffer then returns false. + */ + static bool + FastReadLineNL(size_t bytesread) + { + int linesize; + + while (true) /* (we need a loop so that if '\n' is found, but prev ch is backslash, we can search for the next '\n') */ + { + if( (endloc = strchrlen(begloc, '\n', bytesread - data_bufInd)) == NULL ) /* reached end of buffer */ + { + linesize = COPY_BUF_SIZE - (begloc - data_buf); + bytebuffer_load(begloc, linesize, &line_fastbuf); + + if( data_buf[linesize - 1] == '\\' ) /* last ch in buf is a backslash */ + bs_in_prevbuf = true; + + line_done = false; + buf_done = true; + break; + } + else /* found a(nother) linefeed in data_buf. */ + { + linesize = endloc - begloc + 1; + bytebuffer_load(begloc, linesize, &line_fastbuf); + data_bufInd += linesize; + begloc = endloc + 1; + + /* + * in some cases, this linefeed happens to be the last character + * in the buffer. we need to catch that. + */ + if (data_bufInd >= bytesread) + buf_done = true; + + /* + * found a linefeed, but if is preceded by a backslash + * this is not an end of line and we to stay in the loop + * and process some more data. + */ + if(endloc != data_buf) /* this LF is not first ch in buf */ + { + if(*(endloc - 1) != '\\') + { + line_done = true; + bs_in_prevbuf = false; + break; + } + } + else /* this LF is first ch in buffer */ + { + if(!bs_in_prevbuf) + { + line_done = true; + break; + } + } + + line_done = false; + bs_in_prevbuf = false; + + } /* end of found LF */ + } + + if(!strcmp(line_fastbuf.bytes,"\\.\n")) + { + fe_eof = true; + line_done = false; /* we don't want to process a \. as data line, want to quit. */ + buf_done = true; + } + + return line_done; + } + + /*---------- * Read the value of a single attribute, performing de-escaping as needed. * *************** *** 2319,2324 **** --- 2989,3255 ---- return attribute_buf.data; } + /* + * Read the first attribute in FastCopy mode. This is mainly used to maintain support + * for an OID column. All the rest of the columns will be read at once with + * FastReadAllAttrs(). + */ + static char * + FastReadOidAttr(const char *delim, const char *null_print, int null_print_len, + CopyReadResult *result, bool *isnull) + { + char delimc = delim[0]; + char *start_loc = line_fastbuf.bytes + line_fastbuf.cursor; + char *end_loc; + int attr_len = 0; + + /* reset attribute buf to empty */ + bytebuffer_reset(&attr_fastbuf); + + /* set default status */ + *result = END_OF_LINE; + + int bytes_remaining = line_fastbuf.used_size - line_fastbuf.cursor; /* # of bytes that were not yet processed in this line */ + + if ( (end_loc = strchrlen(start_loc, delimc, bytes_remaining )) == NULL) /* got to end of line */ + { + attr_len = bytes_remaining - 1; /* don't count '\n' in len calculation */ + bytebuffer_load(start_loc, attr_len, &attr_fastbuf); + line_fastbuf.cursor += attr_len + 2; /* skip '\n' and '\0' */ + + *result = END_OF_LINE; + } + else /* found a delimiter */ + { + /* (we don't care if delim was preceded with a backslash, because it's an invalid OID anyway) */ + + attr_len = end_loc - start_loc; /* we don't include the delimiter ch */ + + bytebuffer_load(start_loc, attr_len, &attr_fastbuf); + line_fastbuf.cursor += attr_len + 1; + + *result = NORMAL_ATTR; + } + + + /* check whether raw input matched null marker */ + if (attr_len == null_print_len && strncmp(start_loc, null_print, attr_len) == 0) + *isnull = true; + else + *isnull = false; + + return attr_fastbuf.bytes; + } + + + /* + * Read all attributes in FastCopy mode. + * + * When this routine finishes execution both the nulls array and + * the attr_offsets array are updated. The attr_offsets will include + * the offset from the beginning of the attribute array of which + * each attribute begins. If no an attribute is not used for this + * COPY command, a value of 0 will be assigned. + * For example: for table foo(a,b,c,d,e) and COPY foo(a,b,e) + * attr_offsets will look something like this after this routine + * returns: [0,20,0,0,55] (the 0 in the first array index is + * an actual offset). + * + * Besides updating these 2 arrays, this routine also fills in + * attribute data in the attr_fastbuf buffer. each attribute + * is terminated with a NULL, and therefore by using the attr_offsets + * array we could point to a beginning of an attribute and have it + * as a C string, much like previously done in COPY. + * + * Another aspect to improving performance is reducing the frequency + * of data load into buffers. The original COPY read attribute code + * loads a character at a time. In here we try to load a chunk of data + * at a time. Usually a chunk will include a full data row + * (unless we have an escaped delim). That effectively reduces the number of + * loads by a factor of number of bytes per row. This improves performance + * greatly. + * + * This function also fixes escaping issues. It makes sure that any backslashes + * or data that may look like a c escape sequence will stay *the same* in the + * database, rather than processing its escape. For example: the input data field + * "ftp:\new\top" will still appear as "ftp:\new\top" in the database. Previously + * (with CopyReadAttributes) the data will be modified as \n and \t will be treated + * as escape sequences, and the data will end up as follows: + * + * "ftp: + * ew op" + * + * which is undesired. + */ + static void + FastReadAllAttrs(const char *delim, const char *null_print, int null_print_len, + char *nulls, List *attnumlist , int *attr_offsets, int num_phys_attrs) + { + char delimc = delim[0]; + + char *scan_start = line_fastbuf.bytes + + line_fastbuf.cursor;/* pointer to line buffer where scan should start. * + * note that cursor is > 0 if we copy oids */ + char *scan_end; /* pointer to line buffer where char was found */ + int attr_len = 0; /* length of the attribute that was just parsed */ + + ListCell *cur = list_head(attnumlist); /* cursor to attribute list used for this COPY */ + int attnum = lfirst_int(cur); /* attribute number being parsed */ + int m = attnum - 1; /* attribute index being parsed */ + + int bytes_remaining; /* num of bytes remaining to be scanned in line buf */ + char ch_found = NULL; /* the character found in the scan (delimc or '\\') */ + + int chunk_start = line_fastbuf.cursor;/* offset to beginning of line chunk to load */ + int chunk_len = 0; /* length of chunk of data to load to attr buf */ + + /* Other participants in parsing logic: + * + * line_fastbuf.cursor -- an offset from beginning of the line buffer + * that indicates where we are about to begin the next scan. Note that + * if we have WITH OIDS this cursor is already shifted past the first + * OID attribute. + * attr_fastbuf.cursor -- an offset from the beginning of the + * attribute buffer that indicates where the current attributes begins. + */ + + bytebuffer_reset(&attr_fastbuf); /* start fresh */ + + /* + * Scan through the line buffer to read all attributes data + */ + while(line_fastbuf.cursor < line_fastbuf.used_size) + { + bytes_remaining = line_fastbuf.used_size - line_fastbuf.cursor; + ch_found = NULL; + + if ( (scan_end = strchrslen(scan_start, delimc, '\\', bytes_remaining, &ch_found )) == NULL) + { + /*=========================== + * got to end of line buffer + *===========================*/ + + if(cur == NULL) + ereport(ERROR, + (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), + errmsg("extra data after last expected column"))); + + attnum = lfirst_int(cur); + m = attnum - 1; + + /* don't count '\n' in attr len calculation (-1) */ + attr_len += bytes_remaining - 1; + + /* check if this is a NULL value or data value (assumed NULL) */ + if (attr_len == null_print_len && strncmp(line_fastbuf.bytes + line_fastbuf.used_size - attr_len - 1, null_print, attr_len) == 0) + nulls[m] = 'n'; + else + nulls[m] = ' '; + + attr_offsets[m] = attr_fastbuf.cursor; + + /* update final chunk length */ + chunk_len += attr_len; + + /* load the last chunk, the whole buffer in most cases */ + bytebuffer_load(line_fastbuf.bytes + chunk_start, chunk_len, &attr_fastbuf); + + line_fastbuf.cursor += attr_len + 2; /* skip '\n' and '\0' to exit loop */ + + if(lnext(cur) != NULL) + ereport(ERROR, + (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), + errmsg("missing data column(s) for this line number") + )); + } + else + { + /*================================== + * found a delimiter or a backslash + *==================================*/ + + if(cur == NULL) + ereport(ERROR, + (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), + errmsg("extra data after last expected column"))); + + if(ch_found == delimc) /* found a delimiter */ + { + attnum = lfirst_int(cur); + m = attnum - 1; + + attr_len += scan_end - scan_start; /* we don't include the delimiter ch */ + + /* check if this is a NULL value or data value (assumed NULL) */ + if (attr_len == null_print_len && strncmp(scan_end - attr_len, null_print, attr_len) == 0) + nulls[m] = 'n'; + else + nulls[m] = ' '; + + attr_offsets[m] = attr_fastbuf.cursor; + + /* update buffer cursors to our current location, +1 to skip the delimc */ + line_fastbuf.cursor = scan_end - line_fastbuf.bytes + 1; + attr_fastbuf.cursor += attr_len + 1; + + /* update chunk length so we will load this attribute later on */ + chunk_len += attr_len + 1; + + /* prepare scan for next attr */ + scan_start = line_fastbuf.bytes + line_fastbuf.cursor; + cur = lnext(cur); + attr_len = 0; + } + else /* found a backslash*/ + { + + /* is backslash followed by a delimc? */ + if(*(scan_end + 1) == delimc) + { + /* Found an escaped delimiter char. need to get rid of backslash. + * This is done by loading the chunk up to including the backslash + * into the attribute buffer. Then changing the backslash to a delim + * char, and continuing to scan from *after* the delim in line buf. + */ + attr_len += scan_end - scan_start + 1; /* update to current length, include '\\' */ + chunk_len += attr_len; + + /* load a chunk of data */ + bytebuffer_load(line_fastbuf.bytes + chunk_start, chunk_len, &attr_fastbuf); + + *(attr_fastbuf.bytes + attr_fastbuf.used_size - 1) = delimc; + line_fastbuf.cursor = scan_end - line_fastbuf.bytes + 2; /* continue after the delimc */ + scan_start = scan_end + 2; /* continute scanning starting from after the delimc */ + + chunk_start = line_fastbuf.cursor; + chunk_len = 0; + } + else /* no delim is escaped - continue scanning */ + { + attr_len += scan_end - scan_start + 1; /* update to current length, include '\\' */ + line_fastbuf.cursor = scan_end - line_fastbuf.bytes + 1; + scan_start = scan_end + 1; /* continute scanning starting from 1st byte after the '\\' */ + } + } + + } /* end delimiter/backslash */ + + } /* end line buffer scan. */ + + /* + * Replace all delimiters with NULL for string termination. + * NOTE: only delimiters (NOT necessarily all delimc) are replaced. + */ + int attr; + + for(attr = 1; attr < num_phys_attrs ; attr++) + { + if(attr_offsets[attr] != 0) + *(attr_fastbuf.bytes + attr_offsets[attr] - 1) = '\0'; + } + + } + /* * Read the value of a single attribute in CSV mode, *************** *** 2733,2735 **** --- 3664,3857 ---- return attnums; } + + /**************************************************** + * utility functions, should move out of here later. + ***************************************************/ + + void + bytebuffer_init(bytebuffer *dest) + { + dest->bytes = NULL; + dest->pos = NULL; + dest->chunksize = 0; + dest->used_size = 0; + dest->alloc_size = 0; + dest->cursor = 0; + + /* + * Allocate an initial chunk of data + */ + int size = 256; + if (!alloc_more_bytebuffer(dest, size)) + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("Failed to allocate memory for bytebuffer in bytebuffer init.") + )); + } + + } + + void + bytebuffer_reset(bytebuffer *dest) + { + dest->pos = dest->bytes; + dest->used_size = 0; + dest->cursor = 0; + } + + void + bytebuffer_free(bytebuffer *dest) + { + if ((dest->bytes) != NULL) + { + pfree((dest->bytes)); + } + dest->alloc_size = 0; + bytebuffer_reset(dest); + } + + void + bytebuffer_load(char *source, int source_size, bytebuffer *dest) + { + int minsize; + /* + * Check that (source_size + used_size) fits within the bytebuffer, + * if not, allocate more + */ + minsize = source_size + dest->used_size; + if (minsize >= (dest->alloc_size)) + { + if (! alloc_more_bytebuffer(dest, minsize)) + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("Failed to allocate memory for bytebuffer.") + )); + } + } + + memcpy(dest->pos, source, source_size); + + *(dest->pos+source_size) = '\0'; /* Terminate the string */ + + dest->used_size += source_size; + + dest->pos += source_size; + } + + + /* + * alloc_more_bytebuffer() - Memory management function for a line buffer: + * + * Allocates memory for a line buffer in chunks of size (bbuf->chunksize). + * + * linebuf - If the line buffer pointer passed in is NULL, it will allocate a + * new memory location to it. If the line buffer point passed in is not NULL, + * realloc() is called to allocate a new buffer with (bbuf->chunksize) more + * room. This also copies the contents of the old linebuffer to the new region. + * + * plinebuf_size - is the currently allocated size of linebuf. + * + * pplinebuf - is a pointer to a location contained within the linebuffer, and + * is adjusted to refer to the same place in the newly reallocated linebuffer. + * If it is NULL, then it is set to the beginning of the linebuffer. + * + * (bbuf->chunksize) - size of allocation chunk in units of sizeof(char) + * + * min_size - the minimum size of the buffer needed in bytes + */ + bool + alloc_more_bytebuffer(bytebuffer *bbuf, int min_size) + { + int i; + int offset=0; + + if ((bbuf->alloc_size) >= min_size) + return true; /* Easy out - we were called without need */ + + /* + * If the chunksize is not set, set it to the default (8) + */ + if (bbuf->chunksize==0) bbuf->chunksize = 8; + + /* + * Calculate the number of chunks needed. Add an extra + * chunk if the size doesn't chunk evenly. + */ + i = min_size/(bbuf->chunksize); + if ((min_size % (bbuf->chunksize)) != 0) + i++; + + /* + * In this section, the size of the buffer allocationed or + * reallocationed is extended by one byte to provide for NULL + * termination. + */ + if ((bbuf->bytes)==NULL) + { + bbuf->bytes = (char *)palloc(i * (bbuf->chunksize) * sizeof(char) + 1); + bbuf->pos = bbuf->bytes; + } + else + { + if ((bbuf->pos)!=NULL) + { + offset = (bbuf->pos) - (bbuf->bytes); + } + + bbuf->bytes = (char *)repalloc(bbuf->bytes, i * (bbuf->chunksize) * sizeof(char) + 1); + + if ((bbuf->pos)!=NULL) + { + bbuf->pos = (bbuf->bytes)+offset; + } + } + + bbuf->alloc_size = i * (bbuf->chunksize); + + return ( (bbuf->bytes) != NULL ); + } + + /* + * This is a custom version of the string function strchr(). + * As opposed to the original strchr which searches through + * a string until the target character is found, or a NULL is + * found, this version will not return when a NULL is found. + * Instead it will search through a pre-defined length of + * bytes and will return only if the target character is reached. + * + * parameters: + * s - string being searched. + * c - char we are searching for. + * len - maximum # of bytes to search. + * + * returns: + * pointer to c - if c is located within the string. + * NULL - if c was not found in specified length of search. Note: + * this DOESN'T mean that a NULL was reached. + */ + char *strchrlen(const char *s, int c, size_t len) + { + const char *start; + + for(start = s; (*s != c) && (s < (start + len)) ; s++) + ; + + return ( (*s == c) ? (char *) s : NULL ); + } + + char *strchrslen(const char *s, int c1, int c2, size_t len, char *c_found) + { + const char *start; + *c_found = NULL; + + for(start = s; (*s != c1) && (*s != c2) && (s < (start + len)) ; s++) + ; + + *c_found = *s; + return ( *c_found != NULL ? (char *) s : NULL ); + } + +