/*------------------------------------------------------------------------- * * copy.c-- * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.47 1998/06/19 11:40:46 scrappy Exp $ * *------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "commands/trigger.h" #include #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) #define VALUE(c) ((c) - '0') /* non-export function prototypes */ static void CopyTo(Relation rel, bool binary, bool oids, FILE *fp, char *delim); static void CopyFrom(Relation rel, bool binary, bool oids, FILE *fp, char *delim); static Oid GetOutputFunction(Oid type); static Oid GetTypeElement(Oid type); static Oid GetInputFunction(Oid type); static Oid IsTypeByVal(Oid type); static void GetIndexRelations(Oid main_relation_oid, int *n_indices, Relation **index_rels); #ifdef COPY_PATCH static void CopyReadNewline(FILE *fp, int *newline); static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline); #else static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim); #endif static void CopyAttributeOut(FILE *fp, char *string, char *delim, int is_array); static int CountTuples(Relation relation); extern FILE *Pfout, *Pfin; static int lineno; /* * DoCopy executes a the SQL COPY statement. */ void DoCopy(char *relname, bool binary, bool oids, bool from, bool pipe, char *filename, char *delim) { /*---------------------------------------------------------------------------- Either unload or reload contents of class , depending on . If is false, transfer is between the class and the file named . Otherwise, transfer is between the class and our regular input/output stream. The latter could be either stdin/stdout or a socket, depending on whether we're running under Postmaster control. Iff , unload or reload in the binary format, as opposed to the more wasteful but more robust and portable text format. If in the text format, delimit columns with delimiter . When loading in the text format from an input stream (as opposed to a file), recognize a "." on a line by itself as EOF. Also recognize a stream EOF. When unloading in the text format to an output stream, write a "." on a line by itself at the end of the data. Iff , unload or reload the format that includes OID information. Do not allow a Postgres user without superuser privilege to read from or write to a file. Do not allow the copy if user doesn't have proper permission to access the class. ----------------------------------------------------------------------------*/ FILE *fp; Relation rel; extern char *UserName; /* defined in global.c */ const AclMode required_access = from ? ACL_WR : ACL_RD; int result; rel = heap_openr(relname); if (rel == NULL) elog(ERROR, "COPY command failed. Class %s " "does not exist.", relname); result = pg_aclcheck(relname, UserName, required_access); if (result != ACLCHECK_OK) elog(ERROR, "%s: %s", relname, aclcheck_error_strings[result]); /* Above should not return */ else if (!superuser() && !pipe) elog(ERROR, "You must have Postgres superuser privilege to do a COPY " "directly to or from a file. Anyone can COPY to stdout or " "from stdin. Psql's \\copy command also works for anyone."); /* Above should not return. */ else { if (from) { /* copy from file to database */ if (rel->rd_rel->relkind == RELKIND_SEQUENCE) elog(ERROR, "You can't change sequence relation %s", relname); if (pipe) { if (IsUnderPostmaster) { ReceiveCopyBegin(); fp = Pfin; } else fp = stdin; } else { fp = AllocateFile(filename, "r"); if (fp == NULL) elog(ERROR, "COPY command, running in backend with " "effective uid %d, could not open file '%s' for " "reading. Errno = %s (%d).", geteuid(), filename, strerror(errno), errno); /* Above should not return */ } CopyFrom(rel, binary, oids, fp, delim); } else { /* copy from database to file */ if (pipe) { if (IsUnderPostmaster) { SendCopyBegin(); fp = Pfout; } else fp = stdout; } else { mode_t oumask; /* Pre-existing umask value */ oumask = umask((mode_t) 0); fp = AllocateFile(filename, "w"); umask(oumask); if (fp == NULL) elog(ERROR, "COPY command, running in backend with " "effective uid %d, could not open file '%s' for " "writing. Errno = %s (%d).", geteuid(), filename, strerror(errno), errno); /* Above should not return */ } CopyTo(rel, binary, oids, fp, delim); } if (!pipe) FreeFile(fp); else if (!from && !binary) { fputs("\\.\n", fp); if (IsUnderPostmaster) fflush(Pfout); } } } static void CopyTo(Relation rel, bool binary, bool oids, FILE *fp, char *delim) { HeapTuple tuple; HeapScanDesc scandesc; int32 attr_count, i; AttributeTupleForm *attr; FmgrInfo *out_functions; Oid out_func_oid; Oid *elements; int16 *typmod; Datum value; bool isnull; /* The attribute we are copying is null */ char *nulls; /* * is a (dynamically allocated) array with one character per * attribute in the instance being copied. nulls[I-1] is 'n' if * Attribute Number I is null, and ' ' otherwise. * * is meaningful only if we are doing a binary copy. */ char *string; int32 ntuples; TupleDesc tupDesc; scandesc = heap_beginscan(rel, 0, false, 0, NULL); attr_count = rel->rd_att->natts; attr = rel->rd_att->attrs; tupDesc = rel->rd_att; if (!binary) { out_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo)); elements = (Oid *) palloc(attr_count * sizeof(Oid)); typmod = (int16 *) palloc(attr_count * sizeof(int16)); for (i = 0; i < attr_count; i++) { out_func_oid = (Oid) GetOutputFunction(attr[i]->atttypid); fmgr_info(out_func_oid, &out_functions[i]); elements[i] = GetTypeElement(attr[i]->atttypid); typmod[i] = attr[i]->atttypmod; } nulls = NULL; /* meaningless, but compiler doesn't know * that */ } else { elements = NULL; typmod = NULL; out_functions = NULL; nulls = (char *) palloc(attr_count); for (i = 0; i < attr_count; i++) nulls[i] = ' '; /* XXX expensive */ ntuples = CountTuples(rel); fwrite(&ntuples, sizeof(int32), 1, fp); } for (tuple = heap_getnext(scandesc, 0, NULL); tuple != NULL; tuple = heap_getnext(scandesc, 0, NULL)) { if (oids && !binary) { fputs(oidout(tuple->t_oid), fp); fputc(delim[0], fp); } for (i = 0; i < attr_count; i++) { value = heap_getattr(tuple, i + 1, tupDesc, &isnull); if (!binary) { if (!isnull) { string = (char *) (*fmgr_faddr(&out_functions[i])) (value, elements[i], typmod[i]); CopyAttributeOut(fp, string, delim, attr[i]->attnelems); pfree(string); } else fputs("\\N", fp); /* null indicator */ if (i == attr_count - 1) fputc('\n', fp); else { /* * when copying out, only use the first char of the * delim string */ fputc(delim[0], fp); } } else { /* * only interesting thing heap_getattr tells us in this * case is if we have a null attribute or not. */ if (isnull) nulls[i] = 'n'; } } if (binary) { int32 null_ct = 0, length; for (i = 0; i < attr_count; i++) { if (nulls[i] == 'n') null_ct++; } length = tuple->t_len - tuple->t_hoff; fwrite(&length, sizeof(int32), 1, fp); if (oids) fwrite((char *) &tuple->t_oid, sizeof(int32), 1, fp); fwrite(&null_ct, sizeof(int32), 1, fp); if (null_ct > 0) { for (i = 0; i < attr_count; i++) { if (nulls[i] == 'n') { fwrite(&i, sizeof(int32), 1, fp); nulls[i] = ' '; } } } fwrite((char *) tuple + tuple->t_hoff, length, 1, fp); } } heap_endscan(scandesc); if (binary) pfree(nulls); else { pfree(out_functions); pfree(elements); pfree(typmod); } heap_close(rel); } static void CopyFrom(Relation rel, bool binary, bool oids, FILE *fp, char *delim) { HeapTuple tuple; AttrNumber attr_count; AttributeTupleForm *attr; FmgrInfo *in_functions; int i; Oid in_func_oid; Datum *values; char *nulls, *index_nulls; bool *byval; bool isnull; bool has_index; int done = 0; char *string = NULL, *ptr; Relation *index_rels; int32 len, null_ct, null_id; int32 ntuples, tuples_read = 0; bool reading_to_eof = true; Oid *elements; int16 *typmod; FuncIndexInfo *finfo, **finfoP = NULL; TupleDesc *itupdescArr; HeapTuple pgIndexTup; IndexTupleForm *pgIndexP = NULL; int *indexNatts = NULL; char *predString; Node **indexPred = NULL; TupleDesc rtupdesc; ExprContext *econtext = NULL; #ifndef OMIT_PARTIAL_INDEX TupleTable tupleTable; TupleTableSlot *slot = NULL; #endif int natts; AttrNumber *attnumP; Datum *idatum; int n_indices; InsertIndexResult indexRes; TupleDesc tupDesc; Oid loaded_oid; bool skip_tuple = false; tupDesc = RelationGetTupleDescriptor(rel); attr = tupDesc->attrs; attr_count = tupDesc->natts; has_index = false; /* * This may be a scalar or a functional index. We initialize all * kinds of arrays here to avoid doing extra work at every tuple copy. */ if (rel->rd_rel->relhasindex) { GetIndexRelations(rel->rd_id, &n_indices, &index_rels); if (n_indices > 0) { has_index = true; itupdescArr = (TupleDesc *) palloc(n_indices * sizeof(TupleDesc)); pgIndexP = (IndexTupleForm *) palloc(n_indices * sizeof(IndexTupleForm)); indexNatts = (int *) palloc(n_indices * sizeof(int)); finfo = (FuncIndexInfo *) palloc(n_indices * sizeof(FuncIndexInfo)); finfoP = (FuncIndexInfo **) palloc(n_indices * sizeof(FuncIndexInfo *)); indexPred = (Node **) palloc(n_indices * sizeof(Node *)); econtext = NULL; for (i = 0; i < n_indices; i++) { itupdescArr[i] = RelationGetTupleDescriptor(index_rels[i]); pgIndexTup = SearchSysCacheTuple(INDEXRELID, ObjectIdGetDatum(index_rels[i]->rd_id), 0, 0, 0); Assert(pgIndexTup); pgIndexP[i] = (IndexTupleForm) GETSTRUCT(pgIndexTup); for (attnumP = &(pgIndexP[i]->indkey[0]), natts = 0; *attnumP != InvalidAttrNumber; attnumP++, natts++); if (pgIndexP[i]->indproc != InvalidOid) { FIgetnArgs(&finfo[i]) = natts; natts = 1; FIgetProcOid(&finfo[i]) = pgIndexP[i]->indproc; *(FIgetname(&finfo[i])) = '\0'; finfoP[i] = &finfo[i]; } else finfoP[i] = (FuncIndexInfo *) NULL; indexNatts[i] = natts; if (VARSIZE(&pgIndexP[i]->indpred) != 0) { predString = fmgr(F_TEXTOUT, &pgIndexP[i]->indpred); indexPred[i] = stringToNode(predString); pfree(predString); /* make dummy ExprContext for use by ExecQual */ if (econtext == NULL) { #ifndef OMIT_PARTIAL_INDEX tupleTable = ExecCreateTupleTable(1); slot = ExecAllocTableSlot(tupleTable); econtext = makeNode(ExprContext); econtext->ecxt_scantuple = slot; rtupdesc = RelationGetTupleDescriptor(rel); slot->ttc_tupleDescriptor = rtupdesc; /* * There's no buffer associated with heap tuples * here, so I set the slot's buffer to NULL. * Currently, it appears that the only way a * buffer could be needed would be if the partial * index predicate referred to the "lock" system * attribute. If it did, then heap_getattr would * call HeapTupleGetRuleLock, which uses the * buffer's descriptor to get the relation id. * Rather than try to fix this, I'll just disallow * partial indexes on "lock", which wouldn't be * useful anyway. --Nels, Nov '92 */ /* SetSlotBuffer(slot, (Buffer) NULL); */ /* SetSlotShouldFree(slot, false); */ slot->ttc_buffer = (Buffer) NULL; slot->ttc_shouldFree = false; #endif /* OMIT_PARTIAL_INDEX */ } } else indexPred[i] = NULL; } } } if (!binary) { in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo)); elements = (Oid *) palloc(attr_count * sizeof(Oid)); typmod = (int16 *) palloc(attr_count * sizeof(int16)); for (i = 0; i < attr_count; i++) { in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid); fmgr_info(in_func_oid, &in_functions[i]); elements[i] = GetTypeElement(attr[i]->atttypid); typmod[i] = attr[i]->atttypmod; } } else { in_functions = NULL; elements = NULL; typmod = NULL; fread(&ntuples, sizeof(int32), 1, fp); if (ntuples != 0) reading_to_eof = false; } values = (Datum *) palloc(sizeof(Datum) * attr_count); nulls = (char *) palloc(attr_count); index_nulls = (char *) palloc(attr_count); idatum = (Datum *) palloc(sizeof(Datum) * attr_count); byval = (bool *) palloc(attr_count * sizeof(bool)); for (i = 0; i < attr_count; i++) { nulls[i] = ' '; index_nulls[i] = ' '; byval[i] = (bool) IsTypeByVal(attr[i]->atttypid); } lineno = 0; while (!done) { if (!binary) { #ifdef COPY_PATCH int newline = 0; #endif lineno++; if (oids) { #ifdef COPY_PATCH string = CopyReadAttribute(fp, &isnull, delim, &newline); #else string = CopyReadAttribute(fp, &isnull, delim); #endif if (string == NULL) done = 1; else { loaded_oid = oidin(string); if (loaded_oid < BootstrapObjectIdData) elog(ERROR, "COPY TEXT: Invalid Oid. line: %d", lineno); } } for (i = 0; i < attr_count && !done; i++) { #ifdef COPY_PATCH string = CopyReadAttribute(fp, &isnull, delim, &newline); #else string = CopyReadAttribute(fp, &isnull, delim); #endif if (isnull) { values[i] = PointerGetDatum(NULL); nulls[i] = 'n'; } else if (string == NULL) done = 1; else { values[i] = (Datum) (*fmgr_faddr(&in_functions[i])) (string, elements[i], typmod[i]); /* * Sanity check - by reference attributes cannot * return NULL */ if (!PointerIsValid(values[i]) && !(rel->rd_att->attrs[i]->attbyval)) elog(ERROR, "copy from line %d: Bad file format", lineno); } } #ifdef COPY_PATCH if (!done) CopyReadNewline(fp, &newline); #endif } else { /* binary */ fread(&len, sizeof(int32), 1, fp); if (feof(fp)) done = 1; else { if (oids) { fread(&loaded_oid, sizeof(int32), 1, fp); if (loaded_oid < BootstrapObjectIdData) elog(ERROR, "COPY BINARY: Invalid Oid line: %d", lineno); } fread(&null_ct, sizeof(int32), 1, fp); if (null_ct > 0) { for (i = 0; i < null_ct; i++) { fread(&null_id, sizeof(int32), 1, fp); nulls[null_id] = 'n'; } } string = (char *) palloc(len); fread(string, len, 1, fp); ptr = string; for (i = 0; i < attr_count; i++) { if (byval[i] && nulls[i] != 'n') { switch (attr[i]->attlen) { case sizeof(char): values[i] = (Datum) *(unsigned char *) ptr; ptr += sizeof(char); break; case sizeof(short): ptr = (char *) SHORTALIGN(ptr); values[i] = (Datum) *(unsigned short *) ptr; ptr += sizeof(short); break; case sizeof(int32): ptr = (char *) INTALIGN(ptr); values[i] = (Datum) *(uint32 *) ptr; ptr += sizeof(int32); break; default: elog(ERROR, "COPY BINARY: impossible size! line: %d", lineno); break; } } else if (nulls[i] != 'n') { switch (attr[i]->attlen) { case -1: if (attr[i]->attalign == 'd') ptr = (char *) DOUBLEALIGN(ptr); else ptr = (char *) INTALIGN(ptr); values[i] = (Datum) ptr; ptr += *(uint32 *) ptr; break; case sizeof(char): values[i] = (Datum) ptr; ptr += attr[i]->attlen; break; case sizeof(short): ptr = (char *) SHORTALIGN(ptr); values[i] = (Datum) ptr; ptr += attr[i]->attlen; break; case sizeof(int32): ptr = (char *) INTALIGN(ptr); values[i] = (Datum) ptr; ptr += attr[i]->attlen; break; default: if (attr[i]->attalign == 'd') ptr = (char *) DOUBLEALIGN(ptr); else ptr = (char *) LONGALIGN(ptr); values[i] = (Datum) ptr; ptr += attr[i]->attlen; } } } } } if (done) continue; /* * Does it have any sence ? - vadim 12/14/96 * * tupDesc = CreateTupleDesc(attr_count, attr); */ tuple = heap_formtuple(tupDesc, values, nulls); if (oids) tuple->t_oid = loaded_oid; skip_tuple = false; /* BEFORE ROW INSERT Triggers */ if (rel->trigdesc && rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) { HeapTuple newtuple; newtuple = ExecBRInsertTriggers(rel, tuple); if (newtuple == NULL) /* "do nothing" */ skip_tuple = true; else if (newtuple != tuple) /* modified by Trigger(s) */ { pfree(tuple); tuple = newtuple; } } if (!skip_tuple) { /* ---------------- * Check the constraints of a tuple * ---------------- */ if (rel->rd_att->constr) { HeapTuple newtuple; newtuple = ExecConstraints("CopyFrom", rel, tuple); if (newtuple != tuple) { pfree(tuple); tuple = newtuple; } } heap_insert(rel, tuple); if (has_index) { for (i = 0; i < n_indices; i++) { if (indexPred[i] != NULL) { #ifndef OMIT_PARTIAL_INDEX /* * if tuple doesn't satisfy predicate, don't * update index */ slot->val = tuple; /* SetSlotContents(slot, tuple); */ if (ExecQual((List *) indexPred[i], econtext) == false) continue; #endif /* OMIT_PARTIAL_INDEX */ } FormIndexDatum(indexNatts[i], (AttrNumber *) &(pgIndexP[i]->indkey[0]), tuple, tupDesc, InvalidBuffer, idatum, index_nulls, finfoP[i]); indexRes = index_insert(index_rels[i], idatum, index_nulls, &(tuple->t_ctid), rel); if (indexRes) pfree(indexRes); } } /* AFTER ROW INSERT Triggers */ if (rel->trigdesc && rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) ExecARInsertTriggers(rel, tuple); } if (binary) pfree(string); for (i = 0; i < attr_count; i++) { if (!byval[i] && nulls[i] != 'n') { if (!binary) pfree((void *) values[i]); } else if (nulls[i] == 'n') nulls[i] = ' '; } pfree(tuple); tuples_read++; if (!reading_to_eof && ntuples == tuples_read) done = true; } pfree(values); pfree(nulls); if (!binary) { pfree(in_functions); pfree(elements); pfree(typmod); } pfree(byval); heap_close(rel); } static Oid GetOutputFunction(Oid type) { HeapTuple typeTuple; typeTuple = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(type), 0, 0, 0); if (HeapTupleIsValid(typeTuple)) return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typoutput); elog(ERROR, "GetOutputFunction: Cache lookup of type %d failed", type); return (InvalidOid); } static Oid GetTypeElement(Oid type) { HeapTuple typeTuple; typeTuple = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(type), 0, 0, 0); if (HeapTupleIsValid(typeTuple)) return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typelem); elog(ERROR, "GetOutputFunction: Cache lookup of type %d failed", type); return (InvalidOid); } static Oid GetInputFunction(Oid type) { HeapTuple typeTuple; typeTuple = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(type), 0, 0, 0); if (HeapTupleIsValid(typeTuple)) return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typinput); elog(ERROR, "GetInputFunction: Cache lookup of type %d failed", type); return (InvalidOid); } static Oid IsTypeByVal(Oid type) { HeapTuple typeTuple; typeTuple = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(type), 0, 0, 0); if (HeapTupleIsValid(typeTuple)) return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typbyval); elog(ERROR, "GetInputFunction: Cache lookup of type %d failed", type); return (InvalidOid); } /* * Given the OID of a relation, return an array of index relation descriptors * and the number of index relations. These relation descriptors are open * using heap_open(). * * Space for the array itself is palloc'ed. */ typedef struct rel_list { Oid index_rel_oid; struct rel_list *next; } RelationList; static void GetIndexRelations(Oid main_relation_oid, int *n_indices, Relation **index_rels) { RelationList *head, *scan; Relation pg_index_rel; HeapScanDesc scandesc; Oid index_relation_oid; HeapTuple tuple; TupleDesc tupDesc; int i; bool isnull; pg_index_rel = heap_openr(IndexRelationName); scandesc = heap_beginscan(pg_index_rel, 0, false, 0, NULL); tupDesc = RelationGetTupleDescriptor(pg_index_rel); *n_indices = 0; head = (RelationList *) palloc(sizeof(RelationList)); scan = head; head->next = NULL; for (tuple = heap_getnext(scandesc, 0, NULL); tuple != NULL; tuple = heap_getnext(scandesc, 0, NULL)) { index_relation_oid = (Oid) DatumGetInt32(heap_getattr(tuple, 2, tupDesc, &isnull)); if (index_relation_oid == main_relation_oid) { scan->index_rel_oid = (Oid) DatumGetInt32(heap_getattr(tuple, Anum_pg_index_indexrelid, tupDesc, &isnull)); (*n_indices)++; scan->next = (RelationList *) palloc(sizeof(RelationList)); scan = scan->next; } } heap_endscan(scandesc); heap_close(pg_index_rel); /* We cannot trust to relhasindex of the main_relation now, so... */ if (*n_indices == 0) return; *index_rels = (Relation *) palloc(*n_indices * sizeof(Relation)); for (i = 0, scan = head; i < *n_indices; i++, scan = scan->next) (*index_rels)[i] = index_open(scan->index_rel_oid); for (i = 0, scan = head; i < *n_indices + 1; i++) { scan = head->next; pfree(head); head = scan; } } #define EXT_ATTLEN 5*8192 /* returns 1 is c is in s */ static bool inString(char c, char *s) { int i; if (s) { i = 0; while (s[i] != '\0') { if (s[i] == c) return 1; i++; } } return 0; } #ifdef COPY_PATCH /* * Reads input from fp until an end of line is seen. */ void CopyReadNewline(FILE *fp, int *newline) { if (!*newline) { elog(NOTICE, "CopyReadNewline: line %d - extra fields ignored", lineno); while (!feof(fp) && (getc(fp) != '\n')); } *newline = 0; } #endif /* * Reads input from fp until eof is seen. If we are reading from standard * input, AND we see a dot on a line by itself (a dot followed immediately * by a newline), we exit as if we saw eof. This is so that copy pipelines * can be used as standard input. */ static char * #ifdef COPY_PATCH CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline) #else CopyReadAttribute(FILE *fp, bool *isnull, char *delim) #endif { static char attribute[EXT_ATTLEN]; char c; int done = 0; int i = 0; #ifdef COPY_PATCH /* if last delimiter was a newline return a NULL attribute */ if (*newline) { *isnull = (bool) true; return (NULL); } #endif *isnull = (bool) false; /* set default */ if (feof(fp)) return (NULL); while (!done) { c = getc(fp); if (feof(fp)) return (NULL); else if (c == '\\') { c = getc(fp); if (feof(fp)) return (NULL); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int val; val = VALUE(c); c = getc(fp); if (ISOCTAL(c)) { val = (val << 3) + VALUE(c); c = getc(fp); if (ISOCTAL(c)) val = (val << 3) + VALUE(c); else { if (feof(fp)) return (NULL); ungetc(c, fp); } } else { if (feof(fp)) return (NULL); ungetc(c, fp); } c = val & 0377; } break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'N': attribute[0] = '\0'; /* just to be safe */ *isnull = (bool) true; break; case '.': c = getc(fp); if (c != '\n') elog(ERROR, "CopyReadAttribute - end of record marker corrupted. line: %d", lineno); return (NULL); break; } } else if (inString(c, delim) || c == '\n') { #ifdef COPY_PATCH if (c == '\n') *newline = 1; #endif done = 1; } if (!done) attribute[i++] = c; if (i == EXT_ATTLEN - 1) elog(ERROR, "CopyReadAttribute - attribute length too long. line: %d", lineno); } attribute[i] = '\0'; return (&attribute[0]); } static void CopyAttributeOut(FILE *fp, char *string, char *delim, int is_array) { char c; for (; (c = *string) != '\0'; string++) { if (c == delim[0] || c == '\n' || (c == '\\' && !is_array)) fputc('\\', fp); else if (c == '\\' && is_array) { if (*(string + 1) == '\\') { /* translate \\ to \\\\ */ fputc('\\', fp); fputc('\\', fp); fputc('\\', fp); string++; } else if (*(string + 1) == '"') { /* translate \" to \\\" */ fputc('\\', fp); fputc('\\', fp); } } fputc(*string, fp); } } /* * Returns the number of tuples in a relation. Unfortunately, currently * must do a scan of the entire relation to determine this. * * relation is expected to be an open relation descriptor. */ static int CountTuples(Relation relation) { HeapScanDesc scandesc; HeapTuple tuple; int i; scandesc = heap_beginscan(relation, 0, false, 0, NULL); for (tuple = heap_getnext(scandesc, 0, NULL), i = 0; tuple != NULL; tuple = heap_getnext(scandesc, 0, NULL), i++) ; heap_endscan(scandesc); return (i); }