IMPROVED VERSION APPLIED:
authorBruce Momjian
Tue, 30 Jul 2002 16:55:06 +0000 (16:55 +0000)
committerBruce Momjian
Tue, 30 Jul 2002 16:55:06 +0000 (16:55 +0000)
The attached patch completes the following TODO item:

    * Generate failure on short COPY lines rather than pad NULLs

I also restructed a lot of the existing COPY code, did some code
review on the column list patch sent in by Brent Verner a little
while ago, and added some regression tests. I also added an
explicit check (and resultant error) for extra data before
the end-of-line.

Neil Conway

doc/src/sgml/ref/copy.sgml
src/backend/commands/copy.c
src/test/regress/expected/copy2.out
src/test/regress/sql/copy2.sql

index a1a5e9baa765e8f0d0e64f6ed2d6e3f35658fad1..484b0a620871f07cc3143611749aea6119202d03 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -199,12 +199,12 @@ ERROR: reason
    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.
+  
+   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,8 +266,8 @@ ERROR: reason
    
 
    
-    COPY FROM neither invokes rules nor acts on column
-    defaults.  It does invoke triggers and check constraints.
+    COPY FROM will invoke any triggers or check
+   constraints. However, it will not invoke rules.
    
 
    
@@ -330,7 +330,12 @@ ERROR: reason
     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.
+    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 272e45d384e02a54a539bac3580a2146fff01a20..19e17ed2873d81ac0d0d3d7eaddafa4d3acebe26 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.160 2002/07/20 05:16:57 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.161 2002/07/30 16:55:06 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #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 void CopyReadNewline(FILE *fp, int *newline);
-static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print);
+static char *CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result);
 static void CopyAttributeOut(FILE *fp, char *string, char *delim);
-static void CopyAssertAttlist(Relation rel, List* attlist, bool from);
+static void CopyCheckAttlist(Relation rel, List *attlist);
 
 static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0";
 
@@ -271,14 +276,10 @@ DoCopy(const CopyStmt *stmt)
    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";
+   char *delim = NULL;
+   char *null_print = NULL;
    FILE       *fp;
    Relation    rel;
    AclMode     required_access = (is_from ? ACL_INSERT : ACL_SELECT);
@@ -289,51 +290,53 @@ DoCopy(const CopyStmt *stmt)
    {
        DefElem    *defel = (DefElem *) lfirst(option);
 
+       /* XXX: Should we bother checking for doubled options? */
+
        if (strcmp(defel->defname, "binary") == 0)
        {
-           if (dbinary)
-               /* should this really be an error? */
+           if (binary)
                elog(ERROR, "COPY: BINARY option appears more than once");
-           dbinary = defel;
+
+           binary = intVal(defel->arg);
        }
        else if (strcmp(defel->defname, "oids") == 0)
        {
-           if (doids)
-               /* should this really be an error? */
+           if (oids)
                elog(ERROR, "COPY: OIDS option appears more than once");
-           doids = defel;
+
+           oids = intVal(defel->arg);
        }
        else if (strcmp(defel->defname, "delimiter") == 0)
        {
-           if (ddelim)
+           if (delim)
                elog(ERROR, "COPY: DELIMITER string may only be defined once in query");
-           ddelim = defel;
+
+           delim = strVal(defel->arg);
        }
        else if (strcmp(defel->defname, "null") == 0)
        {
-           if (dnull)
+           if (null_print)
                elog(ERROR, "COPY: NULL representation may only be defined once in query");
-           dnull = defel;
+
+           null_print = strVal(defel->arg);
        }
        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)
+   if (binary && delim)
        elog(ERROR, "You can not specify the DELIMITER in BINARY mode.");
 
-   if (binary && dnull)
+   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.
@@ -363,6 +366,13 @@ DoCopy(const CopyStmt *stmt)
    if (strlen(delim) != 1)
        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.
     */
@@ -372,22 +382,24 @@ DoCopy(const CopyStmt *stmt)
    server_encoding = GetDatabaseEncoding();
 #endif
 
-   if( attlist == NIL ){
+   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);
+       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 ){
+   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);
+
+       CopyCheckAttlist(rel, attlist);
    }
    
    if (is_from)
