Index: doc/src/sgml/ref/copy.sgml =================================================================== RCS file: /var/lib/cvs/pgsql/doc/src/sgml/ref/copy.sgml,v retrieving revision 1.33 diff -c -r1.33 copy.sgml *** doc/src/sgml/ref/copy.sgml 18 Jul 2002 04:43:50 -0000 1.33 --- doc/src/sgml/ref/copy.sgml 26 Jul 2002 01:04:21 -0000 *************** *** 199,210 **** whatever is in the table already). ! ! When using the optional column list syntax, COPY TO ! and COPY FROM will only copy the specified ! columns' values to/from the table. If a column in the table ! is not in the column list, COPY FROM will insert ! default values for that column if a default value is defined. --- 199,210 ---- whatever is in the table already). ! ! If a list of columns is specified, COPY will ! only copy the data in the specified columns to or from the table. ! If there are any columns in the table that are not in the table, ! COPY FROM will insert the default value for ! that column. *************** *** 266,273 **** ! COPY FROM neither invokes rules nor acts on column ! defaults. It does invoke triggers and check constraints. --- 266,273 ---- ! COPY FROM will invoke any triggers or check ! constraints. However, it will not invoke rules. *************** *** 330,336 **** The attribute values themselves are strings generated by the output function, or acceptable to the input function, of each attribute's data type. The specified null-value string is used in ! place of attributes that are NULL. If OIDS is specified, the OID is read or written as the first column, --- 330,341 ---- The attribute values themselves are strings generated by the output function, or acceptable to the input function, of each attribute's data type. The specified null-value string is used in ! place of attributes that are NULL. When using COPY ! FROM without a column list, each row of the input file ! must contain data for each attribute in the table: no missing data ! is allowed. Similarly, COPY FROM will raise ! an error if it encounters any data in the input file that would ! not be inserted into the table: extra data is not allowed. If OIDS is specified, the OID is read or written as the first column, Index: src/backend/commands/copy.c =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/commands/copy.c,v retrieving revision 1.160 diff -c -r1.160 copy.c *** src/backend/commands/copy.c 20 Jul 2002 05:16:57 -0000 1.160 --- src/backend/commands/copy.c 26 Jul 2002 01:04:21 -0000 *************** *** 45,60 **** #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) #define OCTVALUE(c) ((c) - '0') /* non-export function prototypes */ static void CopyTo(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print); static void CopyFrom(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print); static Oid GetInputFunction(Oid type); static Oid GetTypeElement(Oid type); ! static void CopyReadNewline(FILE *fp, int *newline); ! static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print); static void CopyAttributeOut(FILE *fp, char *string, char *delim); ! static void CopyAssertAttlist(Relation rel, List* attlist, bool from); static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0"; --- 45,65 ---- #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) #define OCTVALUE(c) ((c) - '0') + typedef enum CopyReadResult + { + NORMAL_ATTR, + END_OF_LINE, + END_OF_FILE + } CopyReadResult; /* non-export function prototypes */ static void CopyTo(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print); static void CopyFrom(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print); static Oid GetInputFunction(Oid type); static Oid GetTypeElement(Oid type); ! static char *CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result); static void CopyAttributeOut(FILE *fp, char *string, char *delim); ! static void CopyCheckAttlist(Relation rel, List *attlist); static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0"; *************** *** 271,284 **** bool pipe = (stmt->filename == NULL); List *option; List *attlist = stmt->attlist; - DefElem *dbinary = NULL; - DefElem *doids = NULL; - DefElem *ddelim = NULL; - DefElem *dnull = NULL; bool binary = false; bool oids = false; ! char *delim = "\t"; ! char *null_print = "\\N"; FILE *fp; Relation rel; AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); --- 276,285 ---- bool pipe = (stmt->filename == NULL); List *option; List *attlist = stmt->attlist; bool binary = false; bool oids = false; ! char *delim = NULL; ! char *null_print = NULL; FILE *fp; Relation rel; AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); *************** *** 289,339 **** { DefElem *defel = (DefElem *) lfirst(option); if (strcmp(defel->defname, "binary") == 0) { ! if (dbinary) ! /* should this really be an error? */ elog(ERROR, "COPY: BINARY option appears more than once"); ! dbinary = defel; } else if (strcmp(defel->defname, "oids") == 0) { ! if (doids) ! /* should this really be an error? */ elog(ERROR, "COPY: OIDS option appears more than once"); ! doids = defel; } else if (strcmp(defel->defname, "delimiter") == 0) { ! if (ddelim) elog(ERROR, "COPY: DELIMITER string may only be defined once in query"); ! ddelim = defel; } else if (strcmp(defel->defname, "null") == 0) { ! if (dnull) elog(ERROR, "COPY: NULL representation may only be defined once in query"); ! dnull = defel; } else elog(ERROR, "COPY: option \"%s\" not recognized", defel->defname); } ! if (dbinary) ! binary = intVal(dbinary->arg); ! if (doids) ! oids = intVal(doids->arg); ! if (ddelim) ! delim = strVal(ddelim->arg); ! if (dnull) ! null_print = strVal(dnull->arg); ! ! if (binary && ddelim) elog(ERROR, "You can not specify the DELIMITER in BINARY mode."); ! if (binary && dnull) elog(ERROR, "You can not specify NULL in BINARY mode."); /* * Open and lock the relation, using the appropriate lock type. --- 290,342 ---- { DefElem *defel = (DefElem *) lfirst(option); + /* XXX: Should we bother checking for doubled options? */ + if (strcmp(defel->defname, "binary") == 0) { ! if (binary) elog(ERROR, "COPY: BINARY option appears more than once"); ! ! binary = intVal(defel->arg); } else if (strcmp(defel->defname, "oids") == 0) { ! if (oids) elog(ERROR, "COPY: OIDS option appears more than once"); ! ! oids = intVal(defel->arg); } else if (strcmp(defel->defname, "delimiter") == 0) { ! if (delim) elog(ERROR, "COPY: DELIMITER string may only be defined once in query"); ! ! delim = strVal(defel->arg); } else if (strcmp(defel->defname, "null") == 0) { ! if (null_print) elog(ERROR, "COPY: NULL representation may only be defined once in query"); ! ! null_print = strVal(defel->arg); } else elog(ERROR, "COPY: option \"%s\" not recognized", defel->defname); } ! if (binary && delim) elog(ERROR, "You can not specify the DELIMITER in BINARY mode."); ! if (binary && null_print) elog(ERROR, "You can not specify NULL in BINARY mode."); + + /* Set defaults */ + if (!delim) + delim = "\t"; + + if (!null_print) + null_print = "\\N"; /* * Open and lock the relation, using the appropriate lock type. *************** *** 364,369 **** --- 367,379 ---- elog(ERROR, "COPY delimiter must be a single character"); /* + * Don't allow COPY w/ OIDs to or from a table without them + */ + if (oids && !rel->rd_rel->relhasoids) + elog(ERROR, "COPY: table \"%s\" does not have OIDs", + RelationGetRelationName(rel)); + + /* * Set up variables to avoid per-attribute overhead. */ initStringInfo(&attribute_buf); *************** *** 372,393 **** server_encoding = GetDatabaseEncoding(); #endif ! if( attlist == NIL ){ /* get list of attributes in the relation */ TupleDesc desc = RelationGetDescr(rel); int i; ! for(i = 0; i < desc->natts; ++i){ ! Ident* id = makeNode(Ident); id->name = NameStr(desc->attrs[i]->attname); attlist = lappend(attlist,id); } } ! else{ ! if( binary ){ elog(ERROR,"COPY: BINARY format cannot be used with specific column list"); ! } ! /* verify that any user-specified attributes exist in the relation */ ! CopyAssertAttlist(rel,attlist,is_from); } if (is_from) --- 382,405 ---- server_encoding = GetDatabaseEncoding(); #endif ! if (attlist == NIL) ! { /* get list of attributes in the relation */ TupleDesc desc = RelationGetDescr(rel); int i; ! for (i = 0; i < desc->natts; ++i) ! { ! Ident *id = makeNode(Ident); id->name = NameStr(desc->attrs[i]->attname); attlist = lappend(attlist,id); } } ! else ! { ! if (binary) elog(ERROR,"COPY: BINARY format cannot be used with specific column list"); ! ! CopyCheckAttlist(rel, attlist); } if (is_from) *************** *** 532,558 **** int16 fld_size; char *string; Snapshot mySnapshot; ! int copy_attr_count; ! int* attmap; ! int p = 0; ! List* cur; ! ! if (oids && !rel->rd_rel->relhasoids) ! elog(ERROR, "COPY: table %s does not have OIDs", ! RelationGetRelationName(rel)); tupDesc = rel->rd_att; attr_count = rel->rd_att->natts; attr = rel->rd_att->attrs; copy_attr_count = length(attlist); { ! attmap = (int*)palloc(copy_attr_count * sizeof(int)); ! foreach(cur,attlist){ ! for (i = 0; i < attr_count; i++){ ! if( strcmp(strVal(lfirst(cur)),NameStr(attr[i]->attname)) == 0){ ! attmap[p++] = i; ! continue; ! } } } } --- 544,571 ---- int16 fld_size; char *string; Snapshot mySnapshot; ! int copy_attr_count; ! int *attmap; ! int p = 0; ! List *cur; tupDesc = rel->rd_att; attr_count = rel->rd_att->natts; attr = rel->rd_att->attrs; + copy_attr_count = length(attlist); + attmap = (int *) palloc(copy_attr_count * sizeof(int)); + + foreach(cur, attlist) { ! const char *currAtt = strVal(lfirst(cur)); ! ! for (i = 0; i < attr_count; i++) ! { ! if (namestrcmp(&attr[i]->attname, currAtt) == 0) ! { ! attmap[p++] = i; ! continue; } } } *************** *** 642,648 **** Datum origvalue, value; bool isnull; ! int mi = attmap[i]; origvalue = heap_getattr(tuple, mi + 1, tupDesc, &isnull); --- 655,661 ---- Datum origvalue, value; bool isnull; ! int mi = attmap[i]; origvalue = heap_getattr(tuple, mi + 1, tupDesc, &isnull); *************** *** 767,785 **** Oid in_func_oid; Datum *values; char *nulls; - bool isnull; int done = 0; - char *string; ResultRelInfo *resultRelInfo; EState *estate = CreateExecutorState(); /* for ExecConstraints() */ TupleTable tupleTable; TupleTableSlot *slot; - Oid loaded_oid = InvalidOid; - bool skip_tuple = false; bool file_has_oids; ! int* attmap = NULL; ! int* defmap = NULL; ! Node** defexprs = NULL; /* array of default att expressions */ ExprContext *econtext; /* used for ExecEvalExpr for default atts */ ExprDoneCond isdone; --- 780,794 ---- Oid in_func_oid; Datum *values; char *nulls; int done = 0; ResultRelInfo *resultRelInfo; EState *estate = CreateExecutorState(); /* for ExecConstraints() */ TupleTable tupleTable; TupleTableSlot *slot; bool file_has_oids; ! int *attmap = NULL; ! int *defmap = NULL; ! Node **defexprs = NULL; /* array of default att expressions */ ExprContext *econtext; /* used for ExecEvalExpr for default atts */ ExprDoneCond isdone; *************** *** 810,849 **** slot = ExecAllocTableSlot(tupleTable); ExecSetSlotDescriptor(slot, tupDesc, false); - if (!binary) { /* * pick up the input function and default expression (if any) for * each attribute in the relation. */ ! List* cur; ! attmap = (int*)palloc(sizeof(int) * attr_count); ! defmap = (int*)palloc(sizeof(int) * attr_count); ! defexprs = (Node**)palloc(sizeof(Node*) * attr_count); in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo)); elements = (Oid *) palloc(attr_count * sizeof(Oid)); for (i = 0; i < attr_count; i++) { ! int p = 0; ! bool specified = false; in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid); fmgr_info(in_func_oid, &in_functions[i]); elements[i] = GetTypeElement(attr[i]->atttypid); ! foreach(cur,attlist){ ! if( strcmp(strVal(lfirst(cur)),NameStr(attr[i]->attname)) == 0){ attmap[p] = i; specified = true; continue; } ! ++p; } ! if( ! specified ){ ! /* column not specified, try to get a default */ ! defexprs[def_attr_count] = build_column_default(rel,i+1); ! if( defexprs[def_attr_count] != NULL ){ defmap[def_attr_count] = i; ! ++def_attr_count; } } } --- 819,865 ---- slot = ExecAllocTableSlot(tupleTable); ExecSetSlotDescriptor(slot, tupDesc, false); if (!binary) { /* * pick up the input function and default expression (if any) for * each attribute in the relation. */ ! attmap = (int *) palloc(sizeof(int) * attr_count); ! defmap = (int *) palloc(sizeof(int) * attr_count); ! defexprs = (Node **) palloc(sizeof(Node *) * attr_count); in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo)); elements = (Oid *) palloc(attr_count * sizeof(Oid)); for (i = 0; i < attr_count; i++) { ! List *l; ! int p = 0; ! bool specified = false; ! in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid); fmgr_info(in_func_oid, &in_functions[i]); elements[i] = GetTypeElement(attr[i]->atttypid); ! ! foreach(l, attlist) ! { ! if (namestrcmp(&attr[i]->attname, strVal(lfirst(l))) == 0) ! { attmap[p] = i; specified = true; continue; } ! p++; } ! ! /* if column not specified, use default value if one exists */ ! if (! specified) ! { ! defexprs[def_attr_count] = build_column_default(rel, i + 1); ! ! if (defexprs[def_attr_count] != NULL) ! { defmap[def_attr_count] = i; ! def_attr_count++; } } } *************** *** 857,869 **** /* Signature */ CopyGetData(readSig, 12, fp); ! if (CopyGetEof(fp) || ! memcmp(readSig, BinarySignature, 12) != 0) elog(ERROR, "COPY BINARY: file signature not recognized"); /* Integer layout field */ CopyGetData(&tmp, sizeof(int32), fp); ! if (CopyGetEof(fp) || ! tmp != 0x01020304) elog(ERROR, "COPY BINARY: incompatible integer layout"); /* Flags field */ CopyGetData(&tmp, sizeof(int32), fp); --- 873,883 ---- /* Signature */ CopyGetData(readSig, 12, fp); ! if (CopyGetEof(fp) || memcmp(readSig, BinarySignature, 12) != 0) elog(ERROR, "COPY BINARY: file signature not recognized"); /* Integer layout field */ CopyGetData(&tmp, sizeof(int32), fp); ! if (CopyGetEof(fp) || tmp != 0x01020304) elog(ERROR, "COPY BINARY: incompatible integer layout"); /* Flags field */ CopyGetData(&tmp, sizeof(int32), fp); *************** *** 875,882 **** elog(ERROR, "COPY BINARY: unrecognized critical flags in header"); /* Header extension length */ CopyGetData(&tmp, sizeof(int32), fp); ! if (CopyGetEof(fp) || ! tmp < 0) elog(ERROR, "COPY BINARY: bogus file header (missing length)"); /* Skip extension header, if present */ while (tmp-- > 0) --- 889,895 ---- elog(ERROR, "COPY BINARY: unrecognized critical flags in header"); /* Header extension length */ CopyGetData(&tmp, sizeof(int32), fp); ! if (CopyGetEof(fp) || tmp < 0) elog(ERROR, "COPY BINARY: bogus file header (missing length)"); /* Skip extension header, if present */ while (tmp-- > 0) *************** *** 890,899 **** elements = NULL; } - /* Silently drop incoming OIDs if table does not have OIDs */ - if (!rel->rd_rel->relhasoids) - oids = false; - values = (Datum *) palloc(attr_count * sizeof(Datum)); nulls = (char *) palloc(attr_count * sizeof(char)); --- 903,908 ---- *************** *** 904,909 **** --- 913,921 ---- while (!done) { + bool skip_tuple; + Oid loaded_oid; + CHECK_FOR_INTERRUPTS(); copy_lineno++; *************** *** 917,932 **** if (!binary) { ! int newline = 0; if (file_has_oids) { ! string = CopyReadAttribute(fp, &isnull, delim, ! &newline, null_print); ! if (isnull) elog(ERROR, "COPY TEXT: NULL Oid"); - else if (string == NULL) - done = 1; /* end of file */ else { loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin, --- 929,945 ---- if (!binary) { ! CopyReadResult result; ! char *string; if (file_has_oids) { ! string = CopyReadAttribute(fp, delim, &result); ! ! if (result == END_OF_FILE) ! done = 1; ! else if (string == NULL || strcmp(string, null_print) == 0) elog(ERROR, "COPY TEXT: NULL Oid"); else { loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin, *************** *** 943,978 **** for (i = 0; i < copy_attr_count && !done; i++) { int m = attmap[i]; - string = CopyReadAttribute(fp, &isnull, delim, - &newline, null_print); ! if( isnull ){ ! /* nothing */ } - else if (string == NULL) - done = 1; /* end of file */ else { values[m] = FunctionCall3(&in_functions[m], ! CStringGetDatum(string), ! ObjectIdGetDatum(elements[m]), ! Int32GetDatum(attr[m]->atttypmod)); nulls[m] = ' '; } } ! /* * as above, we only try a default lookup if one is * known to be available */ ! for (i = 0; i < def_attr_count && !done; i++){ bool isnull; ! values[defmap[i]] = ExecEvalExpr(defexprs[i],econtext,&isnull,&isdone); ! if( ! isnull ) ! nulls[defmap[i]] = ' '; } - if (!done) - CopyReadNewline(fp, &newline); } else { /* binary */ --- 956,1000 ---- for (i = 0; i < copy_attr_count && !done; i++) { int m = attmap[i]; ! string = CopyReadAttribute(fp, delim, &result); ! ! /* If we got an end-of-line before we expected, bail out */ ! if (result == END_OF_LINE && i < (copy_attr_count - 1)) ! elog(ERROR, "COPY TEXT: Missing data for attribute %d", i + 1); ! ! if (result == END_OF_FILE) ! done = 1; ! else if (strcmp(string, null_print) == 0) ! { ! /* we read an SQL NULL, no need to do anything */ } else { values[m] = FunctionCall3(&in_functions[m], ! CStringGetDatum(string), ! ObjectIdGetDatum(elements[m]), ! Int32GetDatum(attr[m]->atttypmod)); nulls[m] = ' '; } } ! ! if (result == NORMAL_ATTR && !done) ! elog(ERROR, "COPY TEXT: Extra data encountered"); ! /* * as above, we only try a default lookup if one is * known to be available */ ! for (i = 0; i < def_attr_count && !done; i++) ! { bool isnull; ! values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext, ! &isnull, &isdone); ! ! if (! isnull) ! nulls[defmap[i]] = ' '; } } else { /* binary */ *************** *** 980,987 **** fld_size; CopyGetData(&fld_count, sizeof(int16), fp); ! if (CopyGetEof(fp) || ! fld_count == -1) done = 1; else { --- 1002,1008 ---- fld_size; CopyGetData(&fld_count, sizeof(int16), fp); ! if (CopyGetEof(fp) || fld_count == -1) done = 1; else { *************** *** 1173,1208 **** return result; } - - /* - * Reads input from fp until an end of line is seen. - */ - - static void - CopyReadNewline(FILE *fp, int *newline) - { - if (!*newline) - { - elog(WARNING, "CopyReadNewline: extra fields ignored"); - while (!CopyGetEof(fp) && (CopyGetChar(fp) != '\n')); - } - *newline = 0; - } - /* * Read the value of a single attribute. * ! * Result is either a string, or NULL (if EOF or a null attribute). ! * Note that the caller should not pfree the string! * ! * *isnull is set true if a null attribute, else false. ! * delim is the column delimiter string (currently always 1 character). ! * *newline remembers whether we've seen a newline ending this tuple. ! * null_print says how NULL values are represented */ static char * ! CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print) { int c; int delimc = (unsigned char)delim[0]; --- 1194,1218 ---- return result; } /* * Read the value of a single attribute. * ! * Results are returned in the status indicator, as well as the ! * return value. If a value was successfully read but there is ! * more to read before EOL, NORMAL_ATTR is set and the value read ! * is returned. If a value was read and we hit EOL, END_OF_LINE ! * is set and the value read is returned. If we hit the EOF, ! * END_OF_FILE is set and NULL is returned. * ! * Note: This function does not care about SQL NULL values -- it ! * is the caller's responsibility to check if the returned string ! * matches what the user specified for the SQL NULL value. ! * ! * delim is the column delimiter string. */ static char * ! CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) { int c; int delimc = (unsigned char)delim[0]; *************** *** 1220,1242 **** attribute_buf.len = 0; attribute_buf.data[0] = '\0'; ! /* if last delimiter was a newline return a NULL attribute */ ! if (*newline) ! { ! *isnull = (bool) true; ! return NULL; ! } ! ! *isnull = (bool) false; /* set default */ for (;;) { c = CopyGetChar(fp); if (c == EOF) ! goto endOfFile; if (c == '\n') { ! *newline = 1; break; } if (c == delimc) --- 1230,1249 ---- attribute_buf.len = 0; attribute_buf.data[0] = '\0'; ! /* set default */ ! *result = NORMAL_ATTR; for (;;) { c = CopyGetChar(fp); if (c == EOF) ! { ! *result = END_OF_FILE; ! return NULL; ! } if (c == '\n') { ! *result = END_OF_LINE; break; } if (c == delimc) *************** *** 1245,1251 **** { c = CopyGetChar(fp); if (c == EOF) ! goto endOfFile; switch (c) { case '0': --- 1252,1261 ---- { c = CopyGetChar(fp); if (c == EOF) ! { ! *result = END_OF_FILE; ! return NULL; ! } switch (c) { case '0': *************** *** 1274,1287 **** else { if (c == EOF) ! goto endOfFile; CopyDonePeek(fp, c, false /* put back */ ); } } else { if (c == EOF) ! goto endOfFile; CopyDonePeek(fp, c, false /* put back */ ); } c = val & 0377; --- 1284,1303 ---- else { if (c == EOF) ! { ! *result = END_OF_FILE; ! return NULL; ! } CopyDonePeek(fp, c, false /* put back */ ); } } else { if (c == EOF) ! { ! *result = END_OF_FILE; ! return NULL; ! } CopyDonePeek(fp, c, false /* put back */ ); } c = val & 0377; *************** *** 1319,1325 **** c = CopyGetChar(fp); if (c != '\n') elog(ERROR, "CopyReadAttribute: end of record marker corrupted"); ! goto endOfFile; } } appendStringInfoCharMacro(&attribute_buf, c); --- 1335,1342 ---- c = CopyGetChar(fp); if (c != '\n') elog(ERROR, "CopyReadAttribute: end of record marker corrupted"); ! *result = END_OF_FILE; ! return NULL; } } appendStringInfoCharMacro(&attribute_buf, c); *************** *** 1334,1340 **** { c = CopyGetChar(fp); if (c == EOF) ! goto endOfFile; appendStringInfoCharMacro(&attribute_buf, c); } } --- 1351,1360 ---- { c = CopyGetChar(fp); if (c == EOF) ! { ! *result = END_OF_FILE; ! return NULL; ! } appendStringInfoCharMacro(&attribute_buf, c); } } *************** *** 1357,1369 **** } #endif - if (strcmp(attribute_buf.data, null_print) == 0) - *isnull = true; - return attribute_buf.data; - - endOfFile: - return NULL; } static void --- 1377,1383 ---- *************** *** 1454,1502 **** } /* ! * CopyAssertAttlist: elog(ERROR,...) if the specified attlist * is not valid for the Relation */ static void ! CopyAssertAttlist(Relation rel, List* attlist, bool from) { ! TupleDesc tupDesc; ! List* cur; ! char* illegalattname = NULL; ! int attr_count; ! const char* to_or_from; ! ! if( attlist == NIL ) ! return; ! to_or_from = (from == true ? "FROM" : "TO"); tupDesc = RelationGetDescr(rel); ! Assert(tupDesc != NULL); ! ! /* ! * make sure there aren't more columns specified than are in the table ! */ ! attr_count = tupDesc->natts; ! if( attr_count < length(attlist) ) ! elog(ERROR,"More columns specified in COPY %s command than in target relation",to_or_from); ! ! /* ! * make sure no columns are specified that don't exist in the table ! */ ! foreach(cur,attlist) ! { ! int found = 0; ! int i = 0; ! for(;iattrs[i]->attname)) == 0) ! ++found; ! } ! if( ! found ) ! illegalattname = strVal(lfirst(cur)); ! } ! if( illegalattname ) ! elog(ERROR,"Attribute referenced in COPY %s command does not exist: \"%s.%s\"",to_or_from,RelationGetRelationName(rel),illegalattname); } --- 1468,1517 ---- } /* ! * CopyCheckAttlist: elog(ERROR,...) if the specified attlist * is not valid for the Relation */ static void ! CopyCheckAttlist(Relation rel, List *attlist) { ! TupleDesc tupDesc; ! int attr_count; ! List *l; ! if (attlist == NIL) ! return; tupDesc = RelationGetDescr(rel); ! Assert(tupDesc != NULL); ! ! /* ! * make sure there aren't more columns specified than are in the table ! */ ! attr_count = tupDesc->natts; ! if (attr_count < length(attlist)) ! elog(ERROR, "COPY: Too many columns specified"); ! ! /* ! * make sure no columns are specified that don't exist in the table ! */ ! foreach(l, attlist) ! { ! char *colName = strVal(lfirst(l)); ! bool found = false; ! int i; ! ! for (i = 0; i < attr_count; i++) ! { ! if (namestrcmp(&tupDesc->attrs[i]->attname, colName) == 0) ! { ! found = true; ! break; ! } ! } ! ! if (!found) ! elog(ERROR, "COPY: Specified column \"%s\" does not exist", ! colName); ! } } Index: src/test/regress/expected/copy2.out =================================================================== RCS file: /var/lib/cvs/pgsql/src/test/regress/expected/copy2.out,v retrieving revision 1.2 diff -c -r1.2 copy2.out *** src/test/regress/expected/copy2.out 18 Jul 2002 17:11:49 -0000 1.2 --- src/test/regress/expected/copy2.out 26 Jul 2002 01:04:22 -0000 *************** *** 23,32 **** FOR EACH ROW EXECUTE PROCEDURE fn_x_after(); CREATE TRIGGER trg_x_before BEFORE INSERT ON x FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); ! COPY x (a,b,c,d,e) from stdin; ! COPY x (b,d) from stdin; ! COPY x (b,d) from stdin; ! COPY x (a,b,c,d,e) from stdin; COPY x TO stdout; 10000 21 31 41 before trigger fired 10001 22 32 42 before trigger fired --- 23,66 ---- FOR EACH ROW EXECUTE PROCEDURE fn_x_after(); CREATE TRIGGER trg_x_before BEFORE INSERT ON x FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); ! COPY x (a, b, c, d, e) from stdin; ! COPY x (b, d) from stdin; ! COPY x (b, d) from stdin; ! COPY x (a, b, c, d, e) from stdin; ! -- non-existent column in column list: should fail ! COPY x (xyz) from stdin; ! ERROR: COPY: Specified column "xyz" does not exist ! -- too many columns in column list: should fail ! COPY x (a, b, c, d, e, d, c) from stdin; ! ERROR: COPY: Too many columns specified ! -- missing data: should fail ! COPY x from stdin; ! ERROR: copy: line 1, COPY TEXT: Missing data for attribute 1 ! lost synchronization with server, resetting connection ! COPY x from stdin; ! ERROR: copy: line 1, COPY TEXT: Missing data for attribute 4 ! lost synchronization with server, resetting connection ! COPY x from stdin; ! ERROR: copy: line 1, COPY TEXT: Missing data for attribute 4 ! lost synchronization with server, resetting connection ! -- extra data: should fail ! COPY x from stdin; ! ERROR: copy: line 1, COPY TEXT: Extra data encountered ! lost synchronization with server, resetting connection ! -- various COPY options: delimiters, oids, NULL string ! COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x'; ! -- COPY w/ oids on a table w/o oids should fail ! CREATE TABLE no_oids ( ! a int, ! b int ! ) WITHOUT OIDS; ! INSERT INTO no_oids (a, b) VALUES (5, 10); ! INSERT INTO no_oids (a, b) VALUES (20, 30); ! -- should fail ! COPY no_oids FROM stdin WITH OIDS; ! ERROR: COPY: table "no_oids" does not have OIDs ! COPY no_oids TO stdout WITH OIDS; ! ERROR: COPY: table "no_oids" does not have OIDs COPY x TO stdout; 10000 21 31 41 before trigger fired 10001 22 32 42 before trigger fired *************** *** 34,44 **** --- 68,92 ---- 10003 24 34 44 before trigger fired 10004 25 35 45 before trigger fired 10005 26 36 46 before trigger fired + 6 \N 45 80 before trigger fired 1 1 stuff test_1 after trigger fired 2 2 stuff test_2 after trigger fired 3 3 stuff test_3 after trigger fired 4 4 stuff test_4 after trigger fired 5 5 stuff test_5 after trigger fired + COPY x (c, e) TO stdout; + 31 before trigger fired + 32 before trigger fired + 33 before trigger fired + 34 before trigger fired + 35 before trigger fired + 36 before trigger fired + 45 before trigger fired + stuff after trigger fired + stuff after trigger fired + stuff after trigger fired + stuff after trigger fired + stuff after trigger fired DROP TABLE x; DROP FUNCTION fn_x_before(); DROP FUNCTION fn_x_after(); Index: src/test/regress/sql/copy2.sql =================================================================== RCS file: /var/lib/cvs/pgsql/src/test/regress/sql/copy2.sql,v retrieving revision 1.2 diff -c -r1.2 copy2.sql *** src/test/regress/sql/copy2.sql 18 Jul 2002 22:31:44 -0000 1.2 --- src/test/regress/sql/copy2.sql 26 Jul 2002 01:04:22 -0000 *************** *** 26,47 **** CREATE TRIGGER trg_x_before BEFORE INSERT ON x FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); ! COPY x (a,b,c,d,e) from stdin; 10000 21 31 41 51 \. ! COPY x (b,d) from stdin; 1 test_1 \. ! COPY x (b,d) from stdin; 2 test_2 3 test_3 4 test_4 5 test_5 \. ! COPY x (a,b,c,d,e) from stdin; 10001 22 32 42 52 10002 23 33 43 53 10003 24 34 44 54 --- 26,47 ---- CREATE TRIGGER trg_x_before BEFORE INSERT ON x FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); ! COPY x (a, b, c, d, e) from stdin; 10000 21 31 41 51 \. ! COPY x (b, d) from stdin; 1 test_1 \. ! COPY x (b, d) from stdin; 2 test_2 3 test_3 4 test_4 5 test_5 \. ! COPY x (a, b, c, d, e) from stdin; 10001 22 32 42 52 10002 23 33 43 53 10003 24 34 44 54 *************** *** 49,55 **** --- 49,96 ---- 10005 26 36 46 56 \. + -- non-existent column in column list: should fail + COPY x (xyz) from stdin; + + -- too many columns in column list: should fail + COPY x (a, b, c, d, e, d, c) from stdin; + + -- missing data: should fail + COPY x from stdin; + + \. + COPY x from stdin; + 2000 230 23 23 + \. + COPY x from stdin; + 2001 231 \N \N + \. + + -- extra data: should fail + COPY x from stdin; + 2002 232 40 50 60 70 80 + \. + + -- various COPY options: delimiters, oids, NULL string + COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x'; + 500000,x,45,80,90 + \. + + -- COPY w/ oids on a table w/o oids should fail + CREATE TABLE no_oids ( + a int, + b int + ) WITHOUT OIDS; + + INSERT INTO no_oids (a, b) VALUES (5, 10); + INSERT INTO no_oids (a, b) VALUES (20, 30); + + -- should fail + COPY no_oids FROM stdin WITH OIDS; + COPY no_oids TO stdout WITH OIDS; + COPY x TO stdout; + COPY x (c, e) TO stdout; DROP TABLE x; DROP FUNCTION fn_x_before(); DROP FUNCTION fn_x_after();