OSDN Git Service

IMPROVED VERSION APPLIED:
authorBruce Momjian <bruce@momjian.us>
Tue, 30 Jul 2002 16:55:06 +0000 (16:55 +0000)
committerBruce Momjian <bruce@momjian.us>
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 a1a5e9b..484b0a6 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/copy.sgml,v 1.33 2002/07/18 04:43:50 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/copy.sgml,v 1.34 2002/07/30 16:55:05 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -199,12 +199,12 @@ ERROR: <replaceable>reason</replaceable>
    whatever is in the table already).
   </para>
 
-       <para>
-   When using the optional column list syntax, <command>COPY TO</command> 
-        and <command>COPY FROM</command> will only copy the specified
-        columns' values to/from the table. If a column in the table
-        is not in the column list, <command>COPY FROM</command> will insert 
-        default values for that column if a default value is defined.
+  <para>
+   If a list of columns is specified, <command>COPY</command> 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,
+   <command>COPY FROM</command> will insert the default value for
+   that column.
   </para>
 
   <para>
@@ -266,8 +266,8 @@ ERROR: <replaceable>reason</replaceable>
    </para>
 
    <para>
-    <command>COPY FROM</command> neither invokes rules nor acts on column
-    defaults.  It does invoke triggers and check constraints.
+    <command>COPY FROM</command> will invoke any triggers or check
+       constraints. However, it will not invoke rules.
    </para>
 
    <para>
@@ -330,7 +330,12 @@ ERROR: <replaceable>reason</replaceable>
     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 <command>COPY
+       FROM</command> 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, <command>COPY FROM</command> 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.
    </para>
    <para>
     If OIDS is specified, the OID is read or written as the first column,
index 272e45d..19e17ed 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.
@@ -364,6 +367,13 @@ DoCopy(const CopyStmt *stmt)
                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,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<attr_count;++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 c84edb9..e4f806c 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 52d0bee..b0ccfe6 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();