@@ -532,27 +544,28 @@ CopyTo(Relation rel, List *attlist, bool binary, bool oids,
    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));
+   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)
    {
-       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;
-               }
+       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,7 +655,7 @@ CopyTo(Relation rel, List *attlist, bool binary, bool oids,
            Datum       origvalue,
                        value;
            bool        isnull;
-           int mi = attmap[i];
+           int         mi = attmap[i];
 
            origvalue = heap_getattr(tuple, mi + 1, tupDesc, &isnull);
 
@@ -767,19 +780,15 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
    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 */
+   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,40 +819,47 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
    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);
+       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;
+           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(cur,attlist){
-               if( strcmp(strVal(lfirst(cur)),NameStr(attr[i]->attname)) == 0){
+
+           foreach(l, attlist)
+           {
+               if (namestrcmp(&attr[i]->attname, strVal(lfirst(l))) == 0)
+               {
                    attmap[p] = i;
                    specified = true;
                    continue;
                }
-               ++p;
+               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 ){
+
+           /* 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;
+                   def_attr_count++;
                }
            }
        }
@@ -857,13 +873,11 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
 
        /* Signature */
        CopyGetData(readSig, 12, fp);
-       if (CopyGetEof(fp) ||
-           memcmp(readSig, BinarySignature, 12) != 0)
+       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)
+       if (CopyGetEof(fp) || tmp != 0x01020304)
            elog(ERROR, "COPY BINARY: incompatible integer layout");
        /* Flags field */
        CopyGetData(&tmp, sizeof(int32), fp);
@@ -875,8 +889,7 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
            elog(ERROR, "COPY BINARY: unrecognized critical flags in header");
        /* Header extension length */
        CopyGetData(&tmp, sizeof(int32), fp);
-       if (CopyGetEof(fp) ||
-           tmp < 0)
+       if (CopyGetEof(fp) || tmp < 0)
            elog(ERROR, "COPY BINARY: bogus file header (missing length)");
        /* Skip extension header, if present */
        while (tmp-- > 0)
@@ -890,10 +903,6 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
        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));
 
@@ -904,6 +913,9 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
 
    while (!done)
    {
+       bool    skip_tuple;
+       Oid     loaded_oid;
+
        CHECK_FOR_INTERRUPTS();
 
        copy_lineno++;
@@ -917,16 +929,17 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
 
        if (!binary)
        {
-           int         newline = 0;
+           CopyReadResult   result;
+           char            *string;
 
            if (file_has_oids)
            {
-               string = CopyReadAttribute(fp, &isnull, delim,
-                                          &newline, null_print);
-               if (isnull)
+               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 if (string == NULL)
-                   done = 1;   /* end of file */
                else
                {
                    loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin,
@@ -943,36 +956,45 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
            for (i = 0; i < copy_attr_count && !done; i++)
            {
                int m = attmap[i];
-               string = CopyReadAttribute(fp, &isnull, delim,
-                                          &newline, null_print);
 
-               if( isnull ){
-                   /* nothing */
+               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 if (string == NULL)
-                   done = 1;   /* end of file */
                else
                {
                    values[m] = FunctionCall3(&in_functions[m],
-                                               CStringGetDatum(string),
-                                           ObjectIdGetDatum(elements[m]),
-                                           Int32GetDatum(attr[m]->atttypmod));
+                                             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++){
+           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]] = ' ';
+               values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext,
+                                                &isnull, &isdone);
+
+               if (! isnull)
+                   nulls[defmap[i]] = ' ';
            }
-           if (!done)
-               CopyReadNewline(fp, &newline);
        }
        else
        {                       /* binary */
@@ -980,8 +1002,7 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
                        fld_size;
 
            CopyGetData(&fld_count, sizeof(int16), fp);
-           if (CopyGetEof(fp) ||
-               fld_count == -1)
+           if (CopyGetEof(fp) || fld_count == -1)
                done = 1;
            else
            {
@@ -1173,36 +1194,25 @@ GetTypeElement(Oid type)
    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!
+ * 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.
  *
- * *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
+ * delim is the column delimiter string.
  */
 
 static char *
-CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print)
+CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result)
 {
    int         c;
    int         delimc = (unsigned char)delim[0];
@@ -1220,23 +1230,20 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
    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 */
+   /* set default */
+   *result = NORMAL_ATTR;
 
    for (;;)
    {
        c = CopyGetChar(fp);
        if (c == EOF)
-           goto endOfFile;
+       {
+           *result = END_OF_FILE;
+           return NULL;
+       }
        if (c == '\n')
        {
-           *newline = 1;
+           *result = END_OF_LINE;
            break;
        }
        if (c == delimc)
@@ -1245,7 +1252,10 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
        {
            c = CopyGetChar(fp);
            if (c == EOF)
-               goto endOfFile;
+           {
+               *result = END_OF_FILE;
+               return NULL;
+           }
            switch (c)
            {
                case '0':
@@ -1274,14 +1284,20 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
                            else
                            {
                                if (c == EOF)
-                                   goto endOfFile;
+                               {
+                                   *result = END_OF_FILE;
+                                   return NULL;
+                               }
                                CopyDonePeek(fp, c, false /* put back */ );
                            }
                        }
                        else
                        {
                            if (c == EOF)
-                               goto endOfFile;
+                           {
+                               *result = END_OF_FILE;
+                               return NULL;
+                           }
                            CopyDonePeek(fp, c, false /* put back */ );
                        }
                        c = val & 0377;
@@ -1319,7 +1335,8 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
                    c = CopyGetChar(fp);
                    if (c != '\n')
                        elog(ERROR, "CopyReadAttribute: end of record marker corrupted");
-                   goto endOfFile;
+                   *result = END_OF_FILE;
+                   return NULL;
            }
        }
        appendStringInfoCharMacro(&attribute_buf, c);
@@ -1334,7 +1351,10 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
            {
                c = CopyGetChar(fp);
                if (c == EOF)
-                   goto endOfFile;
+               {
+                   *result = END_OF_FILE;
+                   return NULL;
+               }
                appendStringInfoCharMacro(&attribute_buf, c);
            }
        }
@@ -1357,13 +1377,7 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
    }
 #endif
 
-   if (strcmp(attribute_buf.data, null_print) == 0)
-       *isnull = true;
-
    return attribute_buf.data;
-
-endOfFile:
-   return NULL;
 }
 
 static void
@@ -1454,49 +1468,50 @@ CopyAttributeOut(FILE *fp, char *server_string, char *delim)
 }
 
 /*
- * CopyAssertAttlist: elog(ERROR,...) if the specified attlist
+ * CopyCheckAttlist: elog(ERROR,...) if the specified attlist
  *                    is not valid for the Relation
  */
 static void
-CopyAssertAttlist(Relation rel, List* attlist, bool from)
+CopyCheckAttlist(Relation rel, List *attlist)
 {
-   TupleDesc tupDesc;
-  List* cur;
-  char* illegalattname = NULL;
-  int attr_count;
-  const char* to_or_from;
-   
-   if( attlist == NIL )
-       return;
+   TupleDesc    tupDesc;
+   int          attr_count;
+   List        *l;
 
-   to_or_from = (from == true ? "FROM" : "TO");
+   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,"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(;i
-    {
-      if( strcmp(strVal(lfirst(cur)),NameStr(tupDesc->attrs[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); 
+   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 c84edb9864ff1adb01274fb9bcc3bbceb9e58e8e..e4f806c8a56f5978434a70a790624803dc8174d6 100644 (file)
@@ -23,10 +23,44 @@ CREATE TRIGGER trg_x_after AFTER INSERT ON x
 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 (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,11 +68,25 @@ COPY x TO stdout;
 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 52d0bee4ed2aa516f60b7401687e2906595dee90..b0ccfe671605c6db6b6fc9116bba90c3229967dd 100644 (file)
@@ -26,22 +26,22 @@ 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 (a, b, c, d, e) from stdin;
 10000  21  31  41  51
 \.
 
-COPY x (b,d) from stdin;
+COPY x (b, d) from stdin;
 1  test_1
 \.
 
-COPY x (b,d) from stdin;
+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;
+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,7 +49,48 @@ COPY x (a,b,c,d,e) from stdin;
 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